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