Implement Audit Logs
This commit is contained in:
parent
2a74985e4b
commit
a5f44729e5
@ -24,8 +24,8 @@ CREATE TABLE IF NOT EXISTS audit_log (
|
||||
user_id varchar(31),
|
||||
user_name varchar(255),
|
||||
action_type varchar(255),
|
||||
action_data varchar(255),
|
||||
action_data varchar(1000000),
|
||||
action_status varchar(255),
|
||||
action_failure_details varchar
|
||||
action_failure_details varchar(1000000)
|
||||
);
|
||||
|
||||
|
||||
@ -40,6 +40,7 @@ import org.thingsboard.server.controller.plugin.PluginWebSocketMsgEndpoint;
|
||||
import org.thingsboard.server.dao.alarm.AlarmService;
|
||||
import org.thingsboard.server.dao.asset.AssetService;
|
||||
import org.thingsboard.server.dao.attributes.AttributesService;
|
||||
import org.thingsboard.server.dao.audit.AuditLogService;
|
||||
import org.thingsboard.server.dao.customer.CustomerService;
|
||||
import org.thingsboard.server.dao.device.DeviceService;
|
||||
import org.thingsboard.server.dao.event.EventService;
|
||||
@ -113,6 +114,9 @@ public class ActorSystemContext {
|
||||
@Autowired
|
||||
@Getter private RelationService relationService;
|
||||
|
||||
@Autowired
|
||||
@Getter private AuditLogService auditLogService;
|
||||
|
||||
@Autowired
|
||||
@Getter @Setter private PluginWebSocketMsgEndpoint wsMsgEndpoint;
|
||||
|
||||
|
||||
@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.Device;
|
||||
import org.thingsboard.server.common.data.EntityType;
|
||||
import org.thingsboard.server.common.data.Tenant;
|
||||
import org.thingsboard.server.common.data.asset.Asset;
|
||||
import org.thingsboard.server.common.data.audit.ActionType;
|
||||
import org.thingsboard.server.common.data.id.*;
|
||||
import org.thingsboard.server.common.data.kv.AttributeKey;
|
||||
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
|
||||
@ -41,9 +42,7 @@ import org.thingsboard.server.extensions.api.device.DeviceAttributesEventNotific
|
||||
import org.thingsboard.server.extensions.api.plugins.PluginApiCallSecurityContext;
|
||||
import org.thingsboard.server.extensions.api.plugins.PluginCallback;
|
||||
import org.thingsboard.server.extensions.api.plugins.PluginContext;
|
||||
import org.thingsboard.server.extensions.api.plugins.msg.PluginToRuleMsg;
|
||||
import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg;
|
||||
import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequest;
|
||||
import org.thingsboard.server.extensions.api.plugins.msg.*;
|
||||
import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg;
|
||||
import org.thingsboard.server.extensions.api.plugins.rpc.RpcMsg;
|
||||
import org.thingsboard.server.extensions.api.plugins.ws.PluginWebsocketSessionRef;
|
||||
@ -196,6 +195,52 @@ public final class PluginProcessingContext implements PluginContext {
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logAttributesUpdated(PluginApiCallSecurityContext ctx, EntityId entityId, String attributeType,
|
||||
List<AttributeKvEntry> attributes, Exception e) {
|
||||
pluginCtx.auditLogService.logEntityAction(
|
||||
ctx.getTenantId(),
|
||||
ctx.getCustomerId(),
|
||||
ctx.getUserId(),
|
||||
ctx.getUserName(),
|
||||
(UUIDBased & EntityId)entityId,
|
||||
null,
|
||||
ActionType.ATTRIBUTES_UPDATED,
|
||||
e,
|
||||
attributeType,
|
||||
attributes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logAttributesDeleted(PluginApiCallSecurityContext ctx, EntityId entityId, String attributeType, List<String> keys, Exception e) {
|
||||
pluginCtx.auditLogService.logEntityAction(
|
||||
ctx.getTenantId(),
|
||||
ctx.getCustomerId(),
|
||||
ctx.getUserId(),
|
||||
ctx.getUserName(),
|
||||
(UUIDBased & EntityId)entityId,
|
||||
null,
|
||||
ActionType.ATTRIBUTES_DELETED,
|
||||
e,
|
||||
attributeType,
|
||||
keys);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logAttributesRead(PluginApiCallSecurityContext ctx, EntityId entityId, String attributeType, List<String> keys, Exception e) {
|
||||
pluginCtx.auditLogService.logEntityAction(
|
||||
ctx.getTenantId(),
|
||||
ctx.getCustomerId(),
|
||||
ctx.getUserId(),
|
||||
ctx.getUserName(),
|
||||
(UUIDBased & EntityId)entityId,
|
||||
null,
|
||||
ActionType.ATTRIBUTES_READ,
|
||||
e,
|
||||
attributeType,
|
||||
keys);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadLatestTimeseries(final EntityId entityId, final Collection<String> keys, final PluginCallback<List<TsKvEntry>> callback) {
|
||||
validate(entityId, new ValidationCallback(callback, ctx -> {
|
||||
@ -460,6 +505,29 @@ public final class PluginProcessingContext implements PluginContext {
|
||||
pluginCtx.sendRpcRequest(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logRpcRequest(PluginApiCallSecurityContext ctx, DeviceId deviceId, ToDeviceRpcRequestBody body, boolean oneWay, Optional<RpcError> rpcError, Exception e) {
|
||||
String rpcErrorStr = "";
|
||||
if (rpcError.isPresent()) {
|
||||
rpcErrorStr = "RPC Error: " + rpcError.get().name();
|
||||
}
|
||||
String method = body.getMethod();
|
||||
String params = body.getParams();
|
||||
pluginCtx.auditLogService.logEntityAction(
|
||||
ctx.getTenantId(),
|
||||
ctx.getCustomerId(),
|
||||
ctx.getUserId(),
|
||||
ctx.getUserName(),
|
||||
deviceId,
|
||||
null,
|
||||
ActionType.RPC_CALL,
|
||||
e,
|
||||
rpcErrorStr,
|
||||
new Boolean(oneWay),
|
||||
method,
|
||||
params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scheduleTimeoutMsg(TimeoutMsg msg) {
|
||||
pluginCtx.scheduleTimeoutMsg(msg);
|
||||
|
||||
@ -27,6 +27,7 @@ import org.thingsboard.server.controller.plugin.PluginWebSocketMsgEndpoint;
|
||||
import org.thingsboard.server.common.data.id.PluginId;
|
||||
import org.thingsboard.server.dao.asset.AssetService;
|
||||
import org.thingsboard.server.dao.attributes.AttributesService;
|
||||
import org.thingsboard.server.dao.audit.AuditLogService;
|
||||
import org.thingsboard.server.dao.customer.CustomerService;
|
||||
import org.thingsboard.server.dao.device.DeviceService;
|
||||
import org.thingsboard.server.dao.plugin.PluginService;
|
||||
@ -63,6 +64,7 @@ public final class SharedPluginProcessingContext {
|
||||
final ClusterRpcService rpcService;
|
||||
final ClusterRoutingService routingService;
|
||||
final RelationService relationService;
|
||||
final AuditLogService auditLogService;
|
||||
final PluginId pluginId;
|
||||
final TenantId tenantId;
|
||||
|
||||
@ -86,6 +88,7 @@ public final class SharedPluginProcessingContext {
|
||||
this.customerService = sysContext.getCustomerService();
|
||||
this.tenantService = sysContext.getTenantService();
|
||||
this.relationService = sysContext.getRelationService();
|
||||
this.auditLogService = sysContext.getAuditLogService();
|
||||
}
|
||||
|
||||
public PluginId getPluginId() {
|
||||
|
||||
@ -148,7 +148,7 @@ public class BasicRpcSessionListener implements GrpcSessionListener {
|
||||
DeviceId deviceId = new DeviceId(toUUID(msg.getDeviceId()));
|
||||
|
||||
ToDeviceRpcRequestBody requestBody = new ToDeviceRpcRequestBody(msg.getMethod(), msg.getParams());
|
||||
ToDeviceRpcRequest request = new ToDeviceRpcRequest(toUUID(msg.getMsgId()), deviceTenantId, deviceId, msg.getOneway(), msg.getExpTime(), requestBody);
|
||||
ToDeviceRpcRequest request = new ToDeviceRpcRequest(toUUID(msg.getMsgId()), null, deviceTenantId, deviceId, msg.getOneway(), msg.getExpTime(), requestBody);
|
||||
|
||||
return new ToDeviceRpcRequestPluginMsg(serverAddress, pluginId, pluginTenantId, request);
|
||||
}
|
||||
|
||||
@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Copyright © 2016-2017 The Thingsboard Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.thingsboard.server.config;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.thingsboard.server.common.data.EntityType;
|
||||
import org.thingsboard.server.common.data.audit.ActionType;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "audit_log.logging_level")
|
||||
public class AuditLogLevelProperties {
|
||||
|
||||
private Map<String, String> mask = new HashMap<>();
|
||||
|
||||
public AuditLogLevelProperties() {
|
||||
super();
|
||||
}
|
||||
|
||||
public void setMask(Map<String, String> mask) {
|
||||
this.mask = mask;
|
||||
}
|
||||
|
||||
public Map<String, String> getMask() {
|
||||
return this.mask;
|
||||
}
|
||||
}
|
||||
@ -40,6 +40,7 @@ import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.web.cors.CorsUtils;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
import org.springframework.web.filter.CorsFilter;
|
||||
import org.thingsboard.server.dao.audit.AuditLogLevelFilter;
|
||||
import org.thingsboard.server.exception.ThingsboardErrorResponseHandler;
|
||||
import org.thingsboard.server.service.security.auth.rest.RestAuthenticationProvider;
|
||||
import org.thingsboard.server.service.security.auth.rest.RestLoginProcessingFilter;
|
||||
@ -198,4 +199,9 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt
|
||||
return new CorsFilter(source);
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AuditLogLevelFilter auditLogLevelFilter(@Autowired AuditLogLevelProperties auditLogLevelProperties) {
|
||||
return new AuditLogLevelFilter(auditLogLevelProperties.getMask());
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,7 +21,9 @@ import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.thingsboard.server.common.data.Customer;
|
||||
import org.thingsboard.server.common.data.EntitySubtype;
|
||||
import org.thingsboard.server.common.data.EntityType;
|
||||
import org.thingsboard.server.common.data.asset.Asset;
|
||||
import org.thingsboard.server.common.data.audit.ActionType;
|
||||
import org.thingsboard.server.common.data.id.AssetId;
|
||||
import org.thingsboard.server.common.data.id.CustomerId;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
@ -73,8 +75,16 @@ public class AssetController extends BaseController {
|
||||
checkCustomerId(asset.getCustomerId());
|
||||
}
|
||||
}
|
||||
return checkNotNull(assetService.saveAsset(asset));
|
||||
Asset savedAsset = checkNotNull(assetService.saveAsset(asset));
|
||||
|
||||
logEntityAction(savedAsset.getId(), savedAsset,
|
||||
savedAsset.getCustomerId(),
|
||||
asset.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null);
|
||||
|
||||
return savedAsset;
|
||||
} catch (Exception e) {
|
||||
logEntityAction(emptyId(EntityType.ASSET), asset,
|
||||
null, asset.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e);
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
@ -86,9 +96,18 @@ public class AssetController extends BaseController {
|
||||
checkParameter(ASSET_ID, strAssetId);
|
||||
try {
|
||||
AssetId assetId = new AssetId(toUUID(strAssetId));
|
||||
checkAssetId(assetId);
|
||||
Asset asset = checkAssetId(assetId);
|
||||
assetService.deleteAsset(assetId);
|
||||
|
||||
logEntityAction(assetId, asset,
|
||||
asset.getCustomerId(),
|
||||
ActionType.DELETED, null, strAssetId);
|
||||
|
||||
} catch (Exception e) {
|
||||
logEntityAction(emptyId(EntityType.ASSET),
|
||||
null,
|
||||
null,
|
||||
ActionType.DELETED, e, strAssetId);
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
@ -102,13 +121,24 @@ public class AssetController extends BaseController {
|
||||
checkParameter(ASSET_ID, strAssetId);
|
||||
try {
|
||||
CustomerId customerId = new CustomerId(toUUID(strCustomerId));
|
||||
checkCustomerId(customerId);
|
||||
Customer customer = checkCustomerId(customerId);
|
||||
|
||||
AssetId assetId = new AssetId(toUUID(strAssetId));
|
||||
checkAssetId(assetId);
|
||||
|
||||
return checkNotNull(assetService.assignAssetToCustomer(assetId, customerId));
|
||||
Asset savedAsset = checkNotNull(assetService.assignAssetToCustomer(assetId, customerId));
|
||||
|
||||
logEntityAction(assetId, savedAsset,
|
||||
savedAsset.getCustomerId(),
|
||||
ActionType.ASSIGNED_TO_CUSTOMER, null, strAssetId, strCustomerId, customer.getName());
|
||||
|
||||
return savedAsset;
|
||||
} catch (Exception e) {
|
||||
|
||||
logEntityAction(emptyId(EntityType.ASSET), null,
|
||||
null,
|
||||
ActionType.ASSIGNED_TO_CUSTOMER, e, strAssetId, strCustomerId);
|
||||
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
@ -124,8 +154,22 @@ public class AssetController extends BaseController {
|
||||
if (asset.getCustomerId() == null || asset.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
|
||||
throw new IncorrectParameterException("Asset isn't assigned to any customer!");
|
||||
}
|
||||
return checkNotNull(assetService.unassignAssetFromCustomer(assetId));
|
||||
|
||||
Customer customer = checkCustomerId(asset.getCustomerId());
|
||||
|
||||
Asset savedAsset = checkNotNull(assetService.unassignAssetFromCustomer(assetId));
|
||||
|
||||
logEntityAction(assetId, asset,
|
||||
asset.getCustomerId(),
|
||||
ActionType.UNASSIGNED_FROM_CUSTOMER, null, strAssetId, customer.getId().toString(), customer.getName());
|
||||
|
||||
return savedAsset;
|
||||
} catch (Exception e) {
|
||||
|
||||
logEntityAction(emptyId(EntityType.ASSET), null,
|
||||
null,
|
||||
ActionType.UNASSIGNED_FROM_CUSTOMER, e, strAssetId);
|
||||
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
@ -139,8 +183,19 @@ public class AssetController extends BaseController {
|
||||
AssetId assetId = new AssetId(toUUID(strAssetId));
|
||||
Asset asset = checkAssetId(assetId);
|
||||
Customer publicCustomer = customerService.findOrCreatePublicCustomer(asset.getTenantId());
|
||||
return checkNotNull(assetService.assignAssetToCustomer(assetId, publicCustomer.getId()));
|
||||
Asset savedAsset = checkNotNull(assetService.assignAssetToCustomer(assetId, publicCustomer.getId()));
|
||||
|
||||
logEntityAction(assetId, savedAsset,
|
||||
savedAsset.getCustomerId(),
|
||||
ActionType.ASSIGNED_TO_CUSTOMER, null, strAssetId, publicCustomer.getId().toString(), publicCustomer.getName());
|
||||
|
||||
return savedAsset;
|
||||
} catch (Exception e) {
|
||||
|
||||
logEntityAction(emptyId(EntityType.ASSET), null,
|
||||
null,
|
||||
ActionType.ASSIGNED_TO_CUSTOMER, e, strAssetId);
|
||||
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -79,9 +79,6 @@ public abstract class BaseController {
|
||||
public static final String INCORRECT_TENANT_ID = "Incorrect tenantId ";
|
||||
public static final String YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION = "You don't have permission to perform this operation!";
|
||||
|
||||
@Value("${audit_log.exceptions.enabled}")
|
||||
private boolean auditLogExceptionsEnabled;
|
||||
|
||||
@Autowired
|
||||
private ThingsboardErrorResponseHandler errorResponseHandler;
|
||||
|
||||
@ -130,11 +127,6 @@ public abstract class BaseController {
|
||||
@Autowired
|
||||
protected AuditLogService auditLogService;
|
||||
|
||||
@ExceptionHandler(Exception.class)
|
||||
public void handleException(Exception ex, HttpServletResponse response) {
|
||||
errorResponseHandler.handle(ex, response);
|
||||
}
|
||||
|
||||
@ExceptionHandler(ThingsboardException.class)
|
||||
public void handleThingsboardException(ThingsboardException ex, HttpServletResponse response) {
|
||||
errorResponseHandler.handle(ex, response);
|
||||
@ -144,11 +136,6 @@ public abstract class BaseController {
|
||||
return handleException(exception, true);
|
||||
}
|
||||
|
||||
ThingsboardException handleException(Exception exception, ActionType actionType, String actionData) {
|
||||
logExceptionToAuditLog(exception, actionType, actionData);
|
||||
return handleException(exception, true);
|
||||
}
|
||||
|
||||
private ThingsboardException handleException(Exception exception, boolean logException) {
|
||||
if (logException) {
|
||||
log.error("Error [{}]", exception.getMessage());
|
||||
@ -171,36 +158,6 @@ public abstract class BaseController {
|
||||
}
|
||||
}
|
||||
|
||||
private void logExceptionToAuditLog(Exception exception, ActionType actionType, String actionData) {
|
||||
try {
|
||||
if (auditLogExceptionsEnabled) {
|
||||
SecurityUser currentUser = getCurrentUser();
|
||||
EntityId entityId;
|
||||
CustomerId customerId;
|
||||
if (!currentUser.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
|
||||
entityId = currentUser.getCustomerId();
|
||||
customerId = currentUser.getCustomerId();
|
||||
} else {
|
||||
entityId = currentUser.getTenantId();
|
||||
customerId = new CustomerId(ModelConstants.NULL_UUID);
|
||||
}
|
||||
|
||||
JsonNode actionDataNode = new ObjectMapper().createObjectNode().put("actionData", actionData);
|
||||
|
||||
auditLogService.logEntityAction(currentUser,
|
||||
entityId,
|
||||
null,
|
||||
customerId,
|
||||
actionType,
|
||||
actionDataNode,
|
||||
ActionStatus.FAILURE,
|
||||
exception.getMessage());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Exception happend during saving to audit log", e);
|
||||
}
|
||||
}
|
||||
|
||||
<T> T checkNotNull(T reference) throws ThingsboardException {
|
||||
if (reference == null) {
|
||||
throw new ThingsboardException("Requested item wasn't found!", ThingsboardErrorCode.ITEM_NOT_FOUND);
|
||||
@ -594,23 +551,19 @@ public abstract class BaseController {
|
||||
return baseUrl;
|
||||
}
|
||||
|
||||
protected void logEntityDeleted(EntityId entityId, String entityName, CustomerId customerId) throws ThingsboardException {
|
||||
logEntitySuccess(entityId, entityName, customerId, ActionType.DELETED);
|
||||
protected <I extends UUIDBased & EntityId> I emptyId(EntityType entityType) {
|
||||
return (I)EntityIdFactory.getByTypeAndUuid(entityType, ModelConstants.NULL_UUID);
|
||||
}
|
||||
|
||||
protected void logEntityAddedOrUpdated(EntityId entityId, String entityName, CustomerId customerId, boolean isAddAction) throws ThingsboardException {
|
||||
logEntitySuccess(entityId, entityName, customerId, isAddAction ? ActionType.ADDED : ActionType.UPDATED);
|
||||
protected <E extends BaseData<I> & HasName,
|
||||
I extends UUIDBased & EntityId> void logEntityAction(I entityId, E entity, CustomerId customerId,
|
||||
ActionType actionType, Exception e, Object... additionalInfo) throws ThingsboardException {
|
||||
User user = getCurrentUser();
|
||||
if (customerId == null || customerId.isNullUid()) {
|
||||
customerId = user.getCustomerId();
|
||||
}
|
||||
auditLogService.logEntityAction(user.getTenantId(), customerId, user.getId(), user.getName(), entityId, entity, actionType, e, additionalInfo);
|
||||
}
|
||||
|
||||
protected void logEntitySuccess(EntityId entityId, String entityName, CustomerId customerId, ActionType actionType) throws ThingsboardException {
|
||||
auditLogService.logEntityAction(
|
||||
getCurrentUser(),
|
||||
entityId,
|
||||
entityName,
|
||||
customerId,
|
||||
actionType,
|
||||
null,
|
||||
ActionStatus.SUCCESS,
|
||||
null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -22,6 +22,8 @@ import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.thingsboard.server.common.data.Customer;
|
||||
import org.thingsboard.server.common.data.EntityType;
|
||||
import org.thingsboard.server.common.data.audit.ActionType;
|
||||
import org.thingsboard.server.common.data.id.CustomerId;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.data.page.TextPageData;
|
||||
@ -86,8 +88,18 @@ public class CustomerController extends BaseController {
|
||||
public Customer saveCustomer(@RequestBody Customer customer) throws ThingsboardException {
|
||||
try {
|
||||
customer.setTenantId(getCurrentUser().getTenantId());
|
||||
return checkNotNull(customerService.saveCustomer(customer));
|
||||
Customer savedCustomer = checkNotNull(customerService.saveCustomer(customer));
|
||||
|
||||
logEntityAction(savedCustomer.getId(), savedCustomer,
|
||||
savedCustomer.getId(),
|
||||
customer.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null);
|
||||
|
||||
return savedCustomer;
|
||||
} catch (Exception e) {
|
||||
|
||||
logEntityAction(emptyId(EntityType.CUSTOMER), customer,
|
||||
null, customer.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e);
|
||||
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
@ -99,9 +111,20 @@ public class CustomerController extends BaseController {
|
||||
checkParameter(CUSTOMER_ID, strCustomerId);
|
||||
try {
|
||||
CustomerId customerId = new CustomerId(toUUID(strCustomerId));
|
||||
checkCustomerId(customerId);
|
||||
Customer customer = checkCustomerId(customerId);
|
||||
customerService.deleteCustomer(customerId);
|
||||
|
||||
logEntityAction(customerId, customer,
|
||||
customer.getId(),
|
||||
ActionType.DELETED, null, strCustomerId);
|
||||
|
||||
} catch (Exception e) {
|
||||
|
||||
logEntityAction(emptyId(EntityType.CUSTOMER),
|
||||
null,
|
||||
null,
|
||||
ActionType.DELETED, e, strCustomerId);
|
||||
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,6 +21,8 @@ import org.springframework.web.bind.annotation.*;
|
||||
import org.thingsboard.server.common.data.Customer;
|
||||
import org.thingsboard.server.common.data.Dashboard;
|
||||
import org.thingsboard.server.common.data.DashboardInfo;
|
||||
import org.thingsboard.server.common.data.EntityType;
|
||||
import org.thingsboard.server.common.data.audit.ActionType;
|
||||
import org.thingsboard.server.common.data.id.CustomerId;
|
||||
import org.thingsboard.server.common.data.id.DashboardId;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
@ -75,8 +77,17 @@ public class DashboardController extends BaseController {
|
||||
public Dashboard saveDashboard(@RequestBody Dashboard dashboard) throws ThingsboardException {
|
||||
try {
|
||||
dashboard.setTenantId(getCurrentUser().getTenantId());
|
||||
return checkNotNull(dashboardService.saveDashboard(dashboard));
|
||||
Dashboard savedDashboard = checkNotNull(dashboardService.saveDashboard(dashboard));
|
||||
|
||||
logEntityAction(savedDashboard.getId(), savedDashboard,
|
||||
savedDashboard.getCustomerId(),
|
||||
dashboard.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null);
|
||||
|
||||
return savedDashboard;
|
||||
} catch (Exception e) {
|
||||
logEntityAction(emptyId(EntityType.DASHBOARD), dashboard,
|
||||
null, dashboard.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e);
|
||||
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
@ -88,9 +99,20 @@ public class DashboardController extends BaseController {
|
||||
checkParameter(DASHBOARD_ID, strDashboardId);
|
||||
try {
|
||||
DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
|
||||
checkDashboardId(dashboardId);
|
||||
Dashboard dashboard = checkDashboardId(dashboardId);
|
||||
dashboardService.deleteDashboard(dashboardId);
|
||||
|
||||
logEntityAction(dashboardId, dashboard,
|
||||
dashboard.getCustomerId(),
|
||||
ActionType.DELETED, null, strDashboardId);
|
||||
|
||||
} catch (Exception e) {
|
||||
|
||||
logEntityAction(emptyId(EntityType.DASHBOARD),
|
||||
null,
|
||||
null,
|
||||
ActionType.DELETED, e, strDashboardId);
|
||||
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
@ -104,13 +126,25 @@ public class DashboardController extends BaseController {
|
||||
checkParameter(DASHBOARD_ID, strDashboardId);
|
||||
try {
|
||||
CustomerId customerId = new CustomerId(toUUID(strCustomerId));
|
||||
checkCustomerId(customerId);
|
||||
Customer customer = checkCustomerId(customerId);
|
||||
|
||||
DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
|
||||
checkDashboardId(dashboardId);
|
||||
|
||||
return checkNotNull(dashboardService.assignDashboardToCustomer(dashboardId, customerId));
|
||||
Dashboard savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(dashboardId, customerId));
|
||||
|
||||
logEntityAction(dashboardId, savedDashboard,
|
||||
savedDashboard.getCustomerId(),
|
||||
ActionType.ASSIGNED_TO_CUSTOMER, null, strDashboardId, strCustomerId, customer.getName());
|
||||
|
||||
|
||||
return savedDashboard;
|
||||
} catch (Exception e) {
|
||||
|
||||
logEntityAction(emptyId(EntityType.DASHBOARD), null,
|
||||
null,
|
||||
ActionType.ASSIGNED_TO_CUSTOMER, e, strDashboardId, strCustomerId);
|
||||
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
@ -126,8 +160,22 @@ public class DashboardController extends BaseController {
|
||||
if (dashboard.getCustomerId() == null || dashboard.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
|
||||
throw new IncorrectParameterException("Dashboard isn't assigned to any customer!");
|
||||
}
|
||||
return checkNotNull(dashboardService.unassignDashboardFromCustomer(dashboardId));
|
||||
|
||||
Customer customer = checkCustomerId(dashboard.getCustomerId());
|
||||
|
||||
Dashboard savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(dashboardId));
|
||||
|
||||
logEntityAction(dashboardId, dashboard,
|
||||
dashboard.getCustomerId(),
|
||||
ActionType.UNASSIGNED_FROM_CUSTOMER, null, strDashboardId, customer.getId().toString(), customer.getName());
|
||||
|
||||
return savedDashboard;
|
||||
} catch (Exception e) {
|
||||
|
||||
logEntityAction(emptyId(EntityType.DASHBOARD), null,
|
||||
null,
|
||||
ActionType.UNASSIGNED_FROM_CUSTOMER, e, strDashboardId);
|
||||
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
@ -141,8 +189,19 @@ public class DashboardController extends BaseController {
|
||||
DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
|
||||
Dashboard dashboard = checkDashboardId(dashboardId);
|
||||
Customer publicCustomer = customerService.findOrCreatePublicCustomer(dashboard.getTenantId());
|
||||
return checkNotNull(dashboardService.assignDashboardToCustomer(dashboardId, publicCustomer.getId()));
|
||||
Dashboard savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(dashboardId, publicCustomer.getId()));
|
||||
|
||||
logEntityAction(dashboardId, savedDashboard,
|
||||
savedDashboard.getCustomerId(),
|
||||
ActionType.ASSIGNED_TO_CUSTOMER, null, strDashboardId, publicCustomer.getId().toString(), publicCustomer.getName());
|
||||
|
||||
return savedDashboard;
|
||||
} catch (Exception e) {
|
||||
|
||||
logEntityAction(emptyId(EntityType.DASHBOARD), null,
|
||||
null,
|
||||
ActionType.ASSIGNED_TO_CUSTOMER, e, strDashboardId);
|
||||
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,6 +22,7 @@ import org.springframework.web.bind.annotation.*;
|
||||
import org.thingsboard.server.common.data.Customer;
|
||||
import org.thingsboard.server.common.data.Device;
|
||||
import org.thingsboard.server.common.data.EntitySubtype;
|
||||
import org.thingsboard.server.common.data.EntityType;
|
||||
import org.thingsboard.server.common.data.audit.ActionStatus;
|
||||
import org.thingsboard.server.common.data.audit.ActionType;
|
||||
import org.thingsboard.server.common.data.device.DeviceSearchQuery;
|
||||
@ -85,11 +86,15 @@ public class DeviceController extends BaseController {
|
||||
savedDevice.getName(),
|
||||
savedDevice.getType());
|
||||
|
||||
logEntityAddedOrUpdated(savedDevice.getId(), savedDevice.getName(), savedDevice.getCustomerId(), device.getId() == null);
|
||||
logEntityAction(savedDevice.getId(), savedDevice,
|
||||
savedDevice.getCustomerId(),
|
||||
device.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null);
|
||||
|
||||
return savedDevice;
|
||||
} catch (Exception e) {
|
||||
throw handleException(e, device.getId() == null ? ActionType.ADDED : ActionType.UPDATED, "addDevice(" + device + ")");
|
||||
logEntityAction(emptyId(EntityType.DEVICE), device,
|
||||
null, device.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e);
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -102,9 +107,17 @@ public class DeviceController extends BaseController {
|
||||
DeviceId deviceId = new DeviceId(toUUID(strDeviceId));
|
||||
Device device = checkDeviceId(deviceId);
|
||||
deviceService.deleteDevice(deviceId);
|
||||
logEntityDeleted(device.getId(), device.getName(), device.getCustomerId());
|
||||
|
||||
logEntityAction(deviceId, device,
|
||||
device.getCustomerId(),
|
||||
ActionType.DELETED, null, strDeviceId);
|
||||
|
||||
} catch (Exception e) {
|
||||
throw handleException(e, ActionType.DELETED, "deleteDevice(" + strDeviceId + ")");
|
||||
logEntityAction(emptyId(EntityType.DEVICE),
|
||||
null,
|
||||
null,
|
||||
ActionType.DELETED, e, strDeviceId);
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -117,13 +130,22 @@ public class DeviceController extends BaseController {
|
||||
checkParameter(DEVICE_ID, strDeviceId);
|
||||
try {
|
||||
CustomerId customerId = new CustomerId(toUUID(strCustomerId));
|
||||
checkCustomerId(customerId);
|
||||
Customer customer = checkCustomerId(customerId);
|
||||
|
||||
DeviceId deviceId = new DeviceId(toUUID(strDeviceId));
|
||||
checkDeviceId(deviceId);
|
||||
|
||||
return checkNotNull(deviceService.assignDeviceToCustomer(deviceId, customerId));
|
||||
Device savedDevice = checkNotNull(deviceService.assignDeviceToCustomer(deviceId, customerId));
|
||||
|
||||
logEntityAction(deviceId, savedDevice,
|
||||
savedDevice.getCustomerId(),
|
||||
ActionType.ASSIGNED_TO_CUSTOMER, null, strDeviceId, strCustomerId, customer.getName());
|
||||
|
||||
return savedDevice;
|
||||
} catch (Exception e) {
|
||||
logEntityAction(emptyId(EntityType.DEVICE), null,
|
||||
null,
|
||||
ActionType.ASSIGNED_TO_CUSTOMER, e, strDeviceId, strCustomerId);
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
@ -139,8 +161,19 @@ public class DeviceController extends BaseController {
|
||||
if (device.getCustomerId() == null || device.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
|
||||
throw new IncorrectParameterException("Device isn't assigned to any customer!");
|
||||
}
|
||||
return checkNotNull(deviceService.unassignDeviceFromCustomer(deviceId));
|
||||
Customer customer = checkCustomerId(device.getCustomerId());
|
||||
|
||||
Device savedDevice = checkNotNull(deviceService.unassignDeviceFromCustomer(deviceId));
|
||||
|
||||
logEntityAction(deviceId, device,
|
||||
device.getCustomerId(),
|
||||
ActionType.UNASSIGNED_FROM_CUSTOMER, null, strDeviceId, customer.getId().toString(), customer.getName());
|
||||
|
||||
return savedDevice;
|
||||
} catch (Exception e) {
|
||||
logEntityAction(emptyId(EntityType.DEVICE), null,
|
||||
null,
|
||||
ActionType.UNASSIGNED_FROM_CUSTOMER, e, strDeviceId);
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
@ -154,8 +187,17 @@ public class DeviceController extends BaseController {
|
||||
DeviceId deviceId = new DeviceId(toUUID(strDeviceId));
|
||||
Device device = checkDeviceId(deviceId);
|
||||
Customer publicCustomer = customerService.findOrCreatePublicCustomer(device.getTenantId());
|
||||
return checkNotNull(deviceService.assignDeviceToCustomer(deviceId, publicCustomer.getId()));
|
||||
Device savedDevice = checkNotNull(deviceService.assignDeviceToCustomer(deviceId, publicCustomer.getId()));
|
||||
|
||||
logEntityAction(deviceId, savedDevice,
|
||||
savedDevice.getCustomerId(),
|
||||
ActionType.ASSIGNED_TO_CUSTOMER, null, strDeviceId, publicCustomer.getId().toString(), publicCustomer.getName());
|
||||
|
||||
return savedDevice;
|
||||
} catch (Exception e) {
|
||||
logEntityAction(emptyId(EntityType.DEVICE), null,
|
||||
null,
|
||||
ActionType.ASSIGNED_TO_CUSTOMER, e, strDeviceId);
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
@ -167,9 +209,16 @@ public class DeviceController extends BaseController {
|
||||
checkParameter(DEVICE_ID, strDeviceId);
|
||||
try {
|
||||
DeviceId deviceId = new DeviceId(toUUID(strDeviceId));
|
||||
checkDeviceId(deviceId);
|
||||
return checkNotNull(deviceCredentialsService.findDeviceCredentialsByDeviceId(deviceId));
|
||||
Device device = checkDeviceId(deviceId);
|
||||
DeviceCredentials deviceCredentials = checkNotNull(deviceCredentialsService.findDeviceCredentialsByDeviceId(deviceId));
|
||||
logEntityAction(deviceId, device,
|
||||
device.getCustomerId(),
|
||||
ActionType.CREDENTIALS_READ, null, strDeviceId);
|
||||
return deviceCredentials;
|
||||
} catch (Exception e) {
|
||||
logEntityAction(emptyId(EntityType.DEVICE), null,
|
||||
null,
|
||||
ActionType.CREDENTIALS_READ, e, strDeviceId);
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
@ -183,10 +232,15 @@ public class DeviceController extends BaseController {
|
||||
Device device = checkDeviceId(deviceCredentials.getDeviceId());
|
||||
DeviceCredentials result = checkNotNull(deviceCredentialsService.updateDeviceCredentials(deviceCredentials));
|
||||
actorService.onCredentialsUpdate(getCurrentUser().getTenantId(), deviceCredentials.getDeviceId());
|
||||
logEntitySuccess(device.getId(), device.getName(), device.getCustomerId(), ActionType.CREDENTIALS_UPDATED);
|
||||
logEntityAction(device.getId(), device,
|
||||
device.getCustomerId(),
|
||||
ActionType.CREDENTIALS_UPDATED, null, deviceCredentials);
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
throw handleException(e, ActionType.CREDENTIALS_UPDATED, "saveDeviceCredentials(" + deviceCredentials + ")");
|
||||
logEntityAction(emptyId(EntityType.DEVICE), null,
|
||||
null,
|
||||
ActionType.CREDENTIALS_UPDATED, e, deviceCredentials);
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -18,6 +18,8 @@ package org.thingsboard.server.controller;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.thingsboard.server.common.data.EntityType;
|
||||
import org.thingsboard.server.common.data.audit.ActionType;
|
||||
import org.thingsboard.server.common.data.id.PluginId;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.data.page.TextPageData;
|
||||
@ -71,8 +73,17 @@ public class PluginController extends BaseController {
|
||||
PluginMetaData plugin = checkNotNull(pluginService.savePlugin(source));
|
||||
actorService.onPluginStateChange(plugin.getTenantId(), plugin.getId(),
|
||||
created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED);
|
||||
|
||||
logEntityAction(plugin.getId(), plugin,
|
||||
null,
|
||||
created ? ActionType.ADDED : ActionType.UPDATED, null);
|
||||
|
||||
return plugin;
|
||||
} catch (Exception e) {
|
||||
|
||||
logEntityAction(emptyId(EntityType.PLUGIN), source,
|
||||
null, source.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e);
|
||||
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
@ -87,7 +98,18 @@ public class PluginController extends BaseController {
|
||||
PluginMetaData plugin = checkPlugin(pluginService.findPluginById(pluginId));
|
||||
pluginService.activatePluginById(pluginId);
|
||||
actorService.onPluginStateChange(plugin.getTenantId(), plugin.getId(), ComponentLifecycleEvent.ACTIVATED);
|
||||
|
||||
logEntityAction(plugin.getId(), plugin,
|
||||
null,
|
||||
ActionType.ACTIVATED, null, strPluginId);
|
||||
|
||||
} catch (Exception e) {
|
||||
|
||||
logEntityAction(emptyId(EntityType.PLUGIN),
|
||||
null,
|
||||
null,
|
||||
ActionType.ACTIVATED, e, strPluginId);
|
||||
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
@ -102,7 +124,18 @@ public class PluginController extends BaseController {
|
||||
PluginMetaData plugin = checkPlugin(pluginService.findPluginById(pluginId));
|
||||
pluginService.suspendPluginById(pluginId);
|
||||
actorService.onPluginStateChange(plugin.getTenantId(), plugin.getId(), ComponentLifecycleEvent.SUSPENDED);
|
||||
|
||||
logEntityAction(plugin.getId(), plugin,
|
||||
null,
|
||||
ActionType.SUSPENDED, null, strPluginId);
|
||||
|
||||
} catch (Exception e) {
|
||||
|
||||
logEntityAction(emptyId(EntityType.PLUGIN),
|
||||
null,
|
||||
null,
|
||||
ActionType.SUSPENDED, e, strPluginId);
|
||||
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
@ -189,7 +222,16 @@ public class PluginController extends BaseController {
|
||||
PluginMetaData plugin = checkPlugin(pluginService.findPluginById(pluginId));
|
||||
pluginService.deletePluginById(pluginId);
|
||||
actorService.onPluginStateChange(plugin.getTenantId(), plugin.getId(), ComponentLifecycleEvent.DELETED);
|
||||
|
||||
logEntityAction(pluginId, plugin,
|
||||
null,
|
||||
ActionType.DELETED, null, strPluginId);
|
||||
|
||||
} catch (Exception e) {
|
||||
logEntityAction(emptyId(EntityType.PLUGIN),
|
||||
null,
|
||||
null,
|
||||
ActionType.DELETED, e, strPluginId);
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,6 +18,8 @@ package org.thingsboard.server.controller;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.thingsboard.server.common.data.EntityType;
|
||||
import org.thingsboard.server.common.data.audit.ActionType;
|
||||
import org.thingsboard.server.common.data.id.RuleId;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.data.page.TextPageData;
|
||||
@ -73,8 +75,17 @@ public class RuleController extends BaseController {
|
||||
RuleMetaData rule = checkNotNull(ruleService.saveRule(source));
|
||||
actorService.onRuleStateChange(rule.getTenantId(), rule.getId(),
|
||||
created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED);
|
||||
|
||||
logEntityAction(rule.getId(), rule,
|
||||
null,
|
||||
created ? ActionType.ADDED : ActionType.UPDATED, null);
|
||||
|
||||
return rule;
|
||||
} catch (Exception e) {
|
||||
|
||||
logEntityAction(emptyId(EntityType.RULE), source,
|
||||
null, source.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e);
|
||||
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
@ -89,7 +100,18 @@ public class RuleController extends BaseController {
|
||||
RuleMetaData rule = checkRule(ruleService.findRuleById(ruleId));
|
||||
ruleService.activateRuleById(ruleId);
|
||||
actorService.onRuleStateChange(rule.getTenantId(), rule.getId(), ComponentLifecycleEvent.ACTIVATED);
|
||||
|
||||
logEntityAction(rule.getId(), rule,
|
||||
null,
|
||||
ActionType.ACTIVATED, null, strRuleId);
|
||||
|
||||
} catch (Exception e) {
|
||||
|
||||
logEntityAction(emptyId(EntityType.RULE),
|
||||
null,
|
||||
null,
|
||||
ActionType.ACTIVATED, e, strRuleId);
|
||||
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
@ -104,7 +126,18 @@ public class RuleController extends BaseController {
|
||||
RuleMetaData rule = checkRule(ruleService.findRuleById(ruleId));
|
||||
ruleService.suspendRuleById(ruleId);
|
||||
actorService.onRuleStateChange(rule.getTenantId(), rule.getId(), ComponentLifecycleEvent.SUSPENDED);
|
||||
|
||||
logEntityAction(rule.getId(), rule,
|
||||
null,
|
||||
ActionType.SUSPENDED, null, strRuleId);
|
||||
|
||||
} catch (Exception e) {
|
||||
|
||||
logEntityAction(emptyId(EntityType.RULE),
|
||||
null,
|
||||
null,
|
||||
ActionType.SUSPENDED, e, strRuleId);
|
||||
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
@ -187,7 +220,18 @@ public class RuleController extends BaseController {
|
||||
RuleMetaData rule = checkRule(ruleService.findRuleById(ruleId));
|
||||
ruleService.deleteRuleById(ruleId);
|
||||
actorService.onRuleStateChange(rule.getTenantId(), rule.getId(), ComponentLifecycleEvent.DELETED);
|
||||
|
||||
logEntityAction(ruleId, rule,
|
||||
null,
|
||||
ActionType.DELETED, null, strRuleId);
|
||||
|
||||
} catch (Exception e) {
|
||||
|
||||
logEntityAction(emptyId(EntityType.RULE),
|
||||
null,
|
||||
null,
|
||||
ActionType.DELETED, e, strRuleId);
|
||||
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,7 +19,9 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.thingsboard.server.common.data.EntityType;
|
||||
import org.thingsboard.server.common.data.User;
|
||||
import org.thingsboard.server.common.data.audit.ActionType;
|
||||
import org.thingsboard.server.common.data.id.CustomerId;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.data.id.UserId;
|
||||
@ -92,8 +94,17 @@ public class UserController extends BaseController {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
logEntityAction(savedUser.getId(), savedUser,
|
||||
savedUser.getCustomerId(),
|
||||
user.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null);
|
||||
|
||||
return savedUser;
|
||||
} catch (Exception e) {
|
||||
|
||||
logEntityAction(emptyId(EntityType.USER), user,
|
||||
null, user.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e);
|
||||
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
@ -156,9 +167,18 @@ public class UserController extends BaseController {
|
||||
checkParameter(USER_ID, strUserId);
|
||||
try {
|
||||
UserId userId = new UserId(toUUID(strUserId));
|
||||
checkUserId(userId);
|
||||
User user = checkUserId(userId);
|
||||
userService.deleteUser(userId);
|
||||
|
||||
logEntityAction(userId, user,
|
||||
user.getCustomerId(),
|
||||
ActionType.DELETED, null, strUserId);
|
||||
|
||||
} catch (Exception e) {
|
||||
logEntityAction(emptyId(EntityType.USER),
|
||||
null,
|
||||
null,
|
||||
ActionType.DELETED, e, strUserId);
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,6 +30,7 @@ import org.springframework.web.context.request.async.DeferredResult;
|
||||
import org.thingsboard.server.actors.service.ActorService;
|
||||
import org.thingsboard.server.common.data.id.CustomerId;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.data.id.UserId;
|
||||
import org.thingsboard.server.common.data.plugin.PluginMetaData;
|
||||
import org.thingsboard.server.controller.BaseController;
|
||||
import org.thingsboard.server.dao.model.ModelConstants;
|
||||
@ -68,7 +69,10 @@ public class PluginApiController extends BaseController {
|
||||
if(tenantId != null && ModelConstants.NULL_UUID.equals(tenantId.getId())){
|
||||
tenantId = null;
|
||||
}
|
||||
PluginApiCallSecurityContext securityCtx = new PluginApiCallSecurityContext(pluginMd.getTenantId(), pluginMd.getId(), tenantId, customerId);
|
||||
UserId userId = getCurrentUser().getId();
|
||||
String userName = getCurrentUser().getName();
|
||||
PluginApiCallSecurityContext securityCtx = new PluginApiCallSecurityContext(pluginMd.getTenantId(), pluginMd.getId(),
|
||||
tenantId, customerId, userId, userName);
|
||||
actorService.process(new BasicPluginRestMsg(securityCtx, new RestRequest(requestEntity, request), result));
|
||||
} else {
|
||||
result.setResult(new ResponseEntity<>(HttpStatus.FORBIDDEN));
|
||||
|
||||
@ -28,6 +28,7 @@ import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.thingsboard.server.actors.service.ActorService;
|
||||
import org.thingsboard.server.common.data.id.UserId;
|
||||
import org.thingsboard.server.config.WebSocketConfiguration;
|
||||
import org.thingsboard.server.extensions.api.plugins.PluginConstants;
|
||||
import org.thingsboard.server.service.security.model.SecurityUser;
|
||||
@ -151,8 +152,10 @@ public class PluginWebSocketHandler extends TextWebSocketHandler implements Plug
|
||||
TenantId tenantId = currentUser.getTenantId();
|
||||
CustomerId customerId = currentUser.getCustomerId();
|
||||
if (PluginApiController.validatePluginAccess(pluginMd, tenantId, customerId)) {
|
||||
UserId userId = currentUser.getId();
|
||||
String userName = currentUser.getName();
|
||||
PluginApiCallSecurityContext securityCtx = new PluginApiCallSecurityContext(pluginMd.getTenantId(), pluginMd.getId(), tenantId,
|
||||
currentUser.getCustomerId());
|
||||
currentUser.getCustomerId(), userId, userName);
|
||||
return new BasicPluginWebsocketSessionRef(UUID.randomUUID().toString(), securityCtx, session.getUri(), session.getAttributes(),
|
||||
session.getLocalAddress(), session.getRemoteAddress());
|
||||
} else {
|
||||
|
||||
@ -306,6 +306,14 @@ audit_log:
|
||||
by_tenant_partitioning: "${AUDIT_LOG_BY_TENANT_PARTITIONING:MONTHS}"
|
||||
# Number of days as history period if startTime and endTime are not specified
|
||||
default_query_period: "${AUDIT_LOG_DEFAULT_QUERY_PERIOD:30}"
|
||||
exceptions:
|
||||
# Enable/disable audit log functionality for exceptions.
|
||||
enabled: "${AUDIT_LOG_EXCEPTIONS_ENABLED:true}"
|
||||
# Logging levels per each entity type.
|
||||
# Allowed values: OFF (disable), W (log write operations), RW (log read and write operations)
|
||||
logging_level:
|
||||
mask:
|
||||
"device": "W"
|
||||
"asset": "W"
|
||||
"dashboard": "W"
|
||||
"customer": "W"
|
||||
"user": "W"
|
||||
"rule": "W"
|
||||
"plugin": "W"
|
||||
|
||||
@ -15,6 +15,27 @@
|
||||
*/
|
||||
package org.thingsboard.server.common.data.audit;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public enum ActionType {
|
||||
ADDED, DELETED, UPDATED, ATTRIBUTE_UPDATED, ATTRIBUTE_DELETED, ATTRIBUTE_ADDED, RPC_CALL, CREDENTIALS_UPDATED
|
||||
}
|
||||
ADDED(false), // log entity
|
||||
DELETED(false), // log string id
|
||||
UPDATED(false), // log entity
|
||||
ATTRIBUTES_UPDATED(false), // log attributes/values
|
||||
ATTRIBUTES_DELETED(false), // log attributes
|
||||
RPC_CALL(false), // log method and params
|
||||
CREDENTIALS_UPDATED(false), // log new credentials
|
||||
ASSIGNED_TO_CUSTOMER(false), // log customer name
|
||||
UNASSIGNED_FROM_CUSTOMER(false), // log customer name
|
||||
ACTIVATED(false), // log string id
|
||||
SUSPENDED(false), // log string id
|
||||
CREDENTIALS_READ(true), // log device id
|
||||
ATTRIBUTES_READ(true); // log attributes
|
||||
|
||||
private final boolean isRead;
|
||||
|
||||
ActionType(boolean isRead) {
|
||||
this.isRead = isRead;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright © 2016-2017 The Thingsboard Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.thingsboard.server.dao.audit;
|
||||
|
||||
import org.thingsboard.server.common.data.EntityType;
|
||||
import org.thingsboard.server.common.data.audit.ActionType;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class AuditLogLevelFilter {
|
||||
|
||||
private Map<EntityType, AuditLogLevelMask> entityTypeMask = new HashMap<>();
|
||||
|
||||
public AuditLogLevelFilter(Map<String, String> mask) {
|
||||
entityTypeMask.clear();
|
||||
mask.forEach((entityTypeStr, logLevelMaskStr) -> {
|
||||
EntityType entityType = EntityType.valueOf(entityTypeStr.toUpperCase());
|
||||
AuditLogLevelMask logLevelMask = AuditLogLevelMask.valueOf(logLevelMaskStr.toUpperCase());
|
||||
entityTypeMask.put(entityType, logLevelMask);
|
||||
});
|
||||
}
|
||||
|
||||
public boolean logEnabled(EntityType entityType, ActionType actionType) {
|
||||
AuditLogLevelMask logLevelMask = entityTypeMask.get(entityType);
|
||||
if (logLevelMask != null) {
|
||||
return actionType.isRead() ? logLevelMask.isRead() : logLevelMask.isWrite();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright © 2016-2017 The Thingsboard Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.thingsboard.server.dao.audit;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public enum AuditLogLevelMask {
|
||||
|
||||
OFF(false, false),
|
||||
W(true, false),
|
||||
RW(true, true);
|
||||
|
||||
private final boolean write;
|
||||
private final boolean read;
|
||||
|
||||
AuditLogLevelMask(boolean write, boolean read) {
|
||||
this.write = write;
|
||||
this.read = read;
|
||||
}
|
||||
}
|
||||
@ -17,14 +17,13 @@ package org.thingsboard.server.dao.audit;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import org.thingsboard.server.common.data.BaseData;
|
||||
import org.thingsboard.server.common.data.HasName;
|
||||
import org.thingsboard.server.common.data.User;
|
||||
import org.thingsboard.server.common.data.audit.ActionStatus;
|
||||
import org.thingsboard.server.common.data.audit.ActionType;
|
||||
import org.thingsboard.server.common.data.audit.AuditLog;
|
||||
import org.thingsboard.server.common.data.id.CustomerId;
|
||||
import org.thingsboard.server.common.data.id.EntityId;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.data.id.UserId;
|
||||
import org.thingsboard.server.common.data.id.*;
|
||||
import org.thingsboard.server.common.data.page.TimePageData;
|
||||
import org.thingsboard.server.common.data.page.TimePageLink;
|
||||
|
||||
@ -40,13 +39,15 @@ public interface AuditLogService {
|
||||
|
||||
TimePageData<AuditLog> findAuditLogsByTenantId(TenantId tenantId, TimePageLink pageLink);
|
||||
|
||||
ListenableFuture<List<Void>> logEntityAction(User user,
|
||||
EntityId entityId,
|
||||
String entityName,
|
||||
CustomerId customerId,
|
||||
ActionType actionType,
|
||||
JsonNode actionData,
|
||||
ActionStatus actionStatus,
|
||||
String actionFailureDetails);
|
||||
<E extends BaseData<I> & HasName,
|
||||
I extends UUIDBased & EntityId> ListenableFuture<List<Void>> logEntityAction(
|
||||
TenantId tenantId,
|
||||
CustomerId customerId,
|
||||
UserId userId,
|
||||
String userName,
|
||||
I entityId,
|
||||
E entity,
|
||||
ActionType actionType,
|
||||
Exception e, Object... additionalInfo);
|
||||
|
||||
}
|
||||
|
||||
@ -17,6 +17,9 @@ package org.thingsboard.server.dao.audit;
|
||||
|
||||
import com.datastax.driver.core.utils.UUIDs;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
@ -24,16 +27,24 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.thingsboard.server.common.data.User;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.thingsboard.server.common.data.BaseData;
|
||||
import org.thingsboard.server.common.data.EntityType;
|
||||
import org.thingsboard.server.common.data.HasName;
|
||||
import org.thingsboard.server.common.data.audit.ActionStatus;
|
||||
import org.thingsboard.server.common.data.audit.ActionType;
|
||||
import org.thingsboard.server.common.data.audit.AuditLog;
|
||||
import org.thingsboard.server.common.data.id.*;
|
||||
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
|
||||
import org.thingsboard.server.common.data.page.TimePageData;
|
||||
import org.thingsboard.server.common.data.page.TimePageLink;
|
||||
import org.thingsboard.server.common.data.security.DeviceCredentials;
|
||||
import org.thingsboard.server.dao.entity.EntityService;
|
||||
import org.thingsboard.server.dao.exception.DataValidationException;
|
||||
import org.thingsboard.server.dao.service.DataValidator;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.List;
|
||||
|
||||
import static org.thingsboard.server.dao.service.Validator.validateEntityId;
|
||||
@ -44,12 +55,20 @@ import static org.thingsboard.server.dao.service.Validator.validateId;
|
||||
@ConditionalOnProperty(prefix = "audit_log", value = "enabled", havingValue = "true")
|
||||
public class AuditLogServiceImpl implements AuditLogService {
|
||||
|
||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
private static final String INCORRECT_TENANT_ID = "Incorrect tenantId ";
|
||||
private static final int INSERTS_PER_ENTRY = 3;
|
||||
|
||||
@Autowired
|
||||
private AuditLogLevelFilter auditLogLevelFilter;
|
||||
|
||||
@Autowired
|
||||
private AuditLogDao auditLogDao;
|
||||
|
||||
@Autowired
|
||||
private EntityService entityService;
|
||||
|
||||
@Override
|
||||
public TimePageData<AuditLog> findAuditLogsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TimePageLink pageLink) {
|
||||
log.trace("Executing findAuditLogsByTenantIdAndCustomerId [{}], [{}], [{}]", tenantId, customerId, pageLink);
|
||||
@ -86,25 +105,149 @@ public class AuditLogServiceImpl implements AuditLogService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListenableFuture<List<Void>> logEntityAction(User user,
|
||||
EntityId entityId,
|
||||
String entityName,
|
||||
CustomerId customerId,
|
||||
ActionType actionType,
|
||||
JsonNode actionData,
|
||||
ActionStatus actionStatus,
|
||||
String actionFailureDetails) {
|
||||
return logAction(
|
||||
user.getTenantId(),
|
||||
entityId,
|
||||
entityName,
|
||||
customerId,
|
||||
user.getId(),
|
||||
user.getName(),
|
||||
actionType,
|
||||
actionData,
|
||||
actionStatus,
|
||||
actionFailureDetails);
|
||||
public <E extends BaseData<I> & HasName, I extends UUIDBased & EntityId> ListenableFuture<List<Void>>
|
||||
logEntityAction(TenantId tenantId, CustomerId customerId, UserId userId, String userName, I entityId, E entity,
|
||||
ActionType actionType, Exception e, Object... additionalInfo) {
|
||||
if (canLog(entityId.getEntityType(), actionType)) {
|
||||
JsonNode actionData = constructActionData(entityId, entity, actionType, additionalInfo);
|
||||
ActionStatus actionStatus = ActionStatus.SUCCESS;
|
||||
String failureDetails = "";
|
||||
String entityName = "";
|
||||
if (entity != null) {
|
||||
entityName = entity.getName();
|
||||
} else {
|
||||
try {
|
||||
entityName = entityService.fetchEntityNameAsync(entityId).get();
|
||||
} catch (Exception ex) {}
|
||||
}
|
||||
if (e != null) {
|
||||
actionStatus = ActionStatus.FAILURE;
|
||||
failureDetails = getFailureStack(e);
|
||||
}
|
||||
if (actionType == ActionType.RPC_CALL) {
|
||||
String rpcErrorString = extractParameter(String.class, additionalInfo);
|
||||
if (!StringUtils.isEmpty(rpcErrorString)) {
|
||||
actionStatus = ActionStatus.FAILURE;
|
||||
failureDetails = rpcErrorString;
|
||||
}
|
||||
}
|
||||
return logAction(tenantId,
|
||||
entityId,
|
||||
entityName,
|
||||
customerId,
|
||||
userId,
|
||||
userName,
|
||||
actionType,
|
||||
actionData,
|
||||
actionStatus,
|
||||
failureDetails);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private <E extends BaseData<I> & HasName, I extends UUIDBased & EntityId> JsonNode constructActionData(I entityId,
|
||||
E entity,
|
||||
ActionType actionType,
|
||||
Object... additionalInfo) {
|
||||
ObjectNode actionData = objectMapper.createObjectNode();
|
||||
switch(actionType) {
|
||||
case ADDED:
|
||||
case UPDATED:
|
||||
ObjectNode entityNode = objectMapper.valueToTree(entity);
|
||||
if (entityId.getEntityType() == EntityType.DASHBOARD) {
|
||||
entityNode.put("configuration", "");
|
||||
}
|
||||
actionData.set("entity", entityNode);
|
||||
break;
|
||||
case DELETED:
|
||||
case ACTIVATED:
|
||||
case SUSPENDED:
|
||||
case CREDENTIALS_READ:
|
||||
String strEntityId = extractParameter(String.class, additionalInfo);
|
||||
actionData.put("entityId", strEntityId);
|
||||
break;
|
||||
case ATTRIBUTES_UPDATED:
|
||||
actionData.put("entityId", entityId.toString());
|
||||
String scope = extractParameter(String.class, 0, additionalInfo);
|
||||
List<AttributeKvEntry> attributes = extractParameter(List.class, 1, additionalInfo);
|
||||
actionData.put("scope", scope);
|
||||
ObjectNode attrsNode = objectMapper.createObjectNode();
|
||||
if (attributes != null) {
|
||||
for (AttributeKvEntry attr : attributes) {
|
||||
attrsNode.put(attr.getKey(), attr.getValueAsString());
|
||||
}
|
||||
}
|
||||
actionData.set("attributes", attrsNode);
|
||||
break;
|
||||
case ATTRIBUTES_DELETED:
|
||||
case ATTRIBUTES_READ:
|
||||
actionData.put("entityId", entityId.toString());
|
||||
scope = extractParameter(String.class, 0, additionalInfo);
|
||||
actionData.put("scope", scope);
|
||||
List<String> keys = extractParameter(List.class, 1, additionalInfo);
|
||||
ArrayNode attrsArrayNode = actionData.putArray("attributes");
|
||||
if (keys != null) {
|
||||
keys.forEach(attrsArrayNode::add);
|
||||
}
|
||||
break;
|
||||
case RPC_CALL:
|
||||
actionData.put("entityId", entityId.toString());
|
||||
Boolean oneWay = extractParameter(Boolean.class, 1, additionalInfo);
|
||||
String method = extractParameter(String.class, 2, additionalInfo);
|
||||
String params = extractParameter(String.class, 3, additionalInfo);
|
||||
actionData.put("oneWay", oneWay);
|
||||
actionData.put("method", method);
|
||||
actionData.put("params", params);
|
||||
break;
|
||||
case CREDENTIALS_UPDATED:
|
||||
actionData.put("entityId", entityId.toString());
|
||||
DeviceCredentials deviceCredentials = extractParameter(DeviceCredentials.class, additionalInfo);
|
||||
actionData.set("credentials", objectMapper.valueToTree(deviceCredentials));
|
||||
break;
|
||||
case ASSIGNED_TO_CUSTOMER:
|
||||
strEntityId = extractParameter(String.class, 0, additionalInfo);
|
||||
String strCustomerId = extractParameter(String.class, 1, additionalInfo);
|
||||
String strCustomerName = extractParameter(String.class, 2, additionalInfo);
|
||||
actionData.put("entityId", strEntityId);
|
||||
actionData.put("assignedCustomerId", strCustomerId);
|
||||
actionData.put("assignedCustomerName", strCustomerName);
|
||||
break;
|
||||
case UNASSIGNED_FROM_CUSTOMER:
|
||||
strEntityId = extractParameter(String.class, 0, additionalInfo);
|
||||
strCustomerId = extractParameter(String.class, 1, additionalInfo);
|
||||
strCustomerName = extractParameter(String.class, 2, additionalInfo);
|
||||
actionData.put("entityId", strEntityId);
|
||||
actionData.put("unassignedCustomerId", strCustomerId);
|
||||
actionData.put("unassignedCustomerName", strCustomerName);
|
||||
break;
|
||||
}
|
||||
return actionData;
|
||||
}
|
||||
|
||||
private <T> T extractParameter(Class<T> clazz, Object... additionalInfo) {
|
||||
return extractParameter(clazz, 0, additionalInfo);
|
||||
}
|
||||
|
||||
private <T> T extractParameter(Class<T> clazz, int index, Object... additionalInfo) {
|
||||
T result = null;
|
||||
if (additionalInfo != null && additionalInfo.length > index) {
|
||||
Object paramObject = additionalInfo[index];
|
||||
if (clazz.isInstance(paramObject)) {
|
||||
result = clazz.cast(paramObject);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private String getFailureStack(Exception e) {
|
||||
StringWriter sw = new StringWriter();
|
||||
e.printStackTrace(new PrintWriter(sw));
|
||||
return sw.toString();
|
||||
}
|
||||
|
||||
private boolean canLog(EntityType entityType, ActionType actionType) {
|
||||
return auditLogLevelFilter.logEnabled(entityType, actionType);
|
||||
}
|
||||
|
||||
private AuditLog createAuditLogEntry(TenantId tenantId,
|
||||
|
||||
@ -19,14 +19,13 @@ import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.thingsboard.server.common.data.BaseData;
|
||||
import org.thingsboard.server.common.data.HasName;
|
||||
import org.thingsboard.server.common.data.User;
|
||||
import org.thingsboard.server.common.data.audit.ActionStatus;
|
||||
import org.thingsboard.server.common.data.audit.ActionType;
|
||||
import org.thingsboard.server.common.data.audit.AuditLog;
|
||||
import org.thingsboard.server.common.data.id.CustomerId;
|
||||
import org.thingsboard.server.common.data.id.EntityId;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.data.id.UserId;
|
||||
import org.thingsboard.server.common.data.id.*;
|
||||
import org.thingsboard.server.common.data.page.TimePageData;
|
||||
import org.thingsboard.server.common.data.page.TimePageLink;
|
||||
|
||||
@ -57,7 +56,8 @@ public class DummyAuditLogServiceImpl implements AuditLogService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListenableFuture<List<Void>> logEntityAction(User user, EntityId entityId, String entityName, CustomerId customerId, ActionType actionType, JsonNode actionData, ActionStatus actionStatus, String actionFailureDetails) {
|
||||
return Futures.immediateFuture(Collections.emptyList());
|
||||
public <E extends BaseData<I> & HasName, I extends UUIDBased & EntityId> ListenableFuture<List<Void>> logEntityAction(TenantId tenantId, CustomerId customerId, UserId userId, String userName, I entityId, E entity, ActionType actionType, Exception e, Object... additionalInfo) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -15,46 +15,10 @@
|
||||
*/
|
||||
package org.thingsboard.server.dao.sql.audit;
|
||||
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.thingsboard.server.common.data.EntityType;
|
||||
import org.thingsboard.server.dao.model.sql.AuditLogEntity;
|
||||
|
||||
import java.util.List;
|
||||
public interface AuditLogRepository extends CrudRepository<AuditLogEntity, String>, JpaSpecificationExecutor<AuditLogEntity> {
|
||||
|
||||
public interface AuditLogRepository extends CrudRepository<AuditLogEntity, String> {
|
||||
|
||||
@Query("SELECT al FROM AuditLogEntity al WHERE al.tenantId = :tenantId " +
|
||||
"AND al.id > :idOffset ORDER BY al.id")
|
||||
List<AuditLogEntity> findByTenantId(@Param("tenantId") String tenantId,
|
||||
@Param("idOffset") String idOffset,
|
||||
Pageable pageable);
|
||||
|
||||
@Query("SELECT al FROM AuditLogEntity al WHERE al.tenantId = :tenantId " +
|
||||
"AND al.entityType = :entityType " +
|
||||
"AND al.entityId = :entityId " +
|
||||
"AND al.id > :idOffset ORDER BY al.id")
|
||||
List<AuditLogEntity> findByTenantIdAndEntityId(@Param("tenantId") String tenantId,
|
||||
@Param("entityId") String entityId,
|
||||
@Param("entityType") EntityType entityType,
|
||||
@Param("idOffset") String idOffset,
|
||||
Pageable pageable);
|
||||
|
||||
@Query("SELECT al FROM AuditLogEntity al WHERE al.tenantId = :tenantId " +
|
||||
"AND al.customerId = :customerId " +
|
||||
"AND al.id > :idOffset ORDER BY al.id")
|
||||
List<AuditLogEntity> findByTenantIdAndCustomerId(@Param("tenantId") String tenantId,
|
||||
@Param("customerId") String customerId,
|
||||
@Param("idOffset") String idOffset,
|
||||
Pageable pageable);
|
||||
|
||||
@Query("SELECT al FROM AuditLogEntity al WHERE al.tenantId = :tenantId " +
|
||||
"AND al.userId = :userId " +
|
||||
"AND al.id > :idOffset ORDER BY al.id")
|
||||
List<AuditLogEntity> findByTenantIdAndUserId(@Param("tenantId") String tenantId,
|
||||
@Param("userId") String userId,
|
||||
@Param("idOffset") String idOffset,
|
||||
Pageable pageable);
|
||||
}
|
||||
|
||||
@ -20,8 +20,12 @@ import com.google.common.util.concurrent.ListeningExecutorService;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.thingsboard.server.common.data.UUIDConverter;
|
||||
import org.thingsboard.server.common.data.audit.AuditLog;
|
||||
import org.thingsboard.server.common.data.id.CustomerId;
|
||||
import org.thingsboard.server.common.data.id.EntityId;
|
||||
@ -31,15 +35,18 @@ import org.thingsboard.server.dao.DaoUtil;
|
||||
import org.thingsboard.server.dao.audit.AuditLogDao;
|
||||
import org.thingsboard.server.dao.model.sql.AuditLogEntity;
|
||||
import org.thingsboard.server.dao.sql.JpaAbstractDao;
|
||||
import org.thingsboard.server.dao.sql.JpaAbstractSearchTimeDao;
|
||||
import org.thingsboard.server.dao.util.SqlDao;
|
||||
|
||||
import javax.annotation.PreDestroy;
|
||||
import javax.persistence.criteria.Predicate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUID;
|
||||
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID_STR;
|
||||
import static org.springframework.data.jpa.domain.Specifications.where;
|
||||
import static org.thingsboard.server.dao.model.ModelConstants.ID_PROPERTY;
|
||||
|
||||
@Component
|
||||
@SqlDao
|
||||
@ -95,41 +102,54 @@ public class JpaAuditLogDao extends JpaAbstractDao<AuditLogEntity, AuditLog> imp
|
||||
|
||||
@Override
|
||||
public List<AuditLog> findAuditLogsByTenantIdAndEntityId(UUID tenantId, EntityId entityId, TimePageLink pageLink) {
|
||||
return DaoUtil.convertDataList(
|
||||
auditLogRepository.findByTenantIdAndEntityId(
|
||||
fromTimeUUID(tenantId),
|
||||
fromTimeUUID(entityId.getId()),
|
||||
entityId.getEntityType(),
|
||||
pageLink.getIdOffset() == null ? NULL_UUID_STR : fromTimeUUID(pageLink.getIdOffset()),
|
||||
new PageRequest(0, pageLink.getLimit())));
|
||||
return findAuditLogs(tenantId, entityId, null, null, pageLink);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AuditLog> findAuditLogsByTenantIdAndCustomerId(UUID tenantId, CustomerId customerId, TimePageLink pageLink) {
|
||||
return DaoUtil.convertDataList(
|
||||
auditLogRepository.findByTenantIdAndCustomerId(
|
||||
fromTimeUUID(tenantId),
|
||||
fromTimeUUID(customerId.getId()),
|
||||
pageLink.getIdOffset() == null ? NULL_UUID_STR : fromTimeUUID(pageLink.getIdOffset()),
|
||||
new PageRequest(0, pageLink.getLimit())));
|
||||
return findAuditLogs(tenantId, null, customerId, null, pageLink);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AuditLog> findAuditLogsByTenantIdAndUserId(UUID tenantId, UserId userId, TimePageLink pageLink) {
|
||||
return DaoUtil.convertDataList(
|
||||
auditLogRepository.findByTenantIdAndUserId(
|
||||
fromTimeUUID(tenantId),
|
||||
fromTimeUUID(userId.getId()),
|
||||
pageLink.getIdOffset() == null ? NULL_UUID_STR : fromTimeUUID(pageLink.getIdOffset()),
|
||||
new PageRequest(0, pageLink.getLimit())));
|
||||
return findAuditLogs(tenantId, null, null, userId, pageLink);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AuditLog> findAuditLogsByTenantId(UUID tenantId, TimePageLink pageLink) {
|
||||
return DaoUtil.convertDataList(
|
||||
auditLogRepository.findByTenantId(
|
||||
fromTimeUUID(tenantId),
|
||||
pageLink.getIdOffset() == null ? NULL_UUID_STR : fromTimeUUID(pageLink.getIdOffset()),
|
||||
new PageRequest(0, pageLink.getLimit())));
|
||||
return findAuditLogs(tenantId, null, null, null, pageLink);
|
||||
}
|
||||
|
||||
private List<AuditLog> findAuditLogs(UUID tenantId, EntityId entityId, CustomerId customerId, UserId userId, TimePageLink pageLink) {
|
||||
Specification<AuditLogEntity> timeSearchSpec = JpaAbstractSearchTimeDao.getTimeSearchPageSpec(pageLink, "id");
|
||||
Specification<AuditLogEntity> fieldsSpec = getEntityFieldsSpec(tenantId, entityId, customerId, userId);
|
||||
Sort.Direction sortDirection = pageLink.isAscOrder() ? Sort.Direction.ASC : Sort.Direction.DESC;
|
||||
Pageable pageable = new PageRequest(0, pageLink.getLimit(), sortDirection, ID_PROPERTY);
|
||||
return DaoUtil.convertDataList(auditLogRepository.findAll(where(timeSearchSpec).and(fieldsSpec), pageable).getContent());
|
||||
}
|
||||
|
||||
private Specification<AuditLogEntity> getEntityFieldsSpec(UUID tenantId, EntityId entityId, CustomerId customerId, UserId userId) {
|
||||
return (root, criteriaQuery, criteriaBuilder) -> {
|
||||
List<Predicate> predicates = new ArrayList<>();
|
||||
if (tenantId != null) {
|
||||
Predicate tenantIdPredicate = criteriaBuilder.equal(root.get("tenantId"), UUIDConverter.fromTimeUUID(tenantId));
|
||||
predicates.add(tenantIdPredicate);
|
||||
}
|
||||
if (entityId != null) {
|
||||
Predicate entityTypePredicate = criteriaBuilder.equal(root.get("entityType"), entityId.getEntityType());
|
||||
predicates.add(entityTypePredicate);
|
||||
Predicate entityIdPredicate = criteriaBuilder.equal(root.get("entityId"), UUIDConverter.fromTimeUUID(entityId.getId()));
|
||||
predicates.add(entityIdPredicate);
|
||||
}
|
||||
if (customerId != null) {
|
||||
Predicate tenantIdPredicate = criteriaBuilder.equal(root.get("customerId"), UUIDConverter.fromTimeUUID(customerId.getId()));
|
||||
predicates.add(tenantIdPredicate);
|
||||
}
|
||||
if (userId != null) {
|
||||
Predicate tenantIdPredicate = criteriaBuilder.equal(root.get("userId"), UUIDConverter.fromTimeUUID(userId.getId()));
|
||||
predicates.add(tenantIdPredicate);
|
||||
}
|
||||
return criteriaBuilder.and(predicates.toArray(new Predicate[]{}));
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,9 +57,9 @@ CREATE TABLE IF NOT EXISTS audit_log (
|
||||
user_id varchar(31),
|
||||
user_name varchar(255),
|
||||
action_type varchar(255),
|
||||
action_data varchar(255),
|
||||
action_data varchar(1000000),
|
||||
action_status varchar(255),
|
||||
action_failure_details varchar
|
||||
action_failure_details varchar(1000000)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS attribute_kv (
|
||||
|
||||
@ -22,6 +22,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.fasterxml.jackson.databind.node.TextNode;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
@ -29,6 +30,7 @@ import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.context.support.AnnotationConfigContextLoader;
|
||||
import org.thingsboard.server.common.data.BaseData;
|
||||
import org.thingsboard.server.common.data.EntityType;
|
||||
import org.thingsboard.server.common.data.Event;
|
||||
import org.thingsboard.server.common.data.id.EntityId;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
@ -40,6 +42,8 @@ import org.thingsboard.server.common.data.plugin.PluginMetaData;
|
||||
import org.thingsboard.server.common.data.rule.RuleMetaData;
|
||||
import org.thingsboard.server.dao.alarm.AlarmService;
|
||||
import org.thingsboard.server.dao.asset.AssetService;
|
||||
import org.thingsboard.server.dao.audit.AuditLogLevelFilter;
|
||||
import org.thingsboard.server.dao.audit.AuditLogLevelMask;
|
||||
import org.thingsboard.server.dao.component.ComponentDescriptorService;
|
||||
import org.thingsboard.server.dao.customer.CustomerService;
|
||||
import org.thingsboard.server.dao.dashboard.DashboardService;
|
||||
@ -58,6 +62,8 @@ import org.thingsboard.server.dao.widget.WidgetsBundleService;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
@ -227,4 +233,14 @@ public abstract class AbstractServiceTest {
|
||||
oNode.set("configuration", readFromResource(configuration));
|
||||
return oNode;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AuditLogLevelFilter auditLogLevelFilter() {
|
||||
Map<String,String> mask = new HashMap<>();
|
||||
for (EntityType entityType : EntityType.values()) {
|
||||
mask.put(entityType.name().toLowerCase(), AuditLogLevelMask.RW.name());
|
||||
}
|
||||
return new AuditLogLevelFilter(mask);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -5,7 +5,6 @@ zk.zk_dir=/thingsboard
|
||||
updates.enabled=false
|
||||
|
||||
audit_log.enabled=true
|
||||
audit_log.exceptions.enabled=false
|
||||
audit_log.by_tenant_partitioning=MONTHS
|
||||
audit_log.default_query_period=30
|
||||
|
||||
|
||||
@ -15,10 +15,7 @@
|
||||
*/
|
||||
package org.thingsboard.server.extensions.api.plugins;
|
||||
|
||||
import org.thingsboard.server.common.data.id.CustomerId;
|
||||
import org.thingsboard.server.common.data.id.EntityId;
|
||||
import org.thingsboard.server.common.data.id.PluginId;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.data.id.*;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@ -30,13 +27,18 @@ public final class PluginApiCallSecurityContext implements Serializable {
|
||||
private final PluginId pluginId;
|
||||
private final TenantId tenantId;
|
||||
private final CustomerId customerId;
|
||||
private final UserId userId;
|
||||
private final String userName;
|
||||
|
||||
public PluginApiCallSecurityContext(TenantId pluginTenantId, PluginId pluginId, TenantId tenantId, CustomerId customerId) {
|
||||
public PluginApiCallSecurityContext(TenantId pluginTenantId, PluginId pluginId, TenantId tenantId, CustomerId customerId,
|
||||
UserId userId, String userName) {
|
||||
super();
|
||||
this.pluginTenantId = pluginTenantId;
|
||||
this.pluginId = pluginId;
|
||||
this.tenantId = tenantId;
|
||||
this.customerId = customerId;
|
||||
this.userId = userId;
|
||||
this.userName = userName;
|
||||
}
|
||||
|
||||
public TenantId getPluginTenantId(){
|
||||
@ -67,4 +69,12 @@ public final class PluginApiCallSecurityContext implements Serializable {
|
||||
return customerId;
|
||||
}
|
||||
|
||||
public UserId getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public String getUserName() {
|
||||
return userName;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -24,9 +24,7 @@ import org.thingsboard.server.common.data.kv.TsKvQuery;
|
||||
import org.thingsboard.server.common.data.relation.EntityRelation;
|
||||
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
|
||||
import org.thingsboard.server.common.msg.cluster.ServerAddress;
|
||||
import org.thingsboard.server.extensions.api.plugins.msg.PluginToRuleMsg;
|
||||
import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg;
|
||||
import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequest;
|
||||
import org.thingsboard.server.extensions.api.plugins.msg.*;
|
||||
import org.thingsboard.server.extensions.api.plugins.rpc.RpcMsg;
|
||||
import org.thingsboard.server.extensions.api.plugins.ws.PluginWebsocketSessionRef;
|
||||
import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg;
|
||||
@ -60,6 +58,7 @@ public interface PluginContext {
|
||||
|
||||
void scheduleTimeoutMsg(TimeoutMsg<?> timeoutMsg);
|
||||
|
||||
void logRpcRequest(PluginApiCallSecurityContext ctx, DeviceId deviceId, ToDeviceRpcRequestBody body, boolean oneWay, Optional<RpcError> rpcError, Exception e);
|
||||
|
||||
/*
|
||||
Websocket API
|
||||
@ -96,6 +95,12 @@ public interface PluginContext {
|
||||
Attributes API
|
||||
*/
|
||||
|
||||
void logAttributesUpdated(PluginApiCallSecurityContext ctx, EntityId entityId, String attributeType, List<AttributeKvEntry> attributes, Exception e);
|
||||
|
||||
void logAttributesDeleted(PluginApiCallSecurityContext ctx, EntityId entityId, String attributeType, List<String> keys, Exception e);
|
||||
|
||||
void logAttributesRead(PluginApiCallSecurityContext ctx, EntityId entityId, String attributeType, List<String> keys, Exception e);
|
||||
|
||||
void saveAttributes(TenantId tenantId, EntityId entityId, String attributeType, List<AttributeKvEntry> attributes, PluginCallback<Void> callback);
|
||||
|
||||
void removeAttributes(TenantId tenantId, EntityId entityId, String scope, List<String> attributeKeys, PluginCallback<Void> callback);
|
||||
|
||||
@ -18,6 +18,7 @@ package org.thingsboard.server.extensions.api.plugins.msg;
|
||||
import lombok.Data;
|
||||
import org.thingsboard.server.common.data.id.DeviceId;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.extensions.api.plugins.PluginApiCallSecurityContext;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.UUID;
|
||||
@ -28,6 +29,7 @@ import java.util.UUID;
|
||||
@Data
|
||||
public class ToDeviceRpcRequest implements Serializable {
|
||||
private final UUID id;
|
||||
private final PluginApiCallSecurityContext securityCtx;
|
||||
private final TenantId tenantId;
|
||||
private final DeviceId deviceId;
|
||||
private final boolean oneway;
|
||||
|
||||
@ -152,7 +152,7 @@ public class DeviceMessagingRuleMsgHandler implements RuleMsgHandler {
|
||||
pendingMsgs.put(uid, requestMd);
|
||||
log.trace("[{}] Forwarding {} to [{}]", uid, params, targetDeviceId);
|
||||
ToDeviceRpcRequestBody requestBody = new ToDeviceRpcRequestBody(ON_MSG_METHOD_NAME, GSON.toJson(params.get("body")));
|
||||
ctx.sendRpcRequest(new ToDeviceRpcRequest(uid, targetDevice.getTenantId(), targetDeviceId, oneWay, System.currentTimeMillis() + timeout, requestBody));
|
||||
ctx.sendRpcRequest(new ToDeviceRpcRequest(uid, null, targetDevice.getTenantId(), targetDeviceId, oneWay, System.currentTimeMillis() + timeout, requestBody));
|
||||
} else {
|
||||
replyWithError(ctx, requestMd, RpcError.FORBIDDEN);
|
||||
}
|
||||
|
||||
@ -49,7 +49,7 @@ public class RpcManager {
|
||||
LocalRequestMetaData md = localRpcRequests.remove(requestId);
|
||||
if (md != null) {
|
||||
log.trace("[{}] Processing local rpc response from device [{}]", requestId, md.getRequest().getDeviceId());
|
||||
restHandler.reply(ctx, md.getResponseWriter(), response);
|
||||
restHandler.reply(ctx, md.getRequest(), md.getResponseWriter(), response);
|
||||
} else {
|
||||
log.trace("[{}] Unknown or stale rpc response received [{}]", requestId, response);
|
||||
}
|
||||
@ -62,7 +62,7 @@ public class RpcManager {
|
||||
LocalRequestMetaData md = localRpcRequests.remove(requestId);
|
||||
if (md != null) {
|
||||
log.trace("[{}] Processing rpc timeout for local device [{}]", requestId, md.getRequest().getDeviceId());
|
||||
restHandler.reply(ctx, md.getResponseWriter(), timeoutReponse);
|
||||
restHandler.reply(ctx, md.getRequest(), md.getResponseWriter(), timeoutReponse);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -94,11 +94,12 @@ public class RpcRestMsgHandler extends DefaultRestMsgHandler {
|
||||
|
||||
private boolean handleDeviceRPCRequest(PluginContext ctx, final PluginRestMsg msg, TenantId tenantId, DeviceId deviceId, RpcRequest cmd, boolean oneWay) throws JsonProcessingException {
|
||||
long timeout = System.currentTimeMillis() + (cmd.getTimeout() != null ? cmd.getTimeout() : defaultTimeout);
|
||||
ToDeviceRpcRequestBody body = new ToDeviceRpcRequestBody(cmd.getMethodName(), cmd.getRequestData());
|
||||
ctx.checkAccess(deviceId, new PluginCallback<Void>() {
|
||||
@Override
|
||||
public void onSuccess(PluginContext ctx, Void value) {
|
||||
ToDeviceRpcRequestBody body = new ToDeviceRpcRequestBody(cmd.getMethodName(), cmd.getRequestData());
|
||||
ToDeviceRpcRequest rpcRequest = new ToDeviceRpcRequest(UUID.randomUUID(),
|
||||
msg.getSecurityCtx(),
|
||||
tenantId,
|
||||
deviceId,
|
||||
oneWay,
|
||||
@ -116,15 +117,17 @@ public class RpcRestMsgHandler extends DefaultRestMsgHandler {
|
||||
} else {
|
||||
response = new ResponseEntity(HttpStatus.UNAUTHORIZED);
|
||||
}
|
||||
ctx.logRpcRequest(msg.getSecurityCtx(), deviceId, body, oneWay, Optional.empty(), e);
|
||||
msg.getResponseHolder().setResult(response);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
public void reply(PluginContext ctx, DeferredResult<ResponseEntity> responseWriter, FromDeviceRpcResponse response) {
|
||||
public void reply(PluginContext ctx, ToDeviceRpcRequest rpcRequest, DeferredResult<ResponseEntity> responseWriter, FromDeviceRpcResponse response) {
|
||||
Optional<RpcError> rpcError = response.getError();
|
||||
if (rpcError.isPresent()) {
|
||||
ctx.logRpcRequest(rpcRequest.getSecurityCtx(), rpcRequest.getDeviceId(), rpcRequest.getBody(), rpcRequest.isOneway(), rpcError, null);
|
||||
RpcError error = rpcError.get();
|
||||
switch (error) {
|
||||
case TIMEOUT:
|
||||
@ -142,12 +145,15 @@ public class RpcRestMsgHandler extends DefaultRestMsgHandler {
|
||||
if (responseData.isPresent() && !StringUtils.isEmpty(responseData.get())) {
|
||||
String data = responseData.get();
|
||||
try {
|
||||
ctx.logRpcRequest(rpcRequest.getSecurityCtx(), rpcRequest.getDeviceId(), rpcRequest.getBody(), rpcRequest.isOneway(), rpcError, null);
|
||||
responseWriter.setResult(new ResponseEntity<>(jsonMapper.readTree(data), HttpStatus.OK));
|
||||
} catch (IOException e) {
|
||||
log.debug("Failed to decode device response: {}", data, e);
|
||||
ctx.logRpcRequest(rpcRequest.getSecurityCtx(), rpcRequest.getDeviceId(), rpcRequest.getBody(), rpcRequest.isOneway(), rpcError, e);
|
||||
responseWriter.setResult(new ResponseEntity<>(HttpStatus.NOT_ACCEPTABLE));
|
||||
}
|
||||
} else {
|
||||
ctx.logRpcRequest(rpcRequest.getSecurityCtx(), rpcRequest.getDeviceId(), rpcRequest.getBody(), rpcRequest.isOneway(), rpcError, null);
|
||||
responseWriter.setResult(new ResponseEntity<>(HttpStatus.OK));
|
||||
}
|
||||
}
|
||||
|
||||
@ -77,7 +77,7 @@ public class RpcRuleMsgHandler implements RuleMsgHandler {
|
||||
@Override
|
||||
public void onSuccess(PluginContext ctx, Void value) {
|
||||
ctx.sendRpcRequest(new ToDeviceRpcRequest(UUID.randomUUID(),
|
||||
tenantId, tmpId, true, expirationTime, body)
|
||||
null, tenantId, tmpId, true, expirationTime, body)
|
||||
);
|
||||
log.trace("[{}] Sent RPC Call Action msg", tmpId);
|
||||
}
|
||||
|
||||
@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.EntityType;
|
||||
import org.thingsboard.server.common.data.id.DeviceId;
|
||||
import org.thingsboard.server.common.data.id.EntityId;
|
||||
import org.thingsboard.server.common.data.id.EntityIdFactory;
|
||||
import org.thingsboard.server.common.data.id.UUIDBased;
|
||||
import org.thingsboard.server.common.data.kv.*;
|
||||
import org.thingsboard.server.common.msg.core.TelemetryUploadRequest;
|
||||
import org.thingsboard.server.common.transport.adaptor.JsonConverter;
|
||||
@ -150,18 +151,19 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler {
|
||||
private void handleHttpGetAttributesValues(PluginContext ctx, PluginRestMsg msg,
|
||||
RestRequest request, String scope, EntityId entityId) throws ServletException {
|
||||
String keys = request.getParameter("keys", "");
|
||||
|
||||
PluginCallback<List<AttributeKvEntry>> callback = getAttributeValuesPluginCallback(msg);
|
||||
List<String> keyList = null;
|
||||
if (!StringUtils.isEmpty(keys)) {
|
||||
keyList = Arrays.asList(keys.split(","));
|
||||
}
|
||||
PluginCallback<List<AttributeKvEntry>> callback = getAttributeValuesPluginCallback(msg, scope, entityId, keyList);
|
||||
if (!StringUtils.isEmpty(scope)) {
|
||||
if (!StringUtils.isEmpty(keys)) {
|
||||
List<String> keyList = Arrays.asList(keys.split(","));
|
||||
if (keyList != null && !keyList.isEmpty()) {
|
||||
ctx.loadAttributes(entityId, scope, keyList, callback);
|
||||
} else {
|
||||
ctx.loadAttributes(entityId, scope, callback);
|
||||
}
|
||||
} else {
|
||||
if (!StringUtils.isEmpty(keys)) {
|
||||
List<String> keyList = Arrays.asList(keys.split(","));
|
||||
if (keyList != null && !keyList.isEmpty()) {
|
||||
ctx.loadAttributes(entityId, Arrays.asList(DataConstants.allScopes()), keyList, callback);
|
||||
} else {
|
||||
ctx.loadAttributes(entityId, Arrays.asList(DataConstants.allScopes()), callback);
|
||||
@ -230,9 +232,11 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler {
|
||||
if (attributes.isEmpty()) {
|
||||
throw new IllegalArgumentException("No attributes data found in request body!");
|
||||
}
|
||||
|
||||
ctx.saveAttributes(ctx.getSecurityCtx().orElseThrow(IllegalArgumentException::new).getTenantId(), entityId, scope, attributes, new PluginCallback<Void>() {
|
||||
@Override
|
||||
public void onSuccess(PluginContext ctx, Void value) {
|
||||
ctx.logAttributesUpdated(msg.getSecurityCtx(), entityId, scope, attributes, null);
|
||||
msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.OK));
|
||||
subscriptionManager.onAttributesUpdateFromServer(ctx, entityId, scope, attributes);
|
||||
}
|
||||
@ -240,6 +244,7 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler {
|
||||
@Override
|
||||
public void onFailure(PluginContext ctx, Exception e) {
|
||||
log.error("Failed to save attributes", e);
|
||||
ctx.logAttributesUpdated(msg.getSecurityCtx(), entityId, scope, attributes, e);
|
||||
handleError(e, msg, HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
});
|
||||
@ -334,15 +339,18 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler {
|
||||
String keysParam = request.getParameter("keys");
|
||||
if (!StringUtils.isEmpty(keysParam)) {
|
||||
String[] keys = keysParam.split(",");
|
||||
ctx.removeAttributes(ctx.getSecurityCtx().orElseThrow(IllegalArgumentException::new).getTenantId(), entityId, scope, Arrays.asList(keys), new PluginCallback<Void>() {
|
||||
List<String> keyList = Arrays.asList(keys);
|
||||
ctx.removeAttributes(ctx.getSecurityCtx().orElseThrow(IllegalArgumentException::new).getTenantId(), entityId, scope, keyList, new PluginCallback<Void>() {
|
||||
@Override
|
||||
public void onSuccess(PluginContext ctx, Void value) {
|
||||
ctx.logAttributesDeleted(msg.getSecurityCtx(), entityId, scope, keyList, null);
|
||||
msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.OK));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(PluginContext ctx, Exception e) {
|
||||
log.error("Failed to remove attributes", e);
|
||||
ctx.logAttributesDeleted(msg.getSecurityCtx(), entityId, scope, keyList, e);
|
||||
handleError(e, msg, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
});
|
||||
@ -373,18 +381,21 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler {
|
||||
};
|
||||
}
|
||||
|
||||
private PluginCallback<List<AttributeKvEntry>> getAttributeValuesPluginCallback(final PluginRestMsg msg) {
|
||||
private PluginCallback<List<AttributeKvEntry>> getAttributeValuesPluginCallback(final PluginRestMsg msg, final String scope,
|
||||
final EntityId entityId, final List<String> keyList) {
|
||||
return new PluginCallback<List<AttributeKvEntry>>() {
|
||||
@Override
|
||||
public void onSuccess(PluginContext ctx, List<AttributeKvEntry> attributes) {
|
||||
List<AttributeData> values = attributes.stream().map(attribute -> new AttributeData(attribute.getLastUpdateTs(),
|
||||
attribute.getKey(), attribute.getValue())).collect(Collectors.toList());
|
||||
ctx.logAttributesRead(msg.getSecurityCtx(), entityId, scope, keyList, null);
|
||||
msg.getResponseHolder().setResult(new ResponseEntity<>(values, HttpStatus.OK));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(PluginContext ctx, Exception e) {
|
||||
log.error("Failed to fetch attributes", e);
|
||||
ctx.logAttributesRead(msg.getSecurityCtx(), entityId, scope, keyList, e);
|
||||
handleError(e, msg, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
};
|
||||
|
||||
@ -29,7 +29,7 @@
|
||||
</section>
|
||||
<div flex layout="column" class="tb-alarm-container md-whiteframe-z1">
|
||||
<md-list flex layout="column" class="tb-alarm-table">
|
||||
<md-list class="tb-row tb-header" layout="row" tb-alarm-header>
|
||||
<md-list class="tb-row tb-header" layout="row" layout-align="start center" tb-alarm-header>
|
||||
</md-list>
|
||||
<md-progress-linear style="max-height: 0px;" md-mode="indeterminate" ng-disabled="!$root.loading"
|
||||
ng-show="$root.loading"></md-progress-linear>
|
||||
@ -39,7 +39,7 @@
|
||||
class="tb-prompt" ng-show="noData()">alarm.no-alarms-prompt</span>
|
||||
<md-virtual-repeat-container ng-show="hasData()" flex md-top-index="topIndex" tb-scope-element="repeatContainer">
|
||||
<md-list-item md-virtual-repeat="alarm in theAlarms" md-on-demand flex ng-style="hasScroll() ? {'margin-right':'-15px'} : {}">
|
||||
<md-list class="tb-row" flex layout="row" tb-alarm-row alarm="{{alarm}}">
|
||||
<md-list class="tb-row" flex layout="row" layout-align="start center" tb-alarm-row alarm="{{alarm}}">
|
||||
</md-list>
|
||||
<md-divider flex></md-divider>
|
||||
</md-list-item>
|
||||
|
||||
116
ui/src/app/api/audit-log.service.js
Normal file
116
ui/src/app/api/audit-log.service.js
Normal file
@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright © 2016-2017 The Thingsboard Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export default angular.module('thingsboard.api.auditLog', [])
|
||||
.factory('auditLogService', AuditLogService)
|
||||
.name;
|
||||
|
||||
/*@ngInject*/
|
||||
function AuditLogService($http, $q) {
|
||||
|
||||
var service = {
|
||||
getAuditLogsByEntityId: getAuditLogsByEntityId,
|
||||
getAuditLogsByUserId: getAuditLogsByUserId,
|
||||
getAuditLogsByCustomerId: getAuditLogsByCustomerId,
|
||||
getAuditLogs: getAuditLogs
|
||||
}
|
||||
|
||||
return service;
|
||||
|
||||
function getAuditLogsByEntityId (entityType, entityId, pageLink) {
|
||||
var deferred = $q.defer();
|
||||
var url = `/api/audit/logs/entity/${entityType}/${entityId}?limit=${pageLink.limit}`;
|
||||
|
||||
if (angular.isDefined(pageLink.startTime) && pageLink.startTime != null) {
|
||||
url += '&startTime=' + pageLink.startTime;
|
||||
}
|
||||
if (angular.isDefined(pageLink.endTime) && pageLink.endTime != null) {
|
||||
url += '&endTime=' + pageLink.endTime;
|
||||
}
|
||||
if (angular.isDefined(pageLink.idOffset) && pageLink.idOffset != null) {
|
||||
url += '&offset=' + pageLink.idOffset;
|
||||
}
|
||||
$http.get(url, null).then(function success(response) {
|
||||
deferred.resolve(response.data);
|
||||
}, function fail() {
|
||||
deferred.reject();
|
||||
});
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function getAuditLogsByUserId (userId, pageLink) {
|
||||
var deferred = $q.defer();
|
||||
var url = `/api/audit/logs/user/${userId}?limit=${pageLink.limit}`;
|
||||
|
||||
if (angular.isDefined(pageLink.startTime) && pageLink.startTime != null) {
|
||||
url += '&startTime=' + pageLink.startTime;
|
||||
}
|
||||
if (angular.isDefined(pageLink.endTime) && pageLink.endTime != null) {
|
||||
url += '&endTime=' + pageLink.endTime;
|
||||
}
|
||||
if (angular.isDefined(pageLink.idOffset) && pageLink.idOffset != null) {
|
||||
url += '&offset=' + pageLink.idOffset;
|
||||
}
|
||||
$http.get(url, null).then(function success(response) {
|
||||
deferred.resolve(response.data);
|
||||
}, function fail() {
|
||||
deferred.reject();
|
||||
});
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function getAuditLogsByCustomerId (customerId, pageLink) {
|
||||
var deferred = $q.defer();
|
||||
var url = `/api/audit/logs/customer/${customerId}?limit=${pageLink.limit}`;
|
||||
|
||||
if (angular.isDefined(pageLink.startTime) && pageLink.startTime != null) {
|
||||
url += '&startTime=' + pageLink.startTime;
|
||||
}
|
||||
if (angular.isDefined(pageLink.endTime) && pageLink.endTime != null) {
|
||||
url += '&endTime=' + pageLink.endTime;
|
||||
}
|
||||
if (angular.isDefined(pageLink.idOffset) && pageLink.idOffset != null) {
|
||||
url += '&offset=' + pageLink.idOffset;
|
||||
}
|
||||
$http.get(url, null).then(function success(response) {
|
||||
deferred.resolve(response.data);
|
||||
}, function fail() {
|
||||
deferred.reject();
|
||||
});
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function getAuditLogs (pageLink) {
|
||||
var deferred = $q.defer();
|
||||
var url = `/api/audit/logs?limit=${pageLink.limit}`;
|
||||
|
||||
if (angular.isDefined(pageLink.startTime) && pageLink.startTime != null) {
|
||||
url += '&startTime=' + pageLink.startTime;
|
||||
}
|
||||
if (angular.isDefined(pageLink.endTime) && pageLink.endTime != null) {
|
||||
url += '&endTime=' + pageLink.endTime;
|
||||
}
|
||||
if (angular.isDefined(pageLink.idOffset) && pageLink.idOffset != null) {
|
||||
url += '&offset=' + pageLink.idOffset;
|
||||
}
|
||||
$http.get(url, null).then(function success(response) {
|
||||
deferred.resolve(response.data);
|
||||
}, function fail() {
|
||||
deferred.reject();
|
||||
});
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
}
|
||||
@ -20,7 +20,7 @@ export default angular.module('thingsboard.api.device', [thingsboardTypes])
|
||||
.name;
|
||||
|
||||
/*@ngInject*/
|
||||
function DeviceService($http, $q, attributeService, customerService, types) {
|
||||
function DeviceService($http, $q, $window, userService, attributeService, customerService, types) {
|
||||
|
||||
var service = {
|
||||
assignDeviceToCustomer: assignDeviceToCustomer,
|
||||
@ -181,14 +181,27 @@ function DeviceService($http, $q, attributeService, customerService, types) {
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function getDeviceCredentials(deviceId) {
|
||||
function getDeviceCredentials(deviceId, sync) {
|
||||
var deferred = $q.defer();
|
||||
var url = '/api/device/' + deviceId + '/credentials';
|
||||
$http.get(url, null).then(function success(response) {
|
||||
deferred.resolve(response.data);
|
||||
}, function fail() {
|
||||
deferred.reject();
|
||||
});
|
||||
if (sync) {
|
||||
var request = new $window.XMLHttpRequest();
|
||||
request.open('GET', url, false);
|
||||
request.setRequestHeader("Accept", "application/json, text/plain, */*");
|
||||
userService.setAuthorizationRequestHeader(request);
|
||||
request.send(null);
|
||||
if (request.status === 200) {
|
||||
deferred.resolve(angular.fromJson(request.responseText));
|
||||
} else {
|
||||
deferred.reject();
|
||||
}
|
||||
} else {
|
||||
$http.get(url, null).then(function success(response) {
|
||||
deferred.resolve(response.data);
|
||||
}, function fail() {
|
||||
deferred.reject();
|
||||
});
|
||||
}
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
|
||||
@ -54,6 +54,7 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi
|
||||
refreshJwtToken: refreshJwtToken,
|
||||
refreshTokenPending: refreshTokenPending,
|
||||
updateAuthorizationHeader: updateAuthorizationHeader,
|
||||
setAuthorizationRequestHeader: setAuthorizationRequestHeader,
|
||||
gotoDefaultPlace: gotoDefaultPlace,
|
||||
forceDefaultPlace: forceDefaultPlace,
|
||||
updateLastPublicDashboardId: updateLastPublicDashboardId,
|
||||
@ -367,6 +368,14 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi
|
||||
return jwtToken;
|
||||
}
|
||||
|
||||
function setAuthorizationRequestHeader(request) {
|
||||
var jwtToken = store.get('jwt_token');
|
||||
if (jwtToken) {
|
||||
request.setRequestHeader('X-Authorization', 'Bearer ' + jwtToken);
|
||||
}
|
||||
return jwtToken;
|
||||
}
|
||||
|
||||
function getTenantAdmins(tenantId, pageLink) {
|
||||
var deferred = $q.defer();
|
||||
var url = '/api/tenant/' + tenantId + '/users?limit=' + pageLink.limit;
|
||||
|
||||
@ -63,6 +63,7 @@ import thingsboardApiTime from './api/time.service';
|
||||
import thingsboardKeyboardShortcut from './components/keyboard-shortcut.filter';
|
||||
import thingsboardHelp from './help/help.directive';
|
||||
import thingsboardToast from './services/toast';
|
||||
import thingsboardClipboard from './services/clipboard.service';
|
||||
import thingsboardHome from './layout';
|
||||
import thingsboardApiLogin from './api/login.service';
|
||||
import thingsboardApiDevice from './api/device.service';
|
||||
@ -72,6 +73,7 @@ import thingsboardApiAsset from './api/asset.service';
|
||||
import thingsboardApiAttribute from './api/attribute.service';
|
||||
import thingsboardApiEntity from './api/entity.service';
|
||||
import thingsboardApiAlarm from './api/alarm.service';
|
||||
import thingsboardApiAuditLog from './api/audit-log.service';
|
||||
|
||||
import 'typeface-roboto';
|
||||
import 'font-awesome/css/font-awesome.min.css';
|
||||
@ -123,6 +125,7 @@ angular.module('thingsboard', [
|
||||
thingsboardKeyboardShortcut,
|
||||
thingsboardHelp,
|
||||
thingsboardToast,
|
||||
thingsboardClipboard,
|
||||
thingsboardHome,
|
||||
thingsboardApiLogin,
|
||||
thingsboardApiDevice,
|
||||
@ -132,6 +135,7 @@ angular.module('thingsboard', [
|
||||
thingsboardApiAttribute,
|
||||
thingsboardApiEntity,
|
||||
thingsboardApiAlarm,
|
||||
thingsboardApiAuditLog,
|
||||
uiRouter])
|
||||
.config(AppConfig)
|
||||
.factory('globalInterceptor', GlobalInterceptor)
|
||||
|
||||
@ -66,4 +66,10 @@
|
||||
entity-type="{{vm.types.entityType.asset}}">
|
||||
</tb-relation-table>
|
||||
</md-tab>
|
||||
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.grid.isTenantAdmin()" md-on-select="vm.grid.triggerResize()" label="{{ 'audit-log.audit-logs' | translate }}">
|
||||
<tb-audit-log-table flex entity-type="vm.types.entityType.asset"
|
||||
entity-id="vm.grid.operatingItem().id.id"
|
||||
audit-log-mode="{{vm.types.auditLogMode.entity}}">
|
||||
</tb-audit-log-table>
|
||||
</md-tab>
|
||||
</tb-grid>
|
||||
|
||||
103
ui/src/app/audit/audit-log-details-dialog.controller.js
Normal file
103
ui/src/app/audit/audit-log-details-dialog.controller.js
Normal file
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright © 2016-2017 The Thingsboard Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import $ from 'jquery';
|
||||
import 'brace/ext/language_tools';
|
||||
import 'brace/mode/java';
|
||||
import 'brace/theme/github';
|
||||
|
||||
/* eslint-disable angular/angularelement */
|
||||
|
||||
import './audit-log-details-dialog.scss';
|
||||
|
||||
/*@ngInject*/
|
||||
export default function AuditLogDetailsDialogController($mdDialog, types, auditLog, showingCallback) {
|
||||
|
||||
var vm = this;
|
||||
|
||||
showingCallback.onShowing = function(scope, element) {
|
||||
updateEditorSize(element, vm.actionData, 'tb-audit-log-action-data');
|
||||
vm.actionDataEditor.resize();
|
||||
if (vm.displayFailureDetails) {
|
||||
updateEditorSize(element, vm.actionFailureDetails, 'tb-audit-log-failure-details');
|
||||
vm.failureDetailsEditor.resize();
|
||||
}
|
||||
};
|
||||
|
||||
vm.types = types;
|
||||
vm.auditLog = auditLog;
|
||||
vm.displayFailureDetails = auditLog.actionStatus == types.auditLogActionStatus.FAILURE.value;
|
||||
vm.actionData = auditLog.actionDataText;
|
||||
vm.actionFailureDetails = auditLog.actionFailureDetails;
|
||||
|
||||
vm.actionDataContentOptions = {
|
||||
useWrapMode: false,
|
||||
mode: 'java',
|
||||
showGutter: false,
|
||||
showPrintMargin: false,
|
||||
theme: 'github',
|
||||
advanced: {
|
||||
enableSnippets: false,
|
||||
enableBasicAutocompletion: false,
|
||||
enableLiveAutocompletion: false
|
||||
},
|
||||
onLoad: function (_ace) {
|
||||
vm.actionDataEditor = _ace;
|
||||
}
|
||||
};
|
||||
|
||||
vm.failureDetailsContentOptions = {
|
||||
useWrapMode: false,
|
||||
mode: 'java',
|
||||
showGutter: false,
|
||||
showPrintMargin: false,
|
||||
theme: 'github',
|
||||
advanced: {
|
||||
enableSnippets: false,
|
||||
enableBasicAutocompletion: false,
|
||||
enableLiveAutocompletion: false
|
||||
},
|
||||
onLoad: function (_ace) {
|
||||
vm.failureDetailsEditor = _ace;
|
||||
}
|
||||
};
|
||||
|
||||
function updateEditorSize(element, content, editorId) {
|
||||
var newHeight = 200;
|
||||
var newWidth = 600;
|
||||
if (content && content.length > 0) {
|
||||
var lines = content.split('\n');
|
||||
newHeight = 16 * lines.length + 16;
|
||||
var maxLineLength = 0;
|
||||
for (var i in lines) {
|
||||
var line = lines[i].replace(/\t/g, ' ').replace(/\n/g, '');
|
||||
var lineLength = line.length;
|
||||
maxLineLength = Math.max(maxLineLength, lineLength);
|
||||
}
|
||||
newWidth = 8 * maxLineLength + 16;
|
||||
}
|
||||
$('#'+editorId, element).height(newHeight.toString() + "px").css('min-height', newHeight.toString() + "px")
|
||||
.width(newWidth.toString() + "px");
|
||||
}
|
||||
|
||||
vm.close = close;
|
||||
|
||||
function close () {
|
||||
$mdDialog.hide();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* eslint-enable angular/angularelement */
|
||||
23
ui/src/app/audit/audit-log-details-dialog.scss
Normal file
23
ui/src/app/audit/audit-log-details-dialog.scss
Normal file
@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Copyright © 2016-2017 The Thingsboard Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#tb-audit-log-action-data, #tb-audit-log-failure-details {
|
||||
min-width: 400px;
|
||||
min-height: 50px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 1px solid #C0C0C0;
|
||||
}
|
||||
49
ui/src/app/audit/audit-log-details-dialog.tpl.html
Normal file
49
ui/src/app/audit/audit-log-details-dialog.tpl.html
Normal file
@ -0,0 +1,49 @@
|
||||
<!--
|
||||
|
||||
Copyright © 2016-2017 The Thingsboard Authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
-->
|
||||
<md-dialog aria-label="{{ 'audit-log.audit-log-details' | translate }}">
|
||||
<md-toolbar>
|
||||
<div class="md-toolbar-tools">
|
||||
<h2 translate>audit-log.audit-log-details</h2>
|
||||
<span flex></span>
|
||||
<md-button class="md-icon-button" ng-click="vm.close()">
|
||||
<ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon>
|
||||
</md-button>
|
||||
</div>
|
||||
</md-toolbar>
|
||||
<md-dialog-content>
|
||||
<div class="md-dialog-content" layout="column">
|
||||
<label translate class="tb-title no-padding">audit-log.action-data</label>
|
||||
<div flex id="tb-audit-log-action-data" readonly
|
||||
ui-ace="vm.actionDataContentOptions"
|
||||
ng-model="vm.actionData">
|
||||
</div>
|
||||
<span style="height: 30px;"></span>
|
||||
<label ng-show="vm.displayFailureDetails" translate class="tb-title no-padding">audit-log.failure-details</label>
|
||||
<div ng-show="vm.displayFailureDetails" flex id="tb-audit-log-failure-details" readonly
|
||||
ui-ace="vm.failureDetailsContentOptions"
|
||||
ng-model="vm.actionFailureDetails">
|
||||
</div>
|
||||
</div>
|
||||
</md-dialog-content>
|
||||
<md-dialog-actions layout="row">
|
||||
<span flex></span>
|
||||
<md-button ng-disabled="$root.loading" ng-click="vm.close()" style="margin-right:20px;">{{ 'action.close' |
|
||||
translate }}
|
||||
</md-button>
|
||||
</md-dialog-actions>
|
||||
</md-dialog>
|
||||
41
ui/src/app/audit/audit-log-header.directive.js
Normal file
41
ui/src/app/audit/audit-log-header.directive.js
Normal file
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright © 2016-2017 The Thingsboard Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/* eslint-disable import/no-unresolved, import/default */
|
||||
|
||||
import auditLogHeaderTemplate from './audit-log-header.tpl.html';
|
||||
|
||||
/* eslint-enable import/no-unresolved, import/default */
|
||||
|
||||
/*@ngInject*/
|
||||
export default function AuditLogHeaderDirective($compile, $templateCache, types) {
|
||||
|
||||
var linker = function (scope, element, attrs) {
|
||||
|
||||
var template = $templateCache.get(auditLogHeaderTemplate);
|
||||
element.html(template);
|
||||
scope.auditLogMode = attrs.auditLogMode;
|
||||
scope.types = types;
|
||||
$compile(element.contents())(scope);
|
||||
|
||||
};
|
||||
|
||||
return {
|
||||
restrict: "A",
|
||||
replace: false,
|
||||
link: linker,
|
||||
scope: false
|
||||
};
|
||||
}
|
||||
24
ui/src/app/audit/audit-log-header.tpl.html
Normal file
24
ui/src/app/audit/audit-log-header.tpl.html
Normal file
@ -0,0 +1,24 @@
|
||||
<!--
|
||||
|
||||
Copyright © 2016-2017 The Thingsboard Authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
-->
|
||||
<div translate class="tb-cell" flex="30">audit-log.timestamp</div>
|
||||
<div ng-if="auditLogMode != types.auditLogMode.entity" translate class="tb-cell" flex="10">audit-log.entity-type</div>
|
||||
<div ng-if="auditLogMode != types.auditLogMode.entity" translate class="tb-cell" flex="30">audit-log.entity-name</div>
|
||||
<div ng-if="auditLogMode != types.auditLogMode.user" translate class="tb-cell" flex="30">audit-log.user</div>
|
||||
<div translate class="tb-cell" flex="15">audit-log.type</div>
|
||||
<div translate class="tb-cell" flex="15">audit-log.status</div>
|
||||
<div translate class="tb-cell" flex="10">audit-log.details</div>
|
||||
67
ui/src/app/audit/audit-log-row.directive.js
Normal file
67
ui/src/app/audit/audit-log-row.directive.js
Normal file
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright © 2016-2017 The Thingsboard Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/* eslint-disable import/no-unresolved, import/default */
|
||||
|
||||
import auditLogDetailsDialogTemplate from './audit-log-details-dialog.tpl.html';
|
||||
|
||||
import auditLogRowTemplate from './audit-log-row.tpl.html';
|
||||
|
||||
/* eslint-enable import/no-unresolved, import/default */
|
||||
|
||||
/*@ngInject*/
|
||||
export default function AuditLogRowDirective($compile, $templateCache, types, $mdDialog, $document) {
|
||||
|
||||
var linker = function (scope, element, attrs) {
|
||||
|
||||
var template = $templateCache.get(auditLogRowTemplate);
|
||||
element.html(template);
|
||||
|
||||
scope.auditLog = attrs.auditLog;
|
||||
scope.auditLogMode = attrs.auditLogMode;
|
||||
scope.types = types;
|
||||
|
||||
scope.showAuditLogDetails = function($event) {
|
||||
var onShowingCallback = {
|
||||
onShowing: function(){}
|
||||
}
|
||||
$mdDialog.show({
|
||||
controller: 'AuditLogDetailsDialogController',
|
||||
controllerAs: 'vm',
|
||||
templateUrl: auditLogDetailsDialogTemplate,
|
||||
locals: {
|
||||
auditLog: scope.auditLog,
|
||||
showingCallback: onShowingCallback
|
||||
},
|
||||
parent: angular.element($document[0].body),
|
||||
targetEvent: $event,
|
||||
fullscreen: true,
|
||||
skipHide: true,
|
||||
onShowing: function(scope, element) {
|
||||
onShowingCallback.onShowing(scope, element);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$compile(element.contents())(scope);
|
||||
}
|
||||
|
||||
return {
|
||||
restrict: "A",
|
||||
replace: false,
|
||||
link: linker,
|
||||
scope: false
|
||||
};
|
||||
}
|
||||
36
ui/src/app/audit/audit-log-row.tpl.html
Normal file
36
ui/src/app/audit/audit-log-row.tpl.html
Normal file
@ -0,0 +1,36 @@
|
||||
<!--
|
||||
|
||||
Copyright © 2016-2017 The Thingsboard Authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
-->
|
||||
<div class="tb-cell" flex="30">{{ auditLog.createdTime | date : 'yyyy-MM-dd HH:mm:ss' }}</div>
|
||||
<div ng-if="auditLogMode != types.auditLogMode.entity" class="tb-cell" flex="10">{{ auditLog.entityTypeText }}</div>
|
||||
<div ng-if="auditLogMode != types.auditLogMode.entity" class="tb-cell" flex="30">{{ auditLog.entityName }}</div>
|
||||
<div ng-if="auditLogMode != types.auditLogMode.user" class="tb-cell" flex="30">{{ auditLog.userName }}</div>
|
||||
<div class="tb-cell" flex="15">{{ auditLog.actionTypeText }}</div>
|
||||
<div class="tb-cell" flex="15">{{ auditLog.actionStatusText }}</div>
|
||||
<div class="tb-cell" flex="10">
|
||||
<md-button class="md-icon-button md-primary"
|
||||
ng-click="showAuditLogDetails($event)"
|
||||
aria-label="{{ 'action.view' | translate }}">
|
||||
<md-tooltip md-direction="top">
|
||||
{{ 'audit-log.details' | translate }}
|
||||
</md-tooltip>
|
||||
<md-icon aria-label="{{ 'action.view' | translate }}"
|
||||
class="material-icons">
|
||||
more_horiz
|
||||
</md-icon>
|
||||
</md-button>
|
||||
</div>
|
||||
262
ui/src/app/audit/audit-log-table.directive.js
Normal file
262
ui/src/app/audit/audit-log-table.directive.js
Normal file
@ -0,0 +1,262 @@
|
||||
/*
|
||||
* Copyright © 2016-2017 The Thingsboard Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import './audit-log.scss';
|
||||
|
||||
/* eslint-disable import/no-unresolved, import/default */
|
||||
|
||||
import auditLogTableTemplate from './audit-log-table.tpl.html';
|
||||
|
||||
/* eslint-enable import/no-unresolved, import/default */
|
||||
|
||||
/*@ngInject*/
|
||||
export default function AuditLogTableDirective($compile, $templateCache, $rootScope, $filter, $translate, types, auditLogService) {
|
||||
|
||||
var linker = function (scope, element) {
|
||||
|
||||
var template = $templateCache.get(auditLogTableTemplate);
|
||||
|
||||
element.html(template);
|
||||
|
||||
scope.types = types;
|
||||
|
||||
var pageSize = 20;
|
||||
var startTime = 0;
|
||||
var endTime = 0;
|
||||
|
||||
scope.timewindow = {
|
||||
history: {
|
||||
timewindowMs: 24 * 60 * 60 * 1000 // 1 day
|
||||
}
|
||||
}
|
||||
|
||||
scope.topIndex = 0;
|
||||
scope.searchText = '';
|
||||
|
||||
scope.theAuditLogs = {
|
||||
getItemAtIndex: function (index) {
|
||||
if (index > scope.auditLogs.filtered.length) {
|
||||
scope.theAuditLogs.fetchMoreItems_(index);
|
||||
return null;
|
||||
}
|
||||
return scope.auditLogs.filtered[index];
|
||||
},
|
||||
|
||||
getLength: function () {
|
||||
if (scope.auditLogs.hasNext) {
|
||||
return scope.auditLogs.filtered.length + scope.auditLogs.nextPageLink.limit;
|
||||
} else {
|
||||
return scope.auditLogs.filtered.length;
|
||||
}
|
||||
},
|
||||
|
||||
fetchMoreItems_: function () {
|
||||
if (scope.auditLogs.hasNext && !scope.auditLogs.pending) {
|
||||
var promise = getAuditLogsPromise(scope.auditLogs.nextPageLink);
|
||||
if (promise) {
|
||||
scope.auditLogs.pending = true;
|
||||
promise.then(
|
||||
function success(auditLogs) {
|
||||
scope.auditLogs.data = scope.auditLogs.data.concat(prepareAuditLogsData(auditLogs.data));
|
||||
scope.auditLogs.filtered = $filter('filter')(scope.auditLogs.data, {$: scope.searchText});
|
||||
scope.auditLogs.nextPageLink = auditLogs.nextPageLink;
|
||||
scope.auditLogs.hasNext = auditLogs.hasNext;
|
||||
if (scope.auditLogs.hasNext) {
|
||||
scope.auditLogs.nextPageLink.limit = pageSize;
|
||||
}
|
||||
scope.auditLogs.pending = false;
|
||||
},
|
||||
function fail() {
|
||||
scope.auditLogs.hasNext = false;
|
||||
scope.auditLogs.pending = false;
|
||||
});
|
||||
} else {
|
||||
scope.auditLogs.hasNext = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function prepareAuditLogsData(data) {
|
||||
data.forEach(
|
||||
auditLog => {
|
||||
auditLog.entityTypeText = $translate.instant(types.entityTypeTranslations[auditLog.entityId.entityType].type);
|
||||
auditLog.actionTypeText = $translate.instant(types.auditLogActionType[auditLog.actionType].name);
|
||||
auditLog.actionStatusText = $translate.instant(types.auditLogActionStatus[auditLog.actionStatus].name);
|
||||
auditLog.actionDataText = auditLog.actionData ? angular.toJson(auditLog.actionData, true) : '';
|
||||
}
|
||||
);
|
||||
return data;
|
||||
}
|
||||
|
||||
scope.$watch("entityId", function(newVal, prevVal) {
|
||||
if (newVal && !angular.equals(newVal, prevVal)) {
|
||||
resetFilter();
|
||||
scope.reload();
|
||||
}
|
||||
});
|
||||
|
||||
scope.$watch("userId", function(newVal, prevVal) {
|
||||
if (newVal && !angular.equals(newVal, prevVal)) {
|
||||
resetFilter();
|
||||
scope.reload();
|
||||
}
|
||||
});
|
||||
|
||||
scope.$watch("customerId", function(newVal, prevVal) {
|
||||
if (newVal && !angular.equals(newVal, prevVal)) {
|
||||
resetFilter();
|
||||
scope.reload();
|
||||
}
|
||||
});
|
||||
|
||||
function getAuditLogsPromise(pageLink) {
|
||||
switch(scope.auditLogMode) {
|
||||
case types.auditLogMode.tenant:
|
||||
return auditLogService.getAuditLogs(pageLink);
|
||||
case types.auditLogMode.entity:
|
||||
if (scope.entityType && scope.entityId) {
|
||||
return auditLogService.getAuditLogsByEntityId(scope.entityType, scope.entityId,
|
||||
pageLink);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
case types.auditLogMode.user:
|
||||
if (scope.userId) {
|
||||
return auditLogService.getAuditLogsByUserId(scope.userId, pageLink);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
case types.auditLogMode.customer:
|
||||
if (scope.customerId) {
|
||||
return auditLogService.getAuditLogsByCustomerId(scope.customerId, pageLink);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function destroyWatchers() {
|
||||
if (scope.timewindowWatchHandle) {
|
||||
scope.timewindowWatchHandle();
|
||||
scope.timewindowWatchHandle = null;
|
||||
}
|
||||
if (scope.searchTextWatchHandle) {
|
||||
scope.searchTextWatchHandle();
|
||||
scope.searchTextWatchHandle = null;
|
||||
}
|
||||
}
|
||||
|
||||
function initWatchers() {
|
||||
scope.timewindowWatchHandle = scope.$watch("timewindow", function(newVal, prevVal) {
|
||||
if (newVal && !angular.equals(newVal, prevVal)) {
|
||||
scope.reload();
|
||||
}
|
||||
}, true);
|
||||
|
||||
scope.searchTextWatchHandle = scope.$watch("searchText", function(newVal, prevVal) {
|
||||
if (!angular.equals(newVal, prevVal)) {
|
||||
scope.searchTextUpdated();
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
|
||||
function resetFilter() {
|
||||
destroyWatchers();
|
||||
scope.timewindow = {
|
||||
history: {
|
||||
timewindowMs: 24 * 60 * 60 * 1000 // 1 day
|
||||
}
|
||||
};
|
||||
scope.searchText = '';
|
||||
initWatchers();
|
||||
}
|
||||
|
||||
function updateTimeWindowRange () {
|
||||
if (scope.timewindow.history.timewindowMs) {
|
||||
var currentTime = (new Date).getTime();
|
||||
startTime = currentTime - scope.timewindow.history.timewindowMs;
|
||||
endTime = currentTime;
|
||||
} else {
|
||||
startTime = scope.timewindow.history.fixedTimewindow.startTimeMs;
|
||||
endTime = scope.timewindow.history.fixedTimewindow.endTimeMs;
|
||||
}
|
||||
}
|
||||
|
||||
scope.reload = function() {
|
||||
scope.topIndex = 0;
|
||||
updateTimeWindowRange();
|
||||
scope.auditLogs = {
|
||||
data: [],
|
||||
filtered: [],
|
||||
nextPageLink: {
|
||||
limit: pageSize,
|
||||
startTime: startTime,
|
||||
endTime: endTime
|
||||
},
|
||||
hasNext: true,
|
||||
pending: false
|
||||
};
|
||||
scope.theAuditLogs.getItemAtIndex(pageSize);
|
||||
}
|
||||
|
||||
scope.searchTextUpdated = function() {
|
||||
scope.auditLogs.filtered = $filter('filter')(scope.auditLogs.data, {$: scope.searchText});
|
||||
scope.theAuditLogs.getItemAtIndex(pageSize);
|
||||
}
|
||||
|
||||
scope.noData = function() {
|
||||
return scope.auditLogs.data.length == 0 && !scope.auditLogs.hasNext;
|
||||
}
|
||||
|
||||
scope.hasData = function() {
|
||||
return scope.auditLogs.data.length > 0;
|
||||
}
|
||||
|
||||
scope.loading = function() {
|
||||
return $rootScope.loading;
|
||||
}
|
||||
|
||||
scope.hasScroll = function() {
|
||||
var repeatContainer = scope.repeatContainer[0];
|
||||
if (repeatContainer) {
|
||||
var scrollElement = repeatContainer.children[0];
|
||||
if (scrollElement) {
|
||||
return scrollElement.scrollHeight > scrollElement.clientHeight;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
scope.reload();
|
||||
|
||||
initWatchers();
|
||||
|
||||
$compile(element.contents())(scope);
|
||||
}
|
||||
|
||||
return {
|
||||
restrict: "E",
|
||||
link: linker,
|
||||
scope: {
|
||||
entityType: '=?',
|
||||
entityId: '=?',
|
||||
userId: '=?',
|
||||
customerId: '=?',
|
||||
auditLogMode: '@',
|
||||
pageMode: '@?'
|
||||
}
|
||||
};
|
||||
}
|
||||
68
ui/src/app/audit/audit-log-table.tpl.html
Normal file
68
ui/src/app/audit/audit-log-table.tpl.html
Normal file
@ -0,0 +1,68 @@
|
||||
<!--
|
||||
|
||||
Copyright © 2016-2017 The Thingsboard Authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
-->
|
||||
<md-content flex class="md-padding tb-absolute-fill" layout="column">
|
||||
<div flex layout="column" class="tb-audit-logs" ng-class="{'md-whiteframe-z1': pageMode}">
|
||||
<div layout="column" layout-gt-sm="row" layout-align-gt-sm="start center" class="tb-audit-log-toolbar" ng-class="{'md-padding': pageMode, 'tb-audit-log-margin-18px': !pageMode}">
|
||||
<tb-timewindow ng-model="timewindow" history-only as-button="true"></tb-timewindow>
|
||||
<div flex layout="row" layout-align="start center">
|
||||
<md-button class="md-icon-button" aria-label="{{ 'action.search' | translate }}">
|
||||
<md-icon aria-label="{{ 'action.search' | translate }}" class="material-icons">search</md-icon>
|
||||
<md-tooltip md-direction="top">
|
||||
{{'audit-log.search' | translate}}
|
||||
</md-tooltip>
|
||||
</md-button>
|
||||
<md-input-container flex class="tb-audit-log-search-input">
|
||||
<label> </label>
|
||||
<input ng-model="searchText" placeholder="{{'audit-log.search' | translate}}"/>
|
||||
</md-input-container>
|
||||
<md-button ng-disabled="$root.loading" class="md-icon-button" aria-label="Close" ng-click="searchText = ''">
|
||||
<md-icon aria-label="Close" class="material-icons">close</md-icon>
|
||||
<md-tooltip md-direction="top">
|
||||
{{ 'audit-log.clear-search' | translate }}
|
||||
</md-tooltip>
|
||||
</md-button>
|
||||
<md-button ng-disabled="$root.loading"
|
||||
class="md-icon-button" ng-click="reload()">
|
||||
<md-icon>refresh</md-icon>
|
||||
<md-tooltip md-direction="top">
|
||||
{{ 'action.refresh' | translate }}
|
||||
</md-tooltip>
|
||||
</md-button>
|
||||
</div>
|
||||
</div>
|
||||
<div flex layout="column" class="tb-audit-log-container" ng-class="{'md-whiteframe-z1': !pageMode}">
|
||||
<md-list flex layout="column" class="tb-audit-log-table" ng-class="{'tb-audit-log-table-full': pageMode}">
|
||||
<md-list class="tb-row tb-header" layout="row" layout-align="start center" tb-audit-log-header audit-log-mode="{{auditLogMode}}">
|
||||
</md-list>
|
||||
<md-progress-linear style="max-height: 0px;" md-mode="indeterminate" ng-disabled="!$root.loading"
|
||||
ng-show="$root.loading"></md-progress-linear>
|
||||
<md-divider></md-divider>
|
||||
<span translate layout-align="center center"
|
||||
style="margin-top: 25px;"
|
||||
class="tb-prompt" ng-show="noData()">audit-log.no-audit-logs-prompt</span>
|
||||
<md-virtual-repeat-container ng-show="hasData()" flex md-top-index="topIndex" tb-scope-element="repeatContainer">
|
||||
<md-list-item md-virtual-repeat="auditLog in theAuditLogs" md-on-demand flex ng-style="hasScroll() ? {'margin-right':'-15px'} : {}">
|
||||
<md-list class="tb-row" flex layout="row" layout-align="start center" tb-audit-log-row audit-log-mode="{{auditLogMode}}" audit-log="{{auditLog}}">
|
||||
</md-list>
|
||||
<md-divider flex></md-divider>
|
||||
</md-list-item>
|
||||
</md-virtual-repeat-container>
|
||||
</md-list>
|
||||
</div>
|
||||
</div>
|
||||
</md-content>
|
||||
44
ui/src/app/audit/audit-log.routes.js
Normal file
44
ui/src/app/audit/audit-log.routes.js
Normal file
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright © 2016-2017 The Thingsboard Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/* eslint-disable import/no-unresolved, import/default */
|
||||
|
||||
import auditLogsTemplate from './audit-logs.tpl.html';
|
||||
|
||||
/* eslint-enable import/no-unresolved, import/default */
|
||||
|
||||
/*@ngInject*/
|
||||
export default function AuditLogRoutes($stateProvider) {
|
||||
$stateProvider
|
||||
.state('home.auditLogs', {
|
||||
url: '/auditLogs',
|
||||
module: 'private',
|
||||
auth: ['TENANT_ADMIN'],
|
||||
views: {
|
||||
"content@home": {
|
||||
templateUrl: auditLogsTemplate,
|
||||
controller: 'AuditLogsController',
|
||||
controllerAs: 'vm'
|
||||
}
|
||||
},
|
||||
data: {
|
||||
searchEnabled: false,
|
||||
pageTitle: 'audit-log.audit-logs'
|
||||
},
|
||||
ncyBreadcrumb: {
|
||||
label: '{"icon": "track_changes", "label": "audit-log.audit-logs"}'
|
||||
}
|
||||
});
|
||||
}
|
||||
91
ui/src/app/audit/audit-log.scss
Normal file
91
ui/src/app/audit/audit-log.scss
Normal file
@ -0,0 +1,91 @@
|
||||
/**
|
||||
* Copyright © 2016-2017 The Thingsboard Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
.tb-audit-logs {
|
||||
background-color: #fff;
|
||||
.tb-audit-log-margin-18px {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
.tb-audit-log-toolbar {
|
||||
font-size: 20px;
|
||||
}
|
||||
md-input-container.tb-audit-log-search-input {
|
||||
.md-errors-spacer {
|
||||
min-height: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tb-audit-log-container {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
|
||||
|
||||
md-list.tb-audit-log-table {
|
||||
padding: 0px;
|
||||
min-width: 700px;
|
||||
&.tb-audit-log-table-full {
|
||||
min-width: 900px;
|
||||
}
|
||||
|
||||
md-list-item {
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.tb-row {
|
||||
height: 48px;
|
||||
padding: 0px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tb-row:hover {
|
||||
background-color: #EEEEEE;
|
||||
}
|
||||
|
||||
.tb-header:hover {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.tb-header {
|
||||
.tb-cell {
|
||||
color: rgba(0,0,0,.54);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
white-space: nowrap;
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
|
||||
.tb-cell {
|
||||
padding: 0 24px;
|
||||
margin: auto 0;
|
||||
color: rgba(0,0,0,.87);
|
||||
font-size: 13px;
|
||||
vertical-align: middle;
|
||||
text-align: left;
|
||||
overflow: hidden;
|
||||
.md-button {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.tb-cell.tb-number {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
}
|
||||
24
ui/src/app/audit/audit-logs.controller.js
Normal file
24
ui/src/app/audit/audit-logs.controller.js
Normal file
@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright © 2016-2017 The Thingsboard Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*@ngInject*/
|
||||
export default function AuditLogsController(types) {
|
||||
|
||||
var vm = this;
|
||||
|
||||
vm.types = types;
|
||||
|
||||
}
|
||||
23
ui/src/app/audit/audit-logs.tpl.html
Normal file
23
ui/src/app/audit/audit-logs.tpl.html
Normal file
@ -0,0 +1,23 @@
|
||||
<!--
|
||||
|
||||
Copyright © 2016-2017 The Thingsboard Authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
-->
|
||||
|
||||
<tb-audit-log-table class="md-whiteframe-z1"
|
||||
flex
|
||||
audit-log-mode="{{vm.types.auditLogMode.tenant}}"
|
||||
page-mode="true">
|
||||
</tb-audit-log-table>
|
||||
31
ui/src/app/audit/index.js
Normal file
31
ui/src/app/audit/index.js
Normal file
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright © 2016-2017 The Thingsboard Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import AuditLogRoutes from './audit-log.routes';
|
||||
import AuditLogsController from './audit-logs.controller';
|
||||
import AuditLogDetailsDialogController from './audit-log-details-dialog.controller';
|
||||
import AuditLogHeaderDirective from './audit-log-header.directive';
|
||||
import AuditLogRowDirective from './audit-log-row.directive';
|
||||
import AuditLogTableDirective from './audit-log-table.directive';
|
||||
|
||||
export default angular.module('thingsboard.auditLog', [])
|
||||
.config(AuditLogRoutes)
|
||||
.controller('AuditLogsController', AuditLogsController)
|
||||
.controller('AuditLogDetailsDialogController', AuditLogDetailsDialogController)
|
||||
.directive('tbAuditLogHeader', AuditLogHeaderDirective)
|
||||
.directive('tbAuditLogRow', AuditLogRowDirective)
|
||||
.directive('tbAuditLogTable', AuditLogTableDirective)
|
||||
.name;
|
||||
@ -156,6 +156,63 @@ export default angular.module('thingsboard.types', [])
|
||||
color: "green"
|
||||
}
|
||||
},
|
||||
auditLogActionType: {
|
||||
"ADDED": {
|
||||
name: "audit-log.type-added"
|
||||
},
|
||||
"DELETED": {
|
||||
name: "audit-log.type-deleted"
|
||||
},
|
||||
"UPDATED": {
|
||||
name: "audit-log.type-updated"
|
||||
},
|
||||
"ATTRIBUTES_UPDATED": {
|
||||
name: "audit-log.type-attributes-updated"
|
||||
},
|
||||
"ATTRIBUTES_DELETED": {
|
||||
name: "audit-log.type-attributes-deleted"
|
||||
},
|
||||
"RPC_CALL": {
|
||||
name: "audit-log.type-rpc-call"
|
||||
},
|
||||
"CREDENTIALS_UPDATED": {
|
||||
name: "audit-log.type-credentials-updated"
|
||||
},
|
||||
"ASSIGNED_TO_CUSTOMER": {
|
||||
name: "audit-log.type-assigned-to-customer"
|
||||
},
|
||||
"UNASSIGNED_FROM_CUSTOMER": {
|
||||
name: "audit-log.type-unassigned-from-customer"
|
||||
},
|
||||
"ACTIVATED": {
|
||||
name: "audit-log.type-activated"
|
||||
},
|
||||
"SUSPENDED": {
|
||||
name: "audit-log.type-suspended"
|
||||
},
|
||||
"CREDENTIALS_READ": {
|
||||
name: "audit-log.type-credentials-read"
|
||||
},
|
||||
"ATTRIBUTES_READ": {
|
||||
name: "audit-log.type-attributes-read"
|
||||
}
|
||||
},
|
||||
auditLogActionStatus: {
|
||||
"SUCCESS": {
|
||||
value: "SUCCESS",
|
||||
name: "audit-log.status-success"
|
||||
},
|
||||
"FAILURE": {
|
||||
value: "FAILURE",
|
||||
name: "audit-log.status-failure"
|
||||
}
|
||||
},
|
||||
auditLogMode: {
|
||||
tenant: "tenant",
|
||||
entity: "entity",
|
||||
user: "user",
|
||||
customer: "customer"
|
||||
},
|
||||
aliasFilterType: {
|
||||
singleEntity: {
|
||||
value: 'singleEntity',
|
||||
|
||||
@ -125,7 +125,7 @@ function Grid() {
|
||||
}
|
||||
|
||||
/*@ngInject*/
|
||||
function GridController($scope, $state, $mdDialog, $document, $q, $mdUtil, $timeout, $translate, $mdMedia, $templateCache, $window) {
|
||||
function GridController($scope, $state, $mdDialog, $document, $q, $mdUtil, $timeout, $translate, $mdMedia, $templateCache, $window, userService) {
|
||||
|
||||
var vm = this;
|
||||
|
||||
@ -157,6 +157,7 @@ function GridController($scope, $state, $mdDialog, $document, $q, $mdUtil, $time
|
||||
vm.saveItem = saveItem;
|
||||
vm.toggleItemSelection = toggleItemSelection;
|
||||
vm.triggerResize = triggerResize;
|
||||
vm.isTenantAdmin = isTenantAdmin;
|
||||
|
||||
$scope.$watch(function () {
|
||||
return $mdMedia('xs') || $mdMedia('sm');
|
||||
@ -634,6 +635,10 @@ function GridController($scope, $state, $mdDialog, $document, $q, $mdUtil, $time
|
||||
w.triggerHandler('resize');
|
||||
}
|
||||
|
||||
function isTenantAdmin() {
|
||||
return userService.getAuthority() == 'TENANT_ADMIN';
|
||||
}
|
||||
|
||||
function moveToTop() {
|
||||
moveToIndex(0, true);
|
||||
}
|
||||
|
||||
@ -66,5 +66,10 @@
|
||||
entity-type="{{vm.types.entityType.customer}}">
|
||||
</tb-relation-table>
|
||||
</md-tab>
|
||||
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.grid.isTenantAdmin()" md-on-select="vm.grid.triggerResize()" label="{{ 'audit-log.audit-logs' | translate }}">
|
||||
<tb-audit-log-table flex customer-id="vm.grid.operatingItem().id.id"
|
||||
audit-log-mode="{{vm.types.auditLogMode.customer}}">
|
||||
</tb-audit-log-table>
|
||||
</md-tab>
|
||||
</md-tabs>
|
||||
</tb-grid>
|
||||
|
||||
@ -19,13 +19,24 @@
|
||||
<details-buttons tb-help="'dashboards'" help-container-id="help-container">
|
||||
<div id="help-container"></div>
|
||||
</details-buttons>
|
||||
<tb-dashboard-details dashboard="vm.grid.operatingItem()"
|
||||
is-edit="vm.grid.detailsConfig.isDetailsEditMode"
|
||||
dashboard-scope="vm.dashboardsScope"
|
||||
the-form="vm.grid.detailsForm"
|
||||
on-assign-to-customer="vm.assignToCustomer(event, [ vm.grid.detailsConfig.currentItem.id.id ])"
|
||||
on-make-public="vm.makePublic(event, vm.grid.detailsConfig.currentItem)"
|
||||
on-unassign-from-customer="vm.unassignFromCustomer(event, vm.grid.detailsConfig.currentItem, isPublic)"
|
||||
on-export-dashboard="vm.exportDashboard(event, vm.grid.detailsConfig.currentItem)"
|
||||
on-delete-dashboard="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-dashboard-details>
|
||||
<md-tabs ng-class="{'tb-headless': vm.grid.detailsConfig.isDetailsEditMode}"
|
||||
id="tabs" md-border-bottom flex class="tb-absolute-fill">
|
||||
<md-tab label="{{ 'dashboard.details' | translate }}">
|
||||
<tb-dashboard-details dashboard="vm.grid.operatingItem()"
|
||||
is-edit="vm.grid.detailsConfig.isDetailsEditMode"
|
||||
dashboard-scope="vm.dashboardsScope"
|
||||
the-form="vm.grid.detailsForm"
|
||||
on-assign-to-customer="vm.assignToCustomer(event, [ vm.grid.detailsConfig.currentItem.id.id ])"
|
||||
on-make-public="vm.makePublic(event, vm.grid.detailsConfig.currentItem)"
|
||||
on-unassign-from-customer="vm.unassignFromCustomer(event, vm.grid.detailsConfig.currentItem, isPublic)"
|
||||
on-export-dashboard="vm.exportDashboard(event, vm.grid.detailsConfig.currentItem)"
|
||||
on-delete-dashboard="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-dashboard-details>
|
||||
</md-tab>
|
||||
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.grid.isTenantAdmin()" md-on-select="vm.grid.triggerResize()" label="{{ 'audit-log.audit-logs' | translate }}">
|
||||
<tb-audit-log-table flex entity-type="vm.types.entityType.dashboard"
|
||||
entity-id="vm.grid.operatingItem().id.id"
|
||||
audit-log-mode="{{vm.types.auditLogMode.entity}}">
|
||||
</tb-audit-log-table>
|
||||
</md-tab>
|
||||
</md-tabs>
|
||||
</tb-grid>
|
||||
|
||||
@ -39,10 +39,8 @@
|
||||
<md-icon md-svg-icon="mdi:clipboard-arrow-left"></md-icon>
|
||||
<span translate>device.copyId</span>
|
||||
</md-button>
|
||||
<md-button ngclipboard data-clipboard-action="copy"
|
||||
ngclipboard-success="onAccessTokenCopied(e)"
|
||||
data-clipboard-text="{{deviceCredentials.credentialsId}}" ng-show="!isEdit"
|
||||
class="md-raised">
|
||||
<md-button ng-show="!isEdit"
|
||||
class="md-raised" ng-click="copyAccessToken($event)">
|
||||
<md-icon md-svg-icon="mdi:clipboard-arrow-left"></md-icon>
|
||||
<span translate>device.copyAccessToken</span>
|
||||
</md-button>
|
||||
|
||||
@ -20,7 +20,7 @@ import deviceFieldsetTemplate from './device-fieldset.tpl.html';
|
||||
/* eslint-enable import/no-unresolved, import/default */
|
||||
|
||||
/*@ngInject*/
|
||||
export default function DeviceDirective($compile, $templateCache, toast, $translate, types, deviceService, customerService) {
|
||||
export default function DeviceDirective($compile, $templateCache, toast, $translate, types, clipboardService, deviceService, customerService) {
|
||||
var linker = function (scope, element) {
|
||||
var template = $templateCache.get(deviceFieldsetTemplate);
|
||||
element.html(template);
|
||||
@ -30,17 +30,8 @@ export default function DeviceDirective($compile, $templateCache, toast, $transl
|
||||
scope.isPublic = false;
|
||||
scope.assignedCustomer = null;
|
||||
|
||||
scope.deviceCredentials = null;
|
||||
|
||||
scope.$watch('device', function(newVal) {
|
||||
if (newVal) {
|
||||
if (scope.device.id) {
|
||||
deviceService.getDeviceCredentials(scope.device.id.id).then(
|
||||
function success(credentials) {
|
||||
scope.deviceCredentials = credentials;
|
||||
}
|
||||
);
|
||||
}
|
||||
if (scope.device.customerId && scope.device.customerId.id !== types.id.nullUid) {
|
||||
scope.isAssignedToCustomer = true;
|
||||
customerService.getShortCustomerInfo(scope.device.customerId.id).then(
|
||||
@ -61,8 +52,20 @@ export default function DeviceDirective($compile, $templateCache, toast, $transl
|
||||
toast.showSuccess($translate.instant('device.idCopiedMessage'), 750, angular.element(element).parent().parent(), 'bottom left');
|
||||
};
|
||||
|
||||
scope.onAccessTokenCopied = function() {
|
||||
toast.showSuccess($translate.instant('device.accessTokenCopiedMessage'), 750, angular.element(element).parent().parent(), 'bottom left');
|
||||
scope.copyAccessToken = function(e) {
|
||||
const trigger = e.delegateTarget || e.currentTarget;
|
||||
if (scope.device.id) {
|
||||
deviceService.getDeviceCredentials(scope.device.id.id, true).then(
|
||||
function success(credentials) {
|
||||
var credentialsId = credentials.credentialsId;
|
||||
clipboardService.copyToClipboard(trigger, credentialsId).then(
|
||||
() => {
|
||||
toast.showSuccess($translate.instant('device.accessTokenCopiedMessage'), 750, angular.element(element).parent().parent(), 'bottom left');
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
$compile(element.contents())(scope);
|
||||
|
||||
@ -74,4 +74,10 @@
|
||||
entity-type="{{vm.types.entityType.device}}">
|
||||
</tb-extension-table>
|
||||
</md-tab>
|
||||
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.grid.isTenantAdmin()" md-on-select="vm.grid.triggerResize()" label="{{ 'audit-log.audit-logs' | translate }}">
|
||||
<tb-audit-log-table flex entity-type="vm.types.entityType.device"
|
||||
entity-id="vm.grid.operatingItem().id.id"
|
||||
audit-log-mode="{{vm.types.auditLogMode.entity}}">
|
||||
</tb-audit-log-table>
|
||||
</md-tab>
|
||||
</tb-grid>
|
||||
|
||||
@ -26,9 +26,16 @@
|
||||
</md-select>
|
||||
</md-input-container>
|
||||
<tb-timewindow flex ng-model="timewindow" history-only as-button="true"></tb-timewindow>
|
||||
<md-button ng-disabled="$root.loading"
|
||||
class="md-icon-button" ng-click="reload()">
|
||||
<md-icon>refresh</md-icon>
|
||||
<md-tooltip md-direction="top">
|
||||
{{ 'action.refresh' | translate }}
|
||||
</md-tooltip>
|
||||
</md-button>
|
||||
</section>
|
||||
<md-list flex layout="column" class="md-whiteframe-z1 tb-event-table">
|
||||
<md-list class="tb-row tb-header" layout="row" tb-event-header event-type="{{eventType}}">
|
||||
<md-list class="tb-row tb-header" layout="row" layout-align="start center" tb-event-header event-type="{{eventType}}">
|
||||
</md-list>
|
||||
<md-progress-linear style="max-height: 0px;" md-mode="indeterminate" ng-disabled="!$root.loading"
|
||||
ng-show="$root.loading"></md-progress-linear>
|
||||
@ -38,7 +45,7 @@
|
||||
class="tb-prompt" ng-show="noData()">event.no-events-prompt</span>
|
||||
<md-virtual-repeat-container ng-show="hasData()" flex md-top-index="topIndex" tb-scope-element="repeatContainer">
|
||||
<md-list-item md-virtual-repeat="event in theEvents" md-on-demand flex ng-style="hasScroll() ? {'margin-right':'-15px'} : {}">
|
||||
<md-list class="tb-row" flex layout="row" tb-event-row event-type="{{eventType}}" event="{{event}}">
|
||||
<md-list class="tb-row" flex layout="row" layout-align="start center" tb-event-row event-type="{{eventType}}" event="{{event}}">
|
||||
</md-list>
|
||||
<md-divider flex></md-divider>
|
||||
</md-list-item>
|
||||
|
||||
@ -35,6 +35,7 @@ import thingsboardUserMenu from './user-menu.directive';
|
||||
import thingsboardEntity from '../entity';
|
||||
import thingsboardEvent from '../event';
|
||||
import thingsboardAlarm from '../alarm';
|
||||
import thingsboardAuditLog from '../audit';
|
||||
import thingsboardExtension from '../extension';
|
||||
import thingsboardTenant from '../tenant';
|
||||
import thingsboardCustomer from '../customer';
|
||||
@ -67,6 +68,7 @@ export default angular.module('thingsboard.home', [
|
||||
thingsboardEntity,
|
||||
thingsboardEvent,
|
||||
thingsboardAlarm,
|
||||
thingsboardAuditLog,
|
||||
thingsboardExtension,
|
||||
thingsboardTenant,
|
||||
thingsboardCustomer,
|
||||
|
||||
@ -286,6 +286,38 @@ export default angular.module('thingsboard.locale', [])
|
||||
"selected-attributes": "{ count, select, 1 {1 attribute} other {# attributes} } selected",
|
||||
"selected-telemetry": "{ count, select, 1 {1 telemetry unit} other {# telemetry units} } selected"
|
||||
},
|
||||
"audit-log": {
|
||||
"audit": "Audit",
|
||||
"audit-logs": "Audit Logs",
|
||||
"timestamp": "Timestamp",
|
||||
"entity-type": "Entity Type",
|
||||
"entity-name": "Entity Name",
|
||||
"user": "User",
|
||||
"type": "Type",
|
||||
"status": "Status",
|
||||
"details": "Details",
|
||||
"type-added": "Added",
|
||||
"type-deleted": "Deleted",
|
||||
"type-updated": "Updated",
|
||||
"type-attributes-updated": "Attributes updated",
|
||||
"type-attributes-deleted": "Attributes deleted",
|
||||
"type-rpc-call": "RPC call",
|
||||
"type-credentials-updated": "Credentials updated",
|
||||
"type-assigned-to-customer": "Assigned to Customer",
|
||||
"type-unassigned-from-customer": "Unassigned from Customer",
|
||||
"type-activated": "Activated",
|
||||
"type-suspended": "Suspended",
|
||||
"type-credentials-read": "Credentials read",
|
||||
"type-attributes-read": "Attributes read",
|
||||
"status-success": "Success",
|
||||
"status-failure": "Failure",
|
||||
"audit-log-details": "Audit log details",
|
||||
"no-audit-logs-prompt": "No logs found",
|
||||
"action-data": "Action data",
|
||||
"failure-details": "Failure details",
|
||||
"search": "Search audit logs",
|
||||
"clear-search": "Clear search"
|
||||
},
|
||||
"confirm-on-exit": {
|
||||
"message": "You have unsaved changes. Are you sure you want to leave this page?",
|
||||
"html-message": "You have unsaved changes.<br/>Are you sure you want to leave this page?",
|
||||
@ -1183,7 +1215,8 @@ export default angular.module('thingsboard.locale', [])
|
||||
"activation-link": "User activation link",
|
||||
"activation-link-text": "In order to activate user use the following <a href='{{activationLink}}' target='_blank'>activation link</a> :",
|
||||
"copy-activation-link": "Copy activation link",
|
||||
"activation-link-copied-message": "User activation link has been copied to clipboard"
|
||||
"activation-link-copied-message": "User activation link has been copied to clipboard",
|
||||
"details": "Details"
|
||||
},
|
||||
"value": {
|
||||
"type": "Value type",
|
||||
|
||||
@ -66,5 +66,12 @@
|
||||
entity-type="{{vm.types.entityType.plugin}}">
|
||||
</tb-relation-table>
|
||||
</md-tab>
|
||||
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isPluginEditable(vm.grid.operatingItem()) && vm.grid.isTenantAdmin()"
|
||||
md-on-select="vm.grid.triggerResize()" label="{{ 'audit-log.audit-logs' | translate }}">
|
||||
<tb-audit-log-table flex entity-type="vm.types.entityType.plugin"
|
||||
entity-id="vm.grid.operatingItem().id.id"
|
||||
audit-log-mode="{{vm.types.auditLogMode.entity}}">
|
||||
</tb-audit-log-table>
|
||||
</md-tab>
|
||||
</md-tabs>
|
||||
</tb-grid>
|
||||
|
||||
@ -66,5 +66,12 @@
|
||||
entity-type="{{vm.types.entityType.rule}}">
|
||||
</tb-relation-table>
|
||||
</md-tab>
|
||||
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isRuleEditable(vm.grid.operatingItem()) && vm.grid.isTenantAdmin()"
|
||||
md-on-select="vm.grid.triggerResize()" label="{{ 'audit-log.audit-logs' | translate }}">
|
||||
<tb-audit-log-table flex entity-type="vm.types.entityType.rule"
|
||||
entity-id="vm.grid.operatingItem().id.id"
|
||||
audit-log-mode="{{vm.types.auditLogMode.entity}}">
|
||||
</tb-audit-log-table>
|
||||
</md-tab>
|
||||
</md-tabs>
|
||||
</tb-grid>
|
||||
|
||||
128
ui/src/app/services/clipboard.service.js
Normal file
128
ui/src/app/services/clipboard.service.js
Normal file
@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright © 2016-2017 The Thingsboard Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export default angular.module('thingsboard.clipboard', [])
|
||||
.factory('clipboardService', ClipboardService)
|
||||
.name;
|
||||
|
||||
/*@ngInject*/
|
||||
function ClipboardService($q) {
|
||||
|
||||
var fakeHandler, fakeHandlerCallback, fakeElem;
|
||||
|
||||
var service = {
|
||||
copyToClipboard: copyToClipboard
|
||||
};
|
||||
|
||||
return service;
|
||||
|
||||
/* eslint-disable */
|
||||
function copyToClipboard(trigger, text) {
|
||||
var deferred = $q.defer();
|
||||
const isRTL = document.documentElement.getAttribute('dir') == 'rtl';
|
||||
removeFake();
|
||||
fakeHandlerCallback = () => removeFake();
|
||||
fakeHandler = document.body.addEventListener('click', fakeHandlerCallback) || true;
|
||||
fakeElem = document.createElement('textarea');
|
||||
fakeElem.style.fontSize = '12pt';
|
||||
fakeElem.style.border = '0';
|
||||
fakeElem.style.padding = '0';
|
||||
fakeElem.style.margin = '0';
|
||||
fakeElem.style.position = 'absolute';
|
||||
fakeElem.style[ isRTL ? 'right' : 'left' ] = '-9999px';
|
||||
let yPosition = window.pageYOffset || document.documentElement.scrollTop;
|
||||
fakeElem.style.top = `${yPosition}px`;
|
||||
fakeElem.setAttribute('readonly', '');
|
||||
fakeElem.value = text;
|
||||
document.body.appendChild(fakeElem);
|
||||
var selectedText = select(fakeElem);
|
||||
|
||||
let succeeded;
|
||||
try {
|
||||
succeeded = document.execCommand('copy');
|
||||
}
|
||||
catch (err) {
|
||||
succeeded = false;
|
||||
}
|
||||
if (trigger) {
|
||||
trigger.focus();
|
||||
}
|
||||
window.getSelection().removeAllRanges();
|
||||
removeFake();
|
||||
if (succeeded) {
|
||||
deferred.resolve(selectedText);
|
||||
} else {
|
||||
deferred.reject();
|
||||
}
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function removeFake() {
|
||||
if (fakeHandler) {
|
||||
document.body.removeEventListener('click', fakeHandlerCallback);
|
||||
fakeHandler = null;
|
||||
fakeHandlerCallback = null;
|
||||
}
|
||||
if (fakeElem) {
|
||||
document.body.removeChild(fakeElem);
|
||||
fakeElem = null;
|
||||
}
|
||||
}
|
||||
|
||||
function select(element) {
|
||||
var selectedText;
|
||||
|
||||
if (element.nodeName === 'SELECT') {
|
||||
element.focus();
|
||||
|
||||
selectedText = element.value;
|
||||
}
|
||||
else if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') {
|
||||
var isReadOnly = element.hasAttribute('readonly');
|
||||
|
||||
if (!isReadOnly) {
|
||||
element.setAttribute('readonly', '');
|
||||
}
|
||||
|
||||
element.select();
|
||||
element.setSelectionRange(0, element.value.length);
|
||||
|
||||
if (!isReadOnly) {
|
||||
element.removeAttribute('readonly');
|
||||
}
|
||||
|
||||
selectedText = element.value;
|
||||
}
|
||||
else {
|
||||
if (element.hasAttribute('contenteditable')) {
|
||||
element.focus();
|
||||
}
|
||||
|
||||
var selection = window.getSelection();
|
||||
var range = document.createRange();
|
||||
|
||||
range.selectNodeContents(element);
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
|
||||
selectedText = selection.toString();
|
||||
}
|
||||
|
||||
return selectedText;
|
||||
}
|
||||
|
||||
/* eslint-enable */
|
||||
|
||||
}
|
||||
@ -211,6 +211,12 @@ function Menu(userService, $state, $rootScope) {
|
||||
type: 'link',
|
||||
state: 'home.dashboards',
|
||||
icon: 'dashboards'
|
||||
},
|
||||
{
|
||||
name: 'audit-log.audit-logs',
|
||||
type: 'link',
|
||||
state: 'home.auditLogs',
|
||||
icon: 'track_changes'
|
||||
}];
|
||||
|
||||
homeSections =
|
||||
@ -273,6 +279,16 @@ function Menu(userService, $state, $rootScope) {
|
||||
state: 'home.dashboards'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'audit-log.audit',
|
||||
places: [
|
||||
{
|
||||
name: 'audit-log.audit-logs',
|
||||
icon: 'track_changes',
|
||||
state: 'home.auditLogs'
|
||||
}
|
||||
]
|
||||
}];
|
||||
|
||||
} else if (authority === 'CUSTOMER_USER') {
|
||||
|
||||
@ -42,6 +42,8 @@ export default function UserController(userService, toast, $scope, $mdDialog, $d
|
||||
|
||||
var vm = this;
|
||||
|
||||
vm.types = types;
|
||||
|
||||
vm.userGridConfig = {
|
||||
deleteItemTitleFunc: deleteUserTitle,
|
||||
deleteItemContentFunc: deleteUserText,
|
||||
|
||||
@ -19,10 +19,20 @@
|
||||
<details-buttons tb-help="'users'" help-container-id="help-container">
|
||||
<div id="help-container"></div>
|
||||
</details-buttons>
|
||||
<tb-user user="vm.grid.operatingItem()"
|
||||
is-edit="vm.grid.detailsConfig.isDetailsEditMode"
|
||||
the-form="vm.grid.detailsForm"
|
||||
on-display-activation-link="vm.displayActivationLink(event, vm.grid.detailsConfig.currentItem)"
|
||||
on-resend-activation="vm.resendActivation(vm.grid.detailsConfig.currentItem)"
|
||||
on-delete-user="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-user>
|
||||
<md-tabs ng-class="{'tb-headless': vm.grid.detailsConfig.isDetailsEditMode}"
|
||||
id="tabs" md-border-bottom flex class="tb-absolute-fill">
|
||||
<md-tab label="{{ 'user.details' | translate }}">
|
||||
<tb-user user="vm.grid.operatingItem()"
|
||||
is-edit="vm.grid.detailsConfig.isDetailsEditMode"
|
||||
the-form="vm.grid.detailsForm"
|
||||
on-display-activation-link="vm.displayActivationLink(event, vm.grid.detailsConfig.currentItem)"
|
||||
on-resend-activation="vm.resendActivation(vm.grid.detailsConfig.currentItem)"
|
||||
on-delete-user="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-user>
|
||||
</md-tab>
|
||||
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.grid.isTenantAdmin()" md-on-select="vm.grid.triggerResize()" label="{{ 'audit-log.audit-logs' | translate }}">
|
||||
<tb-audit-log-table flex user-id="vm.grid.operatingItem().id.id"
|
||||
audit-log-mode="{{vm.types.auditLogMode.user}}">
|
||||
</tb-audit-log-table>
|
||||
</md-tab>
|
||||
</md-tabs>
|
||||
</tb-grid>
|
||||
|
||||
@ -203,6 +203,19 @@ md-sidenav {
|
||||
* THINGSBOARD SPECIFIC
|
||||
***********************/
|
||||
|
||||
label {
|
||||
&.tb-title {
|
||||
pointer-events: none;
|
||||
color: #666;
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
padding-bottom: 15px;
|
||||
&.no-padding {
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tb-noselect {
|
||||
-webkit-touch-callout: none; /* iOS Safari */
|
||||
-webkit-user-select: none; /* Safari */
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user