committed by
GitHub
100 changed files with 4278 additions and 112 deletions
@ -0,0 +1,89 @@ |
|||
-- |
|||
-- Copyright © 2016-2017 The Thingsboard Authors |
|||
-- |
|||
-- Licensed under the Apache License, Version 2.0 (the "License"); |
|||
-- you may not use this file except in compliance with the License. |
|||
-- You may obtain a copy of the License at |
|||
-- |
|||
-- http://www.apache.org/licenses/LICENSE-2.0 |
|||
-- |
|||
-- Unless required by applicable law or agreed to in writing, software |
|||
-- distributed under the License is distributed on an "AS IS" BASIS, |
|||
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
-- See the License for the specific language governing permissions and |
|||
-- limitations under the License. |
|||
-- |
|||
|
|||
CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_entity_id ( |
|||
tenant_id timeuuid, |
|||
id timeuuid, |
|||
customer_id timeuuid, |
|||
entity_id timeuuid, |
|||
entity_type text, |
|||
entity_name text, |
|||
user_id timeuuid, |
|||
user_name text, |
|||
action_type text, |
|||
action_data text, |
|||
action_status text, |
|||
action_failure_details text, |
|||
PRIMARY KEY ((tenant_id, entity_id, entity_type), id) |
|||
); |
|||
|
|||
CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_customer_id ( |
|||
tenant_id timeuuid, |
|||
id timeuuid, |
|||
customer_id timeuuid, |
|||
entity_id timeuuid, |
|||
entity_type text, |
|||
entity_name text, |
|||
user_id timeuuid, |
|||
user_name text, |
|||
action_type text, |
|||
action_data text, |
|||
action_status text, |
|||
action_failure_details text, |
|||
PRIMARY KEY ((tenant_id, customer_id), id) |
|||
); |
|||
|
|||
CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_user_id ( |
|||
tenant_id timeuuid, |
|||
id timeuuid, |
|||
customer_id timeuuid, |
|||
entity_id timeuuid, |
|||
entity_type text, |
|||
entity_name text, |
|||
user_id timeuuid, |
|||
user_name text, |
|||
action_type text, |
|||
action_data text, |
|||
action_status text, |
|||
action_failure_details text, |
|||
PRIMARY KEY ((tenant_id, user_id), id) |
|||
); |
|||
|
|||
|
|||
|
|||
CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_tenant_id ( |
|||
tenant_id timeuuid, |
|||
id timeuuid, |
|||
partition bigint, |
|||
customer_id timeuuid, |
|||
entity_id timeuuid, |
|||
entity_type text, |
|||
entity_name text, |
|||
user_id timeuuid, |
|||
user_name text, |
|||
action_type text, |
|||
action_data text, |
|||
action_status text, |
|||
action_failure_details text, |
|||
PRIMARY KEY ((tenant_id, partition), id) |
|||
); |
|||
|
|||
CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_tenant_id_partitions ( |
|||
tenant_id timeuuid, |
|||
partition bigint, |
|||
PRIMARY KEY (( tenant_id ), partition) |
|||
) WITH CLUSTERING ORDER BY ( partition ASC ) |
|||
AND compaction = { 'class' : 'LeveledCompactionStrategy' }; |
|||
@ -0,0 +1,31 @@ |
|||
-- |
|||
-- Copyright © 2016-2017 The Thingsboard Authors |
|||
-- |
|||
-- Licensed under the Apache License, Version 2.0 (the "License"); |
|||
-- you may not use this file except in compliance with the License. |
|||
-- You may obtain a copy of the License at |
|||
-- |
|||
-- http://www.apache.org/licenses/LICENSE-2.0 |
|||
-- |
|||
-- Unless required by applicable law or agreed to in writing, software |
|||
-- distributed under the License is distributed on an "AS IS" BASIS, |
|||
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
-- See the License for the specific language governing permissions and |
|||
-- limitations under the License. |
|||
-- |
|||
|
|||
CREATE TABLE IF NOT EXISTS audit_log ( |
|||
id varchar(31) NOT NULL CONSTRAINT audit_log_pkey PRIMARY KEY, |
|||
tenant_id varchar(31), |
|||
customer_id varchar(31), |
|||
entity_id varchar(31), |
|||
entity_type varchar(255), |
|||
entity_name varchar(255), |
|||
user_id varchar(31), |
|||
user_name varchar(255), |
|||
action_type varchar(255), |
|||
action_data varchar(1000000), |
|||
action_status varchar(255), |
|||
action_failure_details varchar(1000000) |
|||
); |
|||
|
|||
@ -0,0 +1,43 @@ |
|||
/** |
|||
* Copyright © 2016-2017 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.config; |
|||
|
|||
import org.springframework.boot.context.properties.ConfigurationProperties; |
|||
import org.springframework.context.annotation.Configuration; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
import org.thingsboard.server.common.data.audit.ActionType; |
|||
|
|||
import java.util.HashMap; |
|||
import java.util.Map; |
|||
|
|||
@Configuration |
|||
@ConfigurationProperties(prefix = "audit_log.logging_level") |
|||
public class AuditLogLevelProperties { |
|||
|
|||
private Map<String, String> mask = new HashMap<>(); |
|||
|
|||
public AuditLogLevelProperties() { |
|||
super(); |
|||
} |
|||
|
|||
public void setMask(Map<String, String> mask) { |
|||
this.mask = mask; |
|||
} |
|||
|
|||
public Map<String, String> getMask() { |
|||
return this.mask; |
|||
} |
|||
} |
|||
@ -0,0 +1,114 @@ |
|||
/** |
|||
* Copyright © 2016-2017 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.controller; |
|||
|
|||
import org.springframework.security.access.prepost.PreAuthorize; |
|||
import org.springframework.web.bind.annotation.*; |
|||
import org.thingsboard.server.common.data.audit.AuditLog; |
|||
import org.thingsboard.server.common.data.id.CustomerId; |
|||
import org.thingsboard.server.common.data.id.EntityIdFactory; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.id.UserId; |
|||
import org.thingsboard.server.common.data.page.TimePageData; |
|||
import org.thingsboard.server.common.data.page.TimePageLink; |
|||
import org.thingsboard.server.exception.ThingsboardException; |
|||
|
|||
import java.util.UUID; |
|||
|
|||
@RestController |
|||
@RequestMapping("/api") |
|||
public class AuditLogController extends BaseController { |
|||
|
|||
@PreAuthorize("hasAuthority('TENANT_ADMIN')") |
|||
@RequestMapping(value = "/audit/logs/customer/{customerId}", params = {"limit"}, method = RequestMethod.GET) |
|||
@ResponseBody |
|||
public TimePageData<AuditLog> getAuditLogsByCustomerId( |
|||
@PathVariable("customerId") String strCustomerId, |
|||
@RequestParam int limit, |
|||
@RequestParam(required = false) Long startTime, |
|||
@RequestParam(required = false) Long endTime, |
|||
@RequestParam(required = false, defaultValue = "false") boolean ascOrder, |
|||
@RequestParam(required = false) String offset) throws ThingsboardException { |
|||
try { |
|||
checkParameter("CustomerId", strCustomerId); |
|||
TenantId tenantId = getCurrentUser().getTenantId(); |
|||
TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset); |
|||
return checkNotNull(auditLogService.findAuditLogsByTenantIdAndCustomerId(tenantId, new CustomerId(UUID.fromString(strCustomerId)), pageLink)); |
|||
} catch (Exception e) { |
|||
throw handleException(e); |
|||
} |
|||
} |
|||
|
|||
@PreAuthorize("hasAuthority('TENANT_ADMIN')") |
|||
@RequestMapping(value = "/audit/logs/user/{userId}", params = {"limit"}, method = RequestMethod.GET) |
|||
@ResponseBody |
|||
public TimePageData<AuditLog> getAuditLogsByUserId( |
|||
@PathVariable("userId") String strUserId, |
|||
@RequestParam int limit, |
|||
@RequestParam(required = false) Long startTime, |
|||
@RequestParam(required = false) Long endTime, |
|||
@RequestParam(required = false, defaultValue = "false") boolean ascOrder, |
|||
@RequestParam(required = false) String offset) throws ThingsboardException { |
|||
try { |
|||
checkParameter("UserId", strUserId); |
|||
TenantId tenantId = getCurrentUser().getTenantId(); |
|||
TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset); |
|||
return checkNotNull(auditLogService.findAuditLogsByTenantIdAndUserId(tenantId, new UserId(UUID.fromString(strUserId)), pageLink)); |
|||
} catch (Exception e) { |
|||
throw handleException(e); |
|||
} |
|||
} |
|||
|
|||
@PreAuthorize("hasAuthority('TENANT_ADMIN')") |
|||
@RequestMapping(value = "/audit/logs/entity/{entityType}/{entityId}", params = {"limit"}, method = RequestMethod.GET) |
|||
@ResponseBody |
|||
public TimePageData<AuditLog> getAuditLogsByEntityId( |
|||
@PathVariable("entityType") String strEntityType, |
|||
@PathVariable("entityId") String strEntityId, |
|||
@RequestParam int limit, |
|||
@RequestParam(required = false) Long startTime, |
|||
@RequestParam(required = false) Long endTime, |
|||
@RequestParam(required = false, defaultValue = "false") boolean ascOrder, |
|||
@RequestParam(required = false) String offset) throws ThingsboardException { |
|||
try { |
|||
checkParameter("EntityId", strEntityId); |
|||
checkParameter("EntityType", strEntityType); |
|||
TenantId tenantId = getCurrentUser().getTenantId(); |
|||
TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset); |
|||
return checkNotNull(auditLogService.findAuditLogsByTenantIdAndEntityId(tenantId, EntityIdFactory.getByTypeAndId(strEntityType, strEntityId), pageLink)); |
|||
} catch (Exception e) { |
|||
throw handleException(e); |
|||
} |
|||
} |
|||
|
|||
@PreAuthorize("hasAuthority('TENANT_ADMIN')") |
|||
@RequestMapping(value = "/audit/logs", params = {"limit"}, method = RequestMethod.GET) |
|||
@ResponseBody |
|||
public TimePageData<AuditLog> getAuditLogs( |
|||
@RequestParam int limit, |
|||
@RequestParam(required = false) Long startTime, |
|||
@RequestParam(required = false) Long endTime, |
|||
@RequestParam(required = false, defaultValue = "false") boolean ascOrder, |
|||
@RequestParam(required = false) String offset) throws ThingsboardException { |
|||
try { |
|||
TenantId tenantId = getCurrentUser().getTenantId(); |
|||
TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset); |
|||
return checkNotNull(auditLogService.findAuditLogsByTenantId(tenantId, pageLink)); |
|||
} catch (Exception e) { |
|||
throw handleException(e); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
/** |
|||
* Copyright © 2016-2017 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
|
|||
package org.thingsboard.server.install; |
|||
|
|||
import org.springframework.context.annotation.Bean; |
|||
import org.springframework.context.annotation.Configuration; |
|||
import org.springframework.context.annotation.Profile; |
|||
import org.thingsboard.server.dao.audit.AuditLogLevelFilter; |
|||
|
|||
import java.util.HashMap; |
|||
|
|||
@Configuration |
|||
@Profile("install") |
|||
public class ThingsboardInstallConfiguration { |
|||
|
|||
@Bean |
|||
public AuditLogLevelFilter emptyAuditLogLevelFilter() { |
|||
return new AuditLogLevelFilter(new HashMap<>()); |
|||
} |
|||
} |
|||
@ -0,0 +1,148 @@ |
|||
/** |
|||
* Copyright © 2016-2017 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.controller; |
|||
|
|||
import com.fasterxml.jackson.core.type.TypeReference; |
|||
import org.junit.After; |
|||
import org.junit.Assert; |
|||
import org.junit.Before; |
|||
import org.junit.Test; |
|||
import org.thingsboard.server.common.data.Device; |
|||
import org.thingsboard.server.common.data.Tenant; |
|||
import org.thingsboard.server.common.data.User; |
|||
import org.thingsboard.server.common.data.audit.AuditLog; |
|||
import org.thingsboard.server.common.data.page.TimePageData; |
|||
import org.thingsboard.server.common.data.page.TimePageLink; |
|||
import org.thingsboard.server.common.data.security.Authority; |
|||
import org.thingsboard.server.dao.model.ModelConstants; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
|
|||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; |
|||
|
|||
public abstract class BaseAuditLogControllerTest extends AbstractControllerTest { |
|||
|
|||
private Tenant savedTenant; |
|||
private User tenantAdmin; |
|||
|
|||
@Before |
|||
public void beforeTest() throws Exception { |
|||
loginSysAdmin(); |
|||
|
|||
Tenant tenant = new Tenant(); |
|||
tenant.setTitle("My tenant"); |
|||
savedTenant = doPost("/api/tenant", tenant, Tenant.class); |
|||
Assert.assertNotNull(savedTenant); |
|||
|
|||
tenantAdmin = new User(); |
|||
tenantAdmin.setAuthority(Authority.TENANT_ADMIN); |
|||
tenantAdmin.setTenantId(savedTenant.getId()); |
|||
tenantAdmin.setEmail("tenant2@thingsboard.org"); |
|||
tenantAdmin.setFirstName("Joe"); |
|||
tenantAdmin.setLastName("Downs"); |
|||
|
|||
tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); |
|||
} |
|||
|
|||
@After |
|||
public void afterTest() throws Exception { |
|||
loginSysAdmin(); |
|||
|
|||
doDelete("/api/tenant/" + savedTenant.getId().getId().toString()) |
|||
.andExpect(status().isOk()); |
|||
} |
|||
|
|||
@Test |
|||
public void testAuditLogs() throws Exception { |
|||
for (int i = 0; i < 178; i++) { |
|||
Device device = new Device(); |
|||
device.setName("Device" + i); |
|||
device.setType("default"); |
|||
doPost("/api/device", device, Device.class); |
|||
} |
|||
|
|||
List<AuditLog> loadedAuditLogs = new ArrayList<>(); |
|||
TimePageLink pageLink = new TimePageLink(23); |
|||
TimePageData<AuditLog> pageData; |
|||
do { |
|||
pageData = doGetTypedWithTimePageLink("/api/audit/logs?", |
|||
new TypeReference<TimePageData<AuditLog>>() { |
|||
}, pageLink); |
|||
loadedAuditLogs.addAll(pageData.getData()); |
|||
if (pageData.hasNext()) { |
|||
pageLink = pageData.getNextPageLink(); |
|||
} |
|||
} while (pageData.hasNext()); |
|||
|
|||
Assert.assertEquals(178, loadedAuditLogs.size()); |
|||
|
|||
loadedAuditLogs = new ArrayList<>(); |
|||
pageLink = new TimePageLink(23); |
|||
do { |
|||
pageData = doGetTypedWithTimePageLink("/api/audit/logs/customer/" + ModelConstants.NULL_UUID + "?", |
|||
new TypeReference<TimePageData<AuditLog>>() { |
|||
}, pageLink); |
|||
loadedAuditLogs.addAll(pageData.getData()); |
|||
if (pageData.hasNext()) { |
|||
pageLink = pageData.getNextPageLink(); |
|||
} |
|||
} while (pageData.hasNext()); |
|||
|
|||
Assert.assertEquals(178, loadedAuditLogs.size()); |
|||
|
|||
loadedAuditLogs = new ArrayList<>(); |
|||
pageLink = new TimePageLink(23); |
|||
do { |
|||
pageData = doGetTypedWithTimePageLink("/api/audit/logs/user/" + tenantAdmin.getId().getId().toString() + "?", |
|||
new TypeReference<TimePageData<AuditLog>>() { |
|||
}, pageLink); |
|||
loadedAuditLogs.addAll(pageData.getData()); |
|||
if (pageData.hasNext()) { |
|||
pageLink = pageData.getNextPageLink(); |
|||
} |
|||
} while (pageData.hasNext()); |
|||
|
|||
Assert.assertEquals(178, loadedAuditLogs.size()); |
|||
} |
|||
|
|||
@Test |
|||
public void testAuditLogs_byTenantIdAndEntityId() throws Exception { |
|||
Device device = new Device(); |
|||
device.setName("Device name"); |
|||
device.setType("default"); |
|||
Device savedDevice = doPost("/api/device", device, Device.class); |
|||
for (int i = 0; i < 178; i++) { |
|||
savedDevice.setName("Device name" + i); |
|||
doPost("/api/device", savedDevice, Device.class); |
|||
} |
|||
|
|||
List<AuditLog> loadedAuditLogs = new ArrayList<>(); |
|||
TimePageLink pageLink = new TimePageLink(23); |
|||
TimePageData<AuditLog> pageData; |
|||
do { |
|||
pageData = doGetTypedWithTimePageLink("/api/audit/logs/entity/DEVICE/" + savedDevice.getId().getId() + "?", |
|||
new TypeReference<TimePageData<AuditLog>>() { |
|||
}, pageLink); |
|||
loadedAuditLogs.addAll(pageData.getData()); |
|||
if (pageData.hasNext()) { |
|||
pageLink = pageData.getNextPageLink(); |
|||
} |
|||
} while (pageData.hasNext()); |
|||
|
|||
Assert.assertEquals(179, loadedAuditLogs.size()); |
|||
} |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
/** |
|||
* Copyright © 2016-2017 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.controller.nosql; |
|||
|
|||
import org.thingsboard.server.controller.BaseAuditLogControllerTest; |
|||
import org.thingsboard.server.dao.service.DaoNoSqlTest; |
|||
|
|||
@DaoNoSqlTest |
|||
public class AuditLogControllerNoSqlTest extends BaseAuditLogControllerTest { |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
/** |
|||
* Copyright © 2016-2017 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.controller.sql; |
|||
|
|||
import org.thingsboard.server.controller.BaseAuditLogControllerTest; |
|||
import org.thingsboard.server.dao.service.DaoSqlTest; |
|||
|
|||
@DaoSqlTest |
|||
public class AuditLogControllerSqlTest extends BaseAuditLogControllerTest { |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
/** |
|||
* Copyright © 2016-2017 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.common.data.audit; |
|||
|
|||
public enum ActionStatus { |
|||
SUCCESS, FAILURE |
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
/** |
|||
* Copyright © 2016-2017 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.common.data.audit; |
|||
|
|||
import lombok.Getter; |
|||
|
|||
@Getter |
|||
public enum ActionType { |
|||
ADDED(false), // log entity
|
|||
DELETED(false), // log string id
|
|||
UPDATED(false), // log entity
|
|||
ATTRIBUTES_UPDATED(false), // log attributes/values
|
|||
ATTRIBUTES_DELETED(false), // log attributes
|
|||
RPC_CALL(false), // log method and params
|
|||
CREDENTIALS_UPDATED(false), // log new credentials
|
|||
ASSIGNED_TO_CUSTOMER(false), // log customer name
|
|||
UNASSIGNED_FROM_CUSTOMER(false), // log customer name
|
|||
ACTIVATED(false), // log string id
|
|||
SUSPENDED(false), // log string id
|
|||
CREDENTIALS_READ(true), // log device id
|
|||
ATTRIBUTES_READ(true); // log attributes
|
|||
|
|||
private final boolean isRead; |
|||
|
|||
ActionType(boolean isRead) { |
|||
this.isRead = isRead; |
|||
} |
|||
} |
|||
@ -0,0 +1,60 @@ |
|||
/** |
|||
* Copyright © 2016-2017 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.common.data.audit; |
|||
|
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import lombok.Data; |
|||
import lombok.EqualsAndHashCode; |
|||
import org.thingsboard.server.common.data.BaseData; |
|||
import org.thingsboard.server.common.data.id.*; |
|||
|
|||
@EqualsAndHashCode(callSuper = true) |
|||
@Data |
|||
public class AuditLog extends BaseData<AuditLogId> { |
|||
|
|||
private TenantId tenantId; |
|||
private CustomerId customerId; |
|||
private EntityId entityId; |
|||
private String entityName; |
|||
private UserId userId; |
|||
private String userName; |
|||
private ActionType actionType; |
|||
private JsonNode actionData; |
|||
private ActionStatus actionStatus; |
|||
private String actionFailureDetails; |
|||
|
|||
public AuditLog() { |
|||
super(); |
|||
} |
|||
|
|||
public AuditLog(AuditLogId id) { |
|||
super(id); |
|||
} |
|||
|
|||
public AuditLog(AuditLog auditLog) { |
|||
super(auditLog); |
|||
this.tenantId = auditLog.getTenantId(); |
|||
this.customerId = auditLog.getCustomerId(); |
|||
this.entityId = auditLog.getEntityId(); |
|||
this.entityName = auditLog.getEntityName(); |
|||
this.userId = auditLog.getUserId(); |
|||
this.userName = auditLog.getUserName(); |
|||
this.actionType = auditLog.getActionType(); |
|||
this.actionData = auditLog.getActionData(); |
|||
this.actionStatus = auditLog.getActionStatus(); |
|||
this.actionFailureDetails = auditLog.getActionFailureDetails(); |
|||
} |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
/** |
|||
* Copyright © 2016-2017 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.common.data.id; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonCreator; |
|||
import com.fasterxml.jackson.annotation.JsonProperty; |
|||
|
|||
import java.util.UUID; |
|||
|
|||
public class AuditLogId extends UUIDBased { |
|||
|
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
@JsonCreator |
|||
public AuditLogId(@JsonProperty("id") UUID id) { |
|||
super(id); |
|||
} |
|||
|
|||
public static AuditLogId fromString(String auditLogId) { |
|||
return new AuditLogId(UUID.fromString(auditLogId)); |
|||
} |
|||
} |
|||
@ -0,0 +1,47 @@ |
|||
/** |
|||
* Copyright © 2016-2017 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.dao.audit; |
|||
|
|||
import com.google.common.util.concurrent.ListenableFuture; |
|||
import org.thingsboard.server.common.data.audit.AuditLog; |
|||
import org.thingsboard.server.common.data.id.CustomerId; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.data.id.UserId; |
|||
import org.thingsboard.server.common.data.page.TimePageLink; |
|||
|
|||
import java.util.List; |
|||
import java.util.UUID; |
|||
|
|||
public interface AuditLogDao { |
|||
|
|||
ListenableFuture<Void> saveByTenantId(AuditLog auditLog); |
|||
|
|||
ListenableFuture<Void> saveByTenantIdAndEntityId(AuditLog auditLog); |
|||
|
|||
ListenableFuture<Void> saveByTenantIdAndCustomerId(AuditLog auditLog); |
|||
|
|||
ListenableFuture<Void> saveByTenantIdAndUserId(AuditLog auditLog); |
|||
|
|||
ListenableFuture<Void> savePartitionsByTenantId(AuditLog auditLog); |
|||
|
|||
List<AuditLog> findAuditLogsByTenantIdAndEntityId(UUID tenantId, EntityId entityId, TimePageLink pageLink); |
|||
|
|||
List<AuditLog> findAuditLogsByTenantIdAndCustomerId(UUID tenantId, CustomerId customerId, TimePageLink pageLink); |
|||
|
|||
List<AuditLog> findAuditLogsByTenantIdAndUserId(UUID tenantId, UserId userId, TimePageLink pageLink); |
|||
|
|||
List<AuditLog> findAuditLogsByTenantId(UUID tenantId, TimePageLink pageLink); |
|||
} |
|||
@ -0,0 +1,46 @@ |
|||
/** |
|||
* Copyright © 2016-2017 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.dao.audit; |
|||
|
|||
import org.thingsboard.server.common.data.EntityType; |
|||
import org.thingsboard.server.common.data.audit.ActionType; |
|||
|
|||
import java.util.HashMap; |
|||
import java.util.Map; |
|||
|
|||
public class AuditLogLevelFilter { |
|||
|
|||
private Map<EntityType, AuditLogLevelMask> entityTypeMask = new HashMap<>(); |
|||
|
|||
public AuditLogLevelFilter(Map<String, String> mask) { |
|||
entityTypeMask.clear(); |
|||
mask.forEach((entityTypeStr, logLevelMaskStr) -> { |
|||
EntityType entityType = EntityType.valueOf(entityTypeStr.toUpperCase()); |
|||
AuditLogLevelMask logLevelMask = AuditLogLevelMask.valueOf(logLevelMaskStr.toUpperCase()); |
|||
entityTypeMask.put(entityType, logLevelMask); |
|||
}); |
|||
} |
|||
|
|||
public boolean logEnabled(EntityType entityType, ActionType actionType) { |
|||
AuditLogLevelMask logLevelMask = entityTypeMask.get(entityType); |
|||
if (logLevelMask != null) { |
|||
return actionType.isRead() ? logLevelMask.isRead() : logLevelMask.isWrite(); |
|||
} else { |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
/** |
|||
* Copyright © 2016-2017 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.dao.audit; |
|||
|
|||
import lombok.Getter; |
|||
|
|||
@Getter |
|||
public enum AuditLogLevelMask { |
|||
|
|||
OFF(false, false), |
|||
W(true, false), |
|||
RW(true, true); |
|||
|
|||
private final boolean write; |
|||
private final boolean read; |
|||
|
|||
AuditLogLevelMask(boolean write, boolean read) { |
|||
this.write = write; |
|||
this.read = read; |
|||
} |
|||
} |
|||
@ -0,0 +1,70 @@ |
|||
/** |
|||
* Copyright © 2016-2017 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.dao.audit; |
|||
|
|||
import lombok.Getter; |
|||
import org.thingsboard.server.common.data.page.TimePageLink; |
|||
import org.thingsboard.server.dao.model.nosql.AuditLogEntity; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
import java.util.UUID; |
|||
|
|||
public class AuditLogQueryCursor { |
|||
@Getter |
|||
private final UUID tenantId; |
|||
@Getter |
|||
private final List<AuditLogEntity> data; |
|||
@Getter |
|||
private final TimePageLink pageLink; |
|||
|
|||
private final List<Long> partitions; |
|||
|
|||
private int partitionIndex; |
|||
private int currentLimit; |
|||
|
|||
public AuditLogQueryCursor(UUID tenantId, TimePageLink pageLink, List<Long> partitions) { |
|||
this.tenantId = tenantId; |
|||
this.partitions = partitions; |
|||
this.partitionIndex = partitions.size() - 1; |
|||
this.data = new ArrayList<>(); |
|||
this.currentLimit = pageLink.getLimit(); |
|||
this.pageLink = pageLink; |
|||
} |
|||
|
|||
public boolean hasNextPartition() { |
|||
return partitionIndex >= 0; |
|||
} |
|||
|
|||
public boolean isFull() { |
|||
return currentLimit <= 0; |
|||
} |
|||
|
|||
public long getNextPartition() { |
|||
long partition = partitions.get(partitionIndex); |
|||
partitionIndex--; |
|||
return partition; |
|||
} |
|||
|
|||
public int getCurrentLimit() { |
|||
return currentLimit; |
|||
} |
|||
|
|||
public void addData(List<AuditLogEntity> newData) { |
|||
currentLimit -= newData.size(); |
|||
data.addAll(newData); |
|||
} |
|||
} |
|||
@ -0,0 +1,53 @@ |
|||
/** |
|||
* Copyright © 2016-2017 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.dao.audit; |
|||
|
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import com.google.common.util.concurrent.ListenableFuture; |
|||
import org.thingsboard.server.common.data.BaseData; |
|||
import org.thingsboard.server.common.data.HasName; |
|||
import org.thingsboard.server.common.data.User; |
|||
import org.thingsboard.server.common.data.audit.ActionStatus; |
|||
import org.thingsboard.server.common.data.audit.ActionType; |
|||
import org.thingsboard.server.common.data.audit.AuditLog; |
|||
import org.thingsboard.server.common.data.id.*; |
|||
import org.thingsboard.server.common.data.page.TimePageData; |
|||
import org.thingsboard.server.common.data.page.TimePageLink; |
|||
|
|||
import java.util.List; |
|||
|
|||
public interface AuditLogService { |
|||
|
|||
TimePageData<AuditLog> findAuditLogsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TimePageLink pageLink); |
|||
|
|||
TimePageData<AuditLog> findAuditLogsByTenantIdAndUserId(TenantId tenantId, UserId userId, TimePageLink pageLink); |
|||
|
|||
TimePageData<AuditLog> findAuditLogsByTenantIdAndEntityId(TenantId tenantId, EntityId entityId, TimePageLink pageLink); |
|||
|
|||
TimePageData<AuditLog> findAuditLogsByTenantId(TenantId tenantId, TimePageLink pageLink); |
|||
|
|||
<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); |
|||
|
|||
} |
|||
@ -0,0 +1,316 @@ |
|||
/** |
|||
* Copyright © 2016-2017 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.dao.audit; |
|||
|
|||
import com.datastax.driver.core.utils.UUIDs; |
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import com.fasterxml.jackson.databind.ObjectMapper; |
|||
import com.fasterxml.jackson.databind.node.ArrayNode; |
|||
import com.fasterxml.jackson.databind.node.ObjectNode; |
|||
import com.google.common.collect.Lists; |
|||
import com.google.common.util.concurrent.Futures; |
|||
import com.google.common.util.concurrent.ListenableFuture; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
|||
import org.springframework.stereotype.Service; |
|||
import org.springframework.util.StringUtils; |
|||
import org.thingsboard.server.common.data.BaseData; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
import org.thingsboard.server.common.data.HasName; |
|||
import org.thingsboard.server.common.data.audit.ActionStatus; |
|||
import org.thingsboard.server.common.data.audit.ActionType; |
|||
import org.thingsboard.server.common.data.audit.AuditLog; |
|||
import org.thingsboard.server.common.data.id.*; |
|||
import org.thingsboard.server.common.data.kv.AttributeKvEntry; |
|||
import org.thingsboard.server.common.data.page.TimePageData; |
|||
import org.thingsboard.server.common.data.page.TimePageLink; |
|||
import org.thingsboard.server.common.data.security.DeviceCredentials; |
|||
import org.thingsboard.server.dao.entity.EntityService; |
|||
import org.thingsboard.server.dao.exception.DataValidationException; |
|||
import org.thingsboard.server.dao.service.DataValidator; |
|||
|
|||
import java.io.PrintWriter; |
|||
import java.io.StringWriter; |
|||
import java.util.List; |
|||
|
|||
import static org.thingsboard.server.dao.service.Validator.validateEntityId; |
|||
import static org.thingsboard.server.dao.service.Validator.validateId; |
|||
|
|||
@Slf4j |
|||
@Service |
|||
@ConditionalOnProperty(prefix = "audit_log", value = "enabled", havingValue = "true") |
|||
public class AuditLogServiceImpl implements AuditLogService { |
|||
|
|||
private static final ObjectMapper objectMapper = new ObjectMapper(); |
|||
|
|||
private static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; |
|||
private static final int INSERTS_PER_ENTRY = 3; |
|||
|
|||
@Autowired |
|||
private AuditLogLevelFilter auditLogLevelFilter; |
|||
|
|||
@Autowired |
|||
private AuditLogDao auditLogDao; |
|||
|
|||
@Autowired |
|||
private EntityService entityService; |
|||
|
|||
@Override |
|||
public TimePageData<AuditLog> findAuditLogsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TimePageLink pageLink) { |
|||
log.trace("Executing findAuditLogsByTenantIdAndCustomerId [{}], [{}], [{}]", tenantId, customerId, pageLink); |
|||
validateId(tenantId, INCORRECT_TENANT_ID + tenantId); |
|||
validateId(customerId, "Incorrect customerId " + customerId); |
|||
List<AuditLog> auditLogs = auditLogDao.findAuditLogsByTenantIdAndCustomerId(tenantId.getId(), customerId, pageLink); |
|||
return new TimePageData<>(auditLogs, pageLink); |
|||
} |
|||
|
|||
@Override |
|||
public TimePageData<AuditLog> findAuditLogsByTenantIdAndUserId(TenantId tenantId, UserId userId, TimePageLink pageLink) { |
|||
log.trace("Executing findAuditLogsByTenantIdAndUserId [{}], [{}], [{}]", tenantId, userId, pageLink); |
|||
validateId(tenantId, INCORRECT_TENANT_ID + tenantId); |
|||
validateId(userId, "Incorrect userId" + userId); |
|||
List<AuditLog> auditLogs = auditLogDao.findAuditLogsByTenantIdAndUserId(tenantId.getId(), userId, pageLink); |
|||
return new TimePageData<>(auditLogs, pageLink); |
|||
} |
|||
|
|||
@Override |
|||
public TimePageData<AuditLog> findAuditLogsByTenantIdAndEntityId(TenantId tenantId, EntityId entityId, TimePageLink pageLink) { |
|||
log.trace("Executing findAuditLogsByTenantIdAndEntityId [{}], [{}], [{}]", tenantId, entityId, pageLink); |
|||
validateId(tenantId, INCORRECT_TENANT_ID + tenantId); |
|||
validateEntityId(entityId, INCORRECT_TENANT_ID + entityId); |
|||
List<AuditLog> auditLogs = auditLogDao.findAuditLogsByTenantIdAndEntityId(tenantId.getId(), entityId, pageLink); |
|||
return new TimePageData<>(auditLogs, pageLink); |
|||
} |
|||
|
|||
@Override |
|||
public TimePageData<AuditLog> findAuditLogsByTenantId(TenantId tenantId, TimePageLink pageLink) { |
|||
log.trace("Executing findAuditLogs [{}]", pageLink); |
|||
validateId(tenantId, INCORRECT_TENANT_ID + tenantId); |
|||
List<AuditLog> auditLogs = auditLogDao.findAuditLogsByTenantId(tenantId.getId(), pageLink); |
|||
return new TimePageData<>(auditLogs, pageLink); |
|||
} |
|||
|
|||
@Override |
|||
public <E extends BaseData<I> & HasName, I extends UUIDBased & EntityId> ListenableFuture<List<Void>> |
|||
logEntityAction(TenantId tenantId, CustomerId customerId, UserId userId, String userName, I entityId, E entity, |
|||
ActionType actionType, Exception e, Object... additionalInfo) { |
|||
if (canLog(entityId.getEntityType(), actionType)) { |
|||
JsonNode actionData = constructActionData(entityId, entity, actionType, additionalInfo); |
|||
ActionStatus actionStatus = ActionStatus.SUCCESS; |
|||
String failureDetails = ""; |
|||
String entityName = ""; |
|||
if (entity != null) { |
|||
entityName = entity.getName(); |
|||
} else { |
|||
try { |
|||
entityName = entityService.fetchEntityNameAsync(entityId).get(); |
|||
} catch (Exception ex) {} |
|||
} |
|||
if (e != null) { |
|||
actionStatus = ActionStatus.FAILURE; |
|||
failureDetails = getFailureStack(e); |
|||
} |
|||
if (actionType == ActionType.RPC_CALL) { |
|||
String rpcErrorString = extractParameter(String.class, additionalInfo); |
|||
if (!StringUtils.isEmpty(rpcErrorString)) { |
|||
actionStatus = ActionStatus.FAILURE; |
|||
failureDetails = rpcErrorString; |
|||
} |
|||
} |
|||
return logAction(tenantId, |
|||
entityId, |
|||
entityName, |
|||
customerId, |
|||
userId, |
|||
userName, |
|||
actionType, |
|||
actionData, |
|||
actionStatus, |
|||
failureDetails); |
|||
} else { |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
private <E extends BaseData<I> & HasName, I extends UUIDBased & EntityId> JsonNode constructActionData(I entityId, |
|||
E entity, |
|||
ActionType actionType, |
|||
Object... additionalInfo) { |
|||
ObjectNode actionData = objectMapper.createObjectNode(); |
|||
switch(actionType) { |
|||
case ADDED: |
|||
case UPDATED: |
|||
ObjectNode entityNode = objectMapper.valueToTree(entity); |
|||
if (entityId.getEntityType() == EntityType.DASHBOARD) { |
|||
entityNode.put("configuration", ""); |
|||
} |
|||
actionData.set("entity", entityNode); |
|||
break; |
|||
case DELETED: |
|||
case ACTIVATED: |
|||
case SUSPENDED: |
|||
case CREDENTIALS_READ: |
|||
String strEntityId = extractParameter(String.class, additionalInfo); |
|||
actionData.put("entityId", strEntityId); |
|||
break; |
|||
case ATTRIBUTES_UPDATED: |
|||
actionData.put("entityId", entityId.toString()); |
|||
String scope = extractParameter(String.class, 0, additionalInfo); |
|||
List<AttributeKvEntry> attributes = extractParameter(List.class, 1, additionalInfo); |
|||
actionData.put("scope", scope); |
|||
ObjectNode attrsNode = objectMapper.createObjectNode(); |
|||
if (attributes != null) { |
|||
for (AttributeKvEntry attr : attributes) { |
|||
attrsNode.put(attr.getKey(), attr.getValueAsString()); |
|||
} |
|||
} |
|||
actionData.set("attributes", attrsNode); |
|||
break; |
|||
case ATTRIBUTES_DELETED: |
|||
case ATTRIBUTES_READ: |
|||
actionData.put("entityId", entityId.toString()); |
|||
scope = extractParameter(String.class, 0, additionalInfo); |
|||
actionData.put("scope", scope); |
|||
List<String> keys = extractParameter(List.class, 1, additionalInfo); |
|||
ArrayNode attrsArrayNode = actionData.putArray("attributes"); |
|||
if (keys != null) { |
|||
keys.forEach(attrsArrayNode::add); |
|||
} |
|||
break; |
|||
case RPC_CALL: |
|||
actionData.put("entityId", entityId.toString()); |
|||
Boolean oneWay = extractParameter(Boolean.class, 1, additionalInfo); |
|||
String method = extractParameter(String.class, 2, additionalInfo); |
|||
String params = extractParameter(String.class, 3, additionalInfo); |
|||
actionData.put("oneWay", oneWay); |
|||
actionData.put("method", method); |
|||
actionData.put("params", params); |
|||
break; |
|||
case CREDENTIALS_UPDATED: |
|||
actionData.put("entityId", entityId.toString()); |
|||
DeviceCredentials deviceCredentials = extractParameter(DeviceCredentials.class, additionalInfo); |
|||
actionData.set("credentials", objectMapper.valueToTree(deviceCredentials)); |
|||
break; |
|||
case ASSIGNED_TO_CUSTOMER: |
|||
strEntityId = extractParameter(String.class, 0, additionalInfo); |
|||
String strCustomerId = extractParameter(String.class, 1, additionalInfo); |
|||
String strCustomerName = extractParameter(String.class, 2, additionalInfo); |
|||
actionData.put("entityId", strEntityId); |
|||
actionData.put("assignedCustomerId", strCustomerId); |
|||
actionData.put("assignedCustomerName", strCustomerName); |
|||
break; |
|||
case UNASSIGNED_FROM_CUSTOMER: |
|||
strEntityId = extractParameter(String.class, 0, additionalInfo); |
|||
strCustomerId = extractParameter(String.class, 1, additionalInfo); |
|||
strCustomerName = extractParameter(String.class, 2, additionalInfo); |
|||
actionData.put("entityId", strEntityId); |
|||
actionData.put("unassignedCustomerId", strCustomerId); |
|||
actionData.put("unassignedCustomerName", strCustomerName); |
|||
break; |
|||
} |
|||
return actionData; |
|||
} |
|||
|
|||
private <T> T extractParameter(Class<T> clazz, Object... additionalInfo) { |
|||
return extractParameter(clazz, 0, additionalInfo); |
|||
} |
|||
|
|||
private <T> T extractParameter(Class<T> clazz, int index, Object... additionalInfo) { |
|||
T result = null; |
|||
if (additionalInfo != null && additionalInfo.length > index) { |
|||
Object paramObject = additionalInfo[index]; |
|||
if (clazz.isInstance(paramObject)) { |
|||
result = clazz.cast(paramObject); |
|||
} |
|||
} |
|||
return result; |
|||
} |
|||
|
|||
private String getFailureStack(Exception e) { |
|||
StringWriter sw = new StringWriter(); |
|||
e.printStackTrace(new PrintWriter(sw)); |
|||
return sw.toString(); |
|||
} |
|||
|
|||
private boolean canLog(EntityType entityType, ActionType actionType) { |
|||
return auditLogLevelFilter.logEnabled(entityType, actionType); |
|||
} |
|||
|
|||
private AuditLog createAuditLogEntry(TenantId tenantId, |
|||
EntityId entityId, |
|||
String entityName, |
|||
CustomerId customerId, |
|||
UserId userId, |
|||
String userName, |
|||
ActionType actionType, |
|||
JsonNode actionData, |
|||
ActionStatus actionStatus, |
|||
String actionFailureDetails) { |
|||
AuditLog result = new AuditLog(); |
|||
result.setId(new AuditLogId(UUIDs.timeBased())); |
|||
result.setTenantId(tenantId); |
|||
result.setEntityId(entityId); |
|||
result.setEntityName(entityName); |
|||
result.setCustomerId(customerId); |
|||
result.setUserId(userId); |
|||
result.setUserName(userName); |
|||
result.setActionType(actionType); |
|||
result.setActionData(actionData); |
|||
result.setActionStatus(actionStatus); |
|||
result.setActionFailureDetails(actionFailureDetails); |
|||
return result; |
|||
} |
|||
|
|||
private ListenableFuture<List<Void>> logAction(TenantId tenantId, |
|||
EntityId entityId, |
|||
String entityName, |
|||
CustomerId customerId, |
|||
UserId userId, |
|||
String userName, |
|||
ActionType actionType, |
|||
JsonNode actionData, |
|||
ActionStatus actionStatus, |
|||
String actionFailureDetails) { |
|||
AuditLog auditLogEntry = createAuditLogEntry(tenantId, entityId, entityName, customerId, userId, userName, |
|||
actionType, actionData, actionStatus, actionFailureDetails); |
|||
log.trace("Executing logAction [{}]", auditLogEntry); |
|||
auditLogValidator.validate(auditLogEntry); |
|||
List<ListenableFuture<Void>> futures = Lists.newArrayListWithExpectedSize(INSERTS_PER_ENTRY); |
|||
futures.add(auditLogDao.savePartitionsByTenantId(auditLogEntry)); |
|||
futures.add(auditLogDao.saveByTenantId(auditLogEntry)); |
|||
futures.add(auditLogDao.saveByTenantIdAndEntityId(auditLogEntry)); |
|||
futures.add(auditLogDao.saveByTenantIdAndCustomerId(auditLogEntry)); |
|||
futures.add(auditLogDao.saveByTenantIdAndUserId(auditLogEntry)); |
|||
return Futures.allAsList(futures); |
|||
} |
|||
|
|||
private DataValidator<AuditLog> auditLogValidator = |
|||
new DataValidator<AuditLog>() { |
|||
@Override |
|||
protected void validateDataImpl(AuditLog auditLog) { |
|||
if (auditLog.getEntityId() == null) { |
|||
throw new DataValidationException("Entity Id should be specified!"); |
|||
} |
|||
if (auditLog.getTenantId() == null) { |
|||
throw new DataValidationException("Tenant Id should be specified!"); |
|||
} |
|||
if (auditLog.getUserId() == null) { |
|||
throw new DataValidationException("User Id should be specified!"); |
|||
} |
|||
} |
|||
}; |
|||
} |
|||
@ -0,0 +1,349 @@ |
|||
/** |
|||
* Copyright © 2016-2017 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.dao.audit; |
|||
|
|||
import com.datastax.driver.core.BoundStatement; |
|||
import com.datastax.driver.core.PreparedStatement; |
|||
import com.datastax.driver.core.ResultSet; |
|||
import com.datastax.driver.core.ResultSetFuture; |
|||
import com.datastax.driver.core.querybuilder.QueryBuilder; |
|||
import com.datastax.driver.core.querybuilder.Select; |
|||
import com.google.common.base.Function; |
|||
import com.google.common.util.concurrent.Futures; |
|||
import com.google.common.util.concurrent.ListenableFuture; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.beans.factory.annotation.Value; |
|||
import org.springframework.core.env.Environment; |
|||
import org.springframework.stereotype.Component; |
|||
import org.thingsboard.server.common.data.audit.AuditLog; |
|||
import org.thingsboard.server.common.data.id.CustomerId; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.data.id.UserId; |
|||
import org.thingsboard.server.common.data.page.TimePageLink; |
|||
import org.thingsboard.server.dao.DaoUtil; |
|||
import org.thingsboard.server.dao.model.ModelConstants; |
|||
import org.thingsboard.server.dao.model.nosql.AuditLogEntity; |
|||
import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTimeDao; |
|||
import org.thingsboard.server.dao.timeseries.TsPartitionDate; |
|||
import org.thingsboard.server.dao.util.NoSqlDao; |
|||
|
|||
import javax.annotation.Nullable; |
|||
import javax.annotation.PostConstruct; |
|||
import javax.annotation.PreDestroy; |
|||
import java.time.Instant; |
|||
import java.time.LocalDate; |
|||
import java.time.LocalDateTime; |
|||
import java.time.ZoneOffset; |
|||
import java.util.*; |
|||
import java.util.concurrent.ExecutorService; |
|||
import java.util.concurrent.Executors; |
|||
import java.util.stream.Collectors; |
|||
|
|||
import static com.datastax.driver.core.querybuilder.QueryBuilder.eq; |
|||
import static org.thingsboard.server.dao.model.ModelConstants.*; |
|||
|
|||
@Component |
|||
@Slf4j |
|||
@NoSqlDao |
|||
public class CassandraAuditLogDao extends CassandraAbstractSearchTimeDao<AuditLogEntity, AuditLog> implements AuditLogDao { |
|||
|
|||
private static final String INSERT_INTO = "INSERT INTO "; |
|||
|
|||
@Autowired |
|||
private Environment environment; |
|||
|
|||
@Override |
|||
protected Class<AuditLogEntity> getColumnFamilyClass() { |
|||
return AuditLogEntity.class; |
|||
} |
|||
|
|||
@Override |
|||
protected String getColumnFamilyName() { |
|||
return AUDIT_LOG_COLUMN_FAMILY_NAME; |
|||
} |
|||
|
|||
protected ExecutorService readResultsProcessingExecutor; |
|||
|
|||
@Value("${audit_log.by_tenant_partitioning}") |
|||
private String partitioning; |
|||
private TsPartitionDate tsFormat; |
|||
|
|||
@Value("${audit_log.default_query_period}") |
|||
private Integer defaultQueryPeriodInDays; |
|||
|
|||
private PreparedStatement partitionInsertStmt; |
|||
private PreparedStatement saveByTenantStmt; |
|||
private PreparedStatement saveByTenantIdAndUserIdStmt; |
|||
private PreparedStatement saveByTenantIdAndEntityIdStmt; |
|||
private PreparedStatement saveByTenantIdAndCustomerIdStmt; |
|||
|
|||
private boolean isInstall() { |
|||
return environment.acceptsProfiles("install"); |
|||
} |
|||
|
|||
@PostConstruct |
|||
public void init() { |
|||
if (!isInstall()) { |
|||
Optional<TsPartitionDate> partition = TsPartitionDate.parse(partitioning); |
|||
if (partition.isPresent()) { |
|||
tsFormat = partition.get(); |
|||
} else { |
|||
log.warn("Incorrect configuration of partitioning {}", partitioning); |
|||
throw new RuntimeException("Failed to parse partitioning property: " + partitioning + "!"); |
|||
} |
|||
} |
|||
readResultsProcessingExecutor = Executors.newCachedThreadPool(); |
|||
} |
|||
|
|||
@PreDestroy |
|||
public void stopExecutor() { |
|||
if (readResultsProcessingExecutor != null) { |
|||
readResultsProcessingExecutor.shutdownNow(); |
|||
} |
|||
} |
|||
|
|||
private <T> ListenableFuture<T> getFuture(ResultSetFuture future, java.util.function.Function<ResultSet, T> transformer) { |
|||
return Futures.transform(future, new Function<ResultSet, T>() { |
|||
@Nullable |
|||
@Override |
|||
public T apply(@Nullable ResultSet input) { |
|||
return transformer.apply(input); |
|||
} |
|||
}, readResultsProcessingExecutor); |
|||
} |
|||
|
|||
@Override |
|||
public ListenableFuture<Void> saveByTenantId(AuditLog auditLog) { |
|||
log.debug("Save saveByTenantId [{}] ", auditLog); |
|||
|
|||
long partition = toPartitionTs(LocalDate.now().atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli()); |
|||
BoundStatement stmt = getSaveByTenantStmt().bind(); |
|||
stmt = setSaveStmtVariables(stmt, auditLog, partition); |
|||
return getFuture(executeAsyncWrite(stmt), rs -> null); |
|||
} |
|||
|
|||
@Override |
|||
public ListenableFuture<Void> saveByTenantIdAndEntityId(AuditLog auditLog) { |
|||
log.debug("Save saveByTenantIdAndEntityId [{}] ", auditLog); |
|||
|
|||
BoundStatement stmt = getSaveByTenantIdAndEntityIdStmt().bind(); |
|||
stmt = setSaveStmtVariables(stmt, auditLog, -1); |
|||
return getFuture(executeAsyncWrite(stmt), rs -> null); |
|||
} |
|||
|
|||
@Override |
|||
public ListenableFuture<Void> saveByTenantIdAndCustomerId(AuditLog auditLog) { |
|||
log.debug("Save saveByTenantIdAndCustomerId [{}] ", auditLog); |
|||
|
|||
BoundStatement stmt = getSaveByTenantIdAndCustomerIdStmt().bind(); |
|||
stmt = setSaveStmtVariables(stmt, auditLog, -1); |
|||
return getFuture(executeAsyncWrite(stmt), rs -> null); |
|||
} |
|||
|
|||
@Override |
|||
public ListenableFuture<Void> saveByTenantIdAndUserId(AuditLog auditLog) { |
|||
log.debug("Save saveByTenantIdAndUserId [{}] ", auditLog); |
|||
|
|||
BoundStatement stmt = getSaveByTenantIdAndUserIdStmt().bind(); |
|||
stmt = setSaveStmtVariables(stmt, auditLog, -1); |
|||
return getFuture(executeAsyncWrite(stmt), rs -> null); |
|||
} |
|||
|
|||
private BoundStatement setSaveStmtVariables(BoundStatement stmt, AuditLog auditLog, long partition) { |
|||
stmt.setUUID(0, auditLog.getId().getId()) |
|||
.setUUID(1, auditLog.getTenantId().getId()) |
|||
.setUUID(2, auditLog.getCustomerId().getId()) |
|||
.setUUID(3, auditLog.getEntityId().getId()) |
|||
.setString(4, auditLog.getEntityId().getEntityType().name()) |
|||
.setString(5, auditLog.getEntityName()) |
|||
.setUUID(6, auditLog.getUserId().getId()) |
|||
.setString(7, auditLog.getUserName()) |
|||
.setString(8, auditLog.getActionType().name()) |
|||
.setString(9, auditLog.getActionData() != null ? auditLog.getActionData().toString() : null) |
|||
.setString(10, auditLog.getActionStatus().name()) |
|||
.setString(11, auditLog.getActionFailureDetails()); |
|||
if (partition > -1) { |
|||
stmt.setLong(12, partition); |
|||
} |
|||
return stmt; |
|||
} |
|||
|
|||
@Override |
|||
public ListenableFuture<Void> savePartitionsByTenantId(AuditLog auditLog) { |
|||
log.debug("Save savePartitionsByTenantId [{}] ", auditLog); |
|||
|
|||
long partition = toPartitionTs(LocalDate.now().atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli()); |
|||
|
|||
BoundStatement stmt = getPartitionInsertStmt().bind(); |
|||
stmt = stmt.setUUID(0, auditLog.getTenantId().getId()) |
|||
.setLong(1, partition); |
|||
return getFuture(executeAsyncWrite(stmt), rs -> null); |
|||
} |
|||
|
|||
private PreparedStatement getSaveByTenantStmt() { |
|||
if (saveByTenantStmt == null) { |
|||
saveByTenantStmt = getSaveByTenantIdAndCFName(ModelConstants.AUDIT_LOG_BY_TENANT_ID_CF, true); |
|||
} |
|||
return saveByTenantStmt; |
|||
} |
|||
|
|||
private PreparedStatement getSaveByTenantIdAndEntityIdStmt() { |
|||
if (saveByTenantIdAndEntityIdStmt == null) { |
|||
saveByTenantIdAndEntityIdStmt = getSaveByTenantIdAndCFName(ModelConstants.AUDIT_LOG_BY_ENTITY_ID_CF, false); |
|||
} |
|||
return saveByTenantIdAndEntityIdStmt; |
|||
} |
|||
|
|||
private PreparedStatement getSaveByTenantIdAndCustomerIdStmt() { |
|||
if (saveByTenantIdAndCustomerIdStmt == null) { |
|||
saveByTenantIdAndCustomerIdStmt = getSaveByTenantIdAndCFName(ModelConstants.AUDIT_LOG_BY_CUSTOMER_ID_CF, false); |
|||
} |
|||
return saveByTenantIdAndCustomerIdStmt; |
|||
} |
|||
|
|||
private PreparedStatement getSaveByTenantIdAndUserIdStmt() { |
|||
if (saveByTenantIdAndUserIdStmt == null) { |
|||
saveByTenantIdAndUserIdStmt = getSaveByTenantIdAndCFName(ModelConstants.AUDIT_LOG_BY_USER_ID_CF, false); |
|||
} |
|||
return saveByTenantIdAndUserIdStmt; |
|||
} |
|||
|
|||
private PreparedStatement getSaveByTenantIdAndCFName(String cfName, boolean hasPartition) { |
|||
List columnsList = new ArrayList(); |
|||
columnsList.add(ModelConstants.AUDIT_LOG_ID_PROPERTY); |
|||
columnsList.add(ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY); |
|||
columnsList.add(ModelConstants.AUDIT_LOG_CUSTOMER_ID_PROPERTY); |
|||
columnsList.add(ModelConstants.AUDIT_LOG_ENTITY_ID_PROPERTY); |
|||
columnsList.add(ModelConstants.AUDIT_LOG_ENTITY_TYPE_PROPERTY); |
|||
columnsList.add(ModelConstants.AUDIT_LOG_ENTITY_NAME_PROPERTY); |
|||
columnsList.add(ModelConstants.AUDIT_LOG_USER_ID_PROPERTY); |
|||
columnsList.add(ModelConstants.AUDIT_LOG_USER_NAME_PROPERTY); |
|||
columnsList.add(ModelConstants.AUDIT_LOG_ACTION_TYPE_PROPERTY); |
|||
columnsList.add(ModelConstants.AUDIT_LOG_ACTION_DATA_PROPERTY); |
|||
columnsList.add(ModelConstants.AUDIT_LOG_ACTION_STATUS_PROPERTY); |
|||
columnsList.add(ModelConstants.AUDIT_LOG_ACTION_FAILURE_DETAILS_PROPERTY); |
|||
if (hasPartition) { |
|||
columnsList.add(ModelConstants.AUDIT_LOG_PARTITION_PROPERTY); |
|||
} |
|||
StringJoiner values = new StringJoiner(","); |
|||
for (int i=0;i<columnsList.size();i++) { |
|||
values.add("?"); |
|||
} |
|||
String statementString = INSERT_INTO + cfName + " (" + String.join(",", columnsList) + ") VALUES (" + values.toString() + ")"; |
|||
return getSession().prepare(statementString); |
|||
} |
|||
|
|||
private PreparedStatement getPartitionInsertStmt() { |
|||
if (partitionInsertStmt == null) { |
|||
partitionInsertStmt = getSession().prepare(INSERT_INTO + ModelConstants.AUDIT_LOG_BY_TENANT_ID_PARTITIONS_CF + |
|||
"(" + ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY + |
|||
"," + ModelConstants.AUDIT_LOG_PARTITION_PROPERTY + ")" + |
|||
" VALUES(?, ?)"); |
|||
} |
|||
return partitionInsertStmt; |
|||
} |
|||
|
|||
private long toPartitionTs(long ts) { |
|||
LocalDateTime time = LocalDateTime.ofInstant(Instant.ofEpochMilli(ts), ZoneOffset.UTC); |
|||
return tsFormat.truncatedTo(time).toInstant(ZoneOffset.UTC).toEpochMilli(); |
|||
} |
|||
|
|||
@Override |
|||
public List<AuditLog> findAuditLogsByTenantIdAndEntityId(UUID tenantId, EntityId entityId, TimePageLink pageLink) { |
|||
log.trace("Try to find audit logs by tenant [{}], entity [{}] and pageLink [{}]", tenantId, entityId, pageLink); |
|||
List<AuditLogEntity> entities = findPageWithTimeSearch(AUDIT_LOG_BY_ENTITY_ID_CF, |
|||
Arrays.asList(eq(ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY, tenantId), |
|||
eq(ModelConstants.AUDIT_LOG_ENTITY_TYPE_PROPERTY, entityId.getEntityType()), |
|||
eq(ModelConstants.AUDIT_LOG_ENTITY_ID_PROPERTY, entityId.getId())), |
|||
pageLink); |
|||
log.trace("Found audit logs by tenant [{}], entity [{}] and pageLink [{}]", tenantId, entityId, pageLink); |
|||
return DaoUtil.convertDataList(entities); |
|||
} |
|||
|
|||
@Override |
|||
public List<AuditLog> findAuditLogsByTenantIdAndCustomerId(UUID tenantId, CustomerId customerId, TimePageLink pageLink) { |
|||
log.trace("Try to find audit logs by tenant [{}], customer [{}] and pageLink [{}]", tenantId, customerId, pageLink); |
|||
List<AuditLogEntity> entities = findPageWithTimeSearch(AUDIT_LOG_BY_CUSTOMER_ID_CF, |
|||
Arrays.asList(eq(ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY, tenantId), |
|||
eq(ModelConstants.AUDIT_LOG_CUSTOMER_ID_PROPERTY, customerId.getId())), |
|||
pageLink); |
|||
log.trace("Found audit logs by tenant [{}], customer [{}] and pageLink [{}]", tenantId, customerId, pageLink); |
|||
return DaoUtil.convertDataList(entities); |
|||
} |
|||
|
|||
@Override |
|||
public List<AuditLog> findAuditLogsByTenantIdAndUserId(UUID tenantId, UserId userId, TimePageLink pageLink) { |
|||
log.trace("Try to find audit logs by tenant [{}], user [{}] and pageLink [{}]", tenantId, userId, pageLink); |
|||
List<AuditLogEntity> entities = findPageWithTimeSearch(AUDIT_LOG_BY_USER_ID_CF, |
|||
Arrays.asList(eq(ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY, tenantId), |
|||
eq(ModelConstants.AUDIT_LOG_USER_ID_PROPERTY, userId.getId())), |
|||
pageLink); |
|||
log.trace("Found audit logs by tenant [{}], user [{}] and pageLink [{}]", tenantId, userId, pageLink); |
|||
return DaoUtil.convertDataList(entities); |
|||
} |
|||
|
|||
@Override |
|||
public List<AuditLog> findAuditLogsByTenantId(UUID tenantId, TimePageLink pageLink) { |
|||
log.trace("Try to find audit logs by tenant [{}] and pageLink [{}]", tenantId, pageLink); |
|||
|
|||
long minPartition; |
|||
if (pageLink.getStartTime() != null && pageLink.getStartTime() != 0) { |
|||
minPartition = toPartitionTs(pageLink.getStartTime()); |
|||
} else { |
|||
minPartition = toPartitionTs(LocalDate.now().minusDays(defaultQueryPeriodInDays).atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli()); |
|||
} |
|||
|
|||
long maxPartition; |
|||
if (pageLink.getEndTime() != null && pageLink.getEndTime() != 0) { |
|||
maxPartition = toPartitionTs(pageLink.getEndTime()); |
|||
} else { |
|||
maxPartition = toPartitionTs(LocalDate.now().atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli()); |
|||
} |
|||
|
|||
List<Long> partitions = fetchPartitions(tenantId, minPartition, maxPartition) |
|||
.all() |
|||
.stream() |
|||
.map(row -> row.getLong(ModelConstants.PARTITION_COLUMN)) |
|||
.collect(Collectors.toList()); |
|||
|
|||
AuditLogQueryCursor cursor = new AuditLogQueryCursor(tenantId, pageLink, partitions); |
|||
List<AuditLogEntity> entities = fetchSequentiallyWithLimit(cursor); |
|||
log.trace("Found audit logs by tenant [{}] and pageLink [{}]", tenantId, pageLink); |
|||
return DaoUtil.convertDataList(entities); |
|||
} |
|||
|
|||
private List<AuditLogEntity> fetchSequentiallyWithLimit(AuditLogQueryCursor cursor) { |
|||
if (cursor.isFull() || !cursor.hasNextPartition()) { |
|||
return cursor.getData(); |
|||
} else { |
|||
cursor.addData(findPageWithTimeSearch(AUDIT_LOG_BY_TENANT_ID_CF, |
|||
Arrays.asList(eq(ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY, cursor.getTenantId()), |
|||
eq(ModelConstants.AUDIT_LOG_PARTITION_PROPERTY, cursor.getNextPartition())), |
|||
cursor.getPageLink())); |
|||
return fetchSequentiallyWithLimit(cursor); |
|||
} |
|||
} |
|||
|
|||
private ResultSet fetchPartitions(UUID tenantId, long minPartition, long maxPartition) { |
|||
Select.Where select = QueryBuilder.select(ModelConstants.AUDIT_LOG_PARTITION_PROPERTY).from(ModelConstants.AUDIT_LOG_BY_TENANT_ID_PARTITIONS_CF) |
|||
.where(eq(ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY, tenantId)); |
|||
select.and(QueryBuilder.gte(ModelConstants.PARTITION_COLUMN, minPartition)); |
|||
select.and(QueryBuilder.lte(ModelConstants.PARTITION_COLUMN, maxPartition)); |
|||
return getSession().execute(select); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,63 @@ |
|||
/** |
|||
* Copyright © 2016-2017 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.dao.audit; |
|||
|
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import com.google.common.util.concurrent.Futures; |
|||
import com.google.common.util.concurrent.ListenableFuture; |
|||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
|||
import org.thingsboard.server.common.data.BaseData; |
|||
import org.thingsboard.server.common.data.HasName; |
|||
import org.thingsboard.server.common.data.User; |
|||
import org.thingsboard.server.common.data.audit.ActionStatus; |
|||
import org.thingsboard.server.common.data.audit.ActionType; |
|||
import org.thingsboard.server.common.data.audit.AuditLog; |
|||
import org.thingsboard.server.common.data.id.*; |
|||
import org.thingsboard.server.common.data.page.TimePageData; |
|||
import org.thingsboard.server.common.data.page.TimePageLink; |
|||
|
|||
import java.util.Collections; |
|||
import java.util.List; |
|||
|
|||
@ConditionalOnProperty(prefix = "audit_log", value = "enabled", havingValue = "false") |
|||
public class DummyAuditLogServiceImpl implements AuditLogService { |
|||
|
|||
@Override |
|||
public TimePageData<AuditLog> findAuditLogsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TimePageLink pageLink) { |
|||
return new TimePageData<>(null, pageLink); |
|||
} |
|||
|
|||
@Override |
|||
public TimePageData<AuditLog> findAuditLogsByTenantIdAndUserId(TenantId tenantId, UserId userId, TimePageLink pageLink) { |
|||
return new TimePageData<>(null, pageLink); |
|||
} |
|||
|
|||
@Override |
|||
public TimePageData<AuditLog> findAuditLogsByTenantIdAndEntityId(TenantId tenantId, EntityId entityId, TimePageLink pageLink) { |
|||
return new TimePageData<>(null, pageLink); |
|||
} |
|||
|
|||
@Override |
|||
public TimePageData<AuditLog> findAuditLogsByTenantId(TenantId tenantId, TimePageLink pageLink) { |
|||
return new TimePageData<>(null, pageLink); |
|||
} |
|||
|
|||
@Override |
|||
public <E extends BaseData<I> & HasName, I extends UUIDBased & EntityId> ListenableFuture<List<Void>> logEntityAction(TenantId tenantId, CustomerId customerId, UserId userId, String userName, I entityId, E entity, ActionType actionType, Exception e, Object... additionalInfo) { |
|||
return null; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,139 @@ |
|||
/** |
|||
* Copyright © 2016-2017 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.dao.model.nosql; |
|||
|
|||
import com.datastax.driver.core.utils.UUIDs; |
|||
import com.datastax.driver.mapping.annotations.Column; |
|||
import com.datastax.driver.mapping.annotations.Table; |
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import lombok.Data; |
|||
import lombok.NoArgsConstructor; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
import org.thingsboard.server.common.data.audit.ActionStatus; |
|||
import org.thingsboard.server.common.data.audit.ActionType; |
|||
import org.thingsboard.server.common.data.audit.AuditLog; |
|||
import org.thingsboard.server.common.data.id.*; |
|||
import org.thingsboard.server.dao.model.BaseEntity; |
|||
import org.thingsboard.server.dao.model.type.ActionStatusCodec; |
|||
import org.thingsboard.server.dao.model.type.ActionTypeCodec; |
|||
import org.thingsboard.server.dao.model.type.EntityTypeCodec; |
|||
import org.thingsboard.server.dao.model.type.JsonCodec; |
|||
|
|||
import java.util.UUID; |
|||
|
|||
import static org.thingsboard.server.dao.model.ModelConstants.*; |
|||
|
|||
@Table(name = AUDIT_LOG_COLUMN_FAMILY_NAME) |
|||
@Data |
|||
@NoArgsConstructor |
|||
public class AuditLogEntity implements BaseEntity<AuditLog> { |
|||
|
|||
@Column(name = ID_PROPERTY) |
|||
private UUID id; |
|||
|
|||
@Column(name = AUDIT_LOG_TENANT_ID_PROPERTY) |
|||
private UUID tenantId; |
|||
|
|||
@Column(name = AUDIT_LOG_CUSTOMER_ID_PROPERTY) |
|||
private UUID customerId; |
|||
|
|||
@Column(name = AUDIT_LOG_ENTITY_TYPE_PROPERTY, codec = EntityTypeCodec.class) |
|||
private EntityType entityType; |
|||
|
|||
@Column(name = AUDIT_LOG_ENTITY_ID_PROPERTY) |
|||
private UUID entityId; |
|||
|
|||
@Column(name = AUDIT_LOG_ENTITY_NAME_PROPERTY) |
|||
private String entityName; |
|||
|
|||
@Column(name = AUDIT_LOG_USER_ID_PROPERTY) |
|||
private UUID userId; |
|||
|
|||
@Column(name = AUDIT_LOG_USER_NAME_PROPERTY) |
|||
private String userName; |
|||
|
|||
@Column(name = AUDIT_LOG_ACTION_TYPE_PROPERTY, codec = ActionTypeCodec.class) |
|||
private ActionType actionType; |
|||
|
|||
@Column(name = AUDIT_LOG_ACTION_DATA_PROPERTY, codec = JsonCodec.class) |
|||
private JsonNode actionData; |
|||
|
|||
@Column(name = AUDIT_LOG_ACTION_STATUS_PROPERTY, codec = ActionStatusCodec.class) |
|||
private ActionStatus actionStatus; |
|||
|
|||
@Column(name = AUDIT_LOG_ACTION_FAILURE_DETAILS_PROPERTY) |
|||
private String actionFailureDetails; |
|||
|
|||
@Override |
|||
public UUID getId() { |
|||
return id; |
|||
} |
|||
|
|||
@Override |
|||
public void setId(UUID id) { |
|||
this.id = id; |
|||
} |
|||
|
|||
public AuditLogEntity(AuditLog auditLog) { |
|||
if (auditLog.getId() != null) { |
|||
this.id = auditLog.getId().getId(); |
|||
} |
|||
if (auditLog.getTenantId() != null) { |
|||
this.tenantId = auditLog.getTenantId().getId(); |
|||
} |
|||
if (auditLog.getEntityId() != null) { |
|||
this.entityType = auditLog.getEntityId().getEntityType(); |
|||
this.entityId = auditLog.getEntityId().getId(); |
|||
} |
|||
if (auditLog.getCustomerId() != null) { |
|||
this.customerId = auditLog.getCustomerId().getId(); |
|||
} |
|||
if (auditLog.getUserId() != null) { |
|||
this.userId = auditLog.getUserId().getId(); |
|||
} |
|||
this.entityName = auditLog.getEntityName(); |
|||
this.userName = auditLog.getUserName(); |
|||
this.actionType = auditLog.getActionType(); |
|||
this.actionData = auditLog.getActionData(); |
|||
this.actionStatus = auditLog.getActionStatus(); |
|||
this.actionFailureDetails = auditLog.getActionFailureDetails(); |
|||
} |
|||
|
|||
@Override |
|||
public AuditLog toData() { |
|||
AuditLog auditLog = new AuditLog(new AuditLogId(id)); |
|||
auditLog.setCreatedTime(UUIDs.unixTimestamp(id)); |
|||
if (tenantId != null) { |
|||
auditLog.setTenantId(new TenantId(tenantId)); |
|||
} |
|||
if (entityId != null && entityType != null) { |
|||
auditLog.setEntityId(EntityIdFactory.getByTypeAndUuid(entityType, entityId)); |
|||
} |
|||
if (customerId != null) { |
|||
auditLog.setCustomerId(new CustomerId(customerId)); |
|||
} |
|||
if (userId != null) { |
|||
auditLog.setUserId(new UserId(userId)); |
|||
} |
|||
auditLog.setEntityName(this.entityName); |
|||
auditLog.setUserName(this.userName); |
|||
auditLog.setActionType(this.actionType); |
|||
auditLog.setActionData(this.actionData); |
|||
auditLog.setActionStatus(this.actionStatus); |
|||
auditLog.setActionFailureDetails(this.actionFailureDetails); |
|||
return auditLog; |
|||
} |
|||
} |
|||
@ -0,0 +1,135 @@ |
|||
/** |
|||
* Copyright © 2016-2017 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.dao.model.sql; |
|||
|
|||
import com.datastax.driver.core.utils.UUIDs; |
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import lombok.Data; |
|||
import lombok.EqualsAndHashCode; |
|||
import org.hibernate.annotations.Type; |
|||
import org.hibernate.annotations.TypeDef; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
import org.thingsboard.server.common.data.audit.ActionStatus; |
|||
import org.thingsboard.server.common.data.audit.ActionType; |
|||
import org.thingsboard.server.common.data.audit.AuditLog; |
|||
import org.thingsboard.server.common.data.id.*; |
|||
import org.thingsboard.server.dao.model.BaseEntity; |
|||
import org.thingsboard.server.dao.model.BaseSqlEntity; |
|||
import org.thingsboard.server.dao.model.ModelConstants; |
|||
import org.thingsboard.server.dao.util.mapping.JsonStringType; |
|||
|
|||
import javax.persistence.*; |
|||
|
|||
import static org.thingsboard.server.dao.model.ModelConstants.*; |
|||
|
|||
@Data |
|||
@EqualsAndHashCode(callSuper = true) |
|||
@Entity |
|||
@TypeDef(name = "json", typeClass = JsonStringType.class) |
|||
@Table(name = ModelConstants.AUDIT_LOG_COLUMN_FAMILY_NAME) |
|||
public class AuditLogEntity extends BaseSqlEntity<AuditLog> implements BaseEntity<AuditLog> { |
|||
|
|||
@Column(name = AUDIT_LOG_TENANT_ID_PROPERTY) |
|||
private String tenantId; |
|||
|
|||
@Column(name = AUDIT_LOG_CUSTOMER_ID_PROPERTY) |
|||
private String customerId; |
|||
|
|||
@Enumerated(EnumType.STRING) |
|||
@Column(name = AUDIT_LOG_ENTITY_TYPE_PROPERTY) |
|||
private EntityType entityType; |
|||
|
|||
@Column(name = AUDIT_LOG_ENTITY_ID_PROPERTY) |
|||
private String entityId; |
|||
|
|||
@Column(name = AUDIT_LOG_ENTITY_NAME_PROPERTY) |
|||
private String entityName; |
|||
|
|||
@Column(name = AUDIT_LOG_USER_ID_PROPERTY) |
|||
private String userId; |
|||
|
|||
@Column(name = AUDIT_LOG_USER_NAME_PROPERTY) |
|||
private String userName; |
|||
|
|||
@Enumerated(EnumType.STRING) |
|||
@Column(name = AUDIT_LOG_ACTION_TYPE_PROPERTY) |
|||
private ActionType actionType; |
|||
|
|||
@Type(type = "json") |
|||
@Column(name = AUDIT_LOG_ACTION_DATA_PROPERTY) |
|||
private JsonNode actionData; |
|||
|
|||
@Enumerated(EnumType.STRING) |
|||
@Column(name = AUDIT_LOG_ACTION_STATUS_PROPERTY) |
|||
private ActionStatus actionStatus; |
|||
|
|||
@Column(name = AUDIT_LOG_ACTION_FAILURE_DETAILS_PROPERTY) |
|||
private String actionFailureDetails; |
|||
|
|||
public AuditLogEntity() { |
|||
super(); |
|||
} |
|||
|
|||
public AuditLogEntity(AuditLog auditLog) { |
|||
if (auditLog.getId() != null) { |
|||
this.setId(auditLog.getId().getId()); |
|||
} |
|||
if (auditLog.getTenantId() != null) { |
|||
this.tenantId = toString(auditLog.getTenantId().getId()); |
|||
} |
|||
if (auditLog.getCustomerId() != null) { |
|||
this.customerId = toString(auditLog.getCustomerId().getId()); |
|||
} |
|||
if (auditLog.getEntityId() != null) { |
|||
this.entityId = toString(auditLog.getEntityId().getId()); |
|||
this.entityType = auditLog.getEntityId().getEntityType(); |
|||
} |
|||
if (auditLog.getUserId() != null) { |
|||
this.userId = toString(auditLog.getUserId().getId()); |
|||
} |
|||
this.entityName = auditLog.getEntityName(); |
|||
this.userName = auditLog.getUserName(); |
|||
this.actionType = auditLog.getActionType(); |
|||
this.actionData = auditLog.getActionData(); |
|||
this.actionStatus = auditLog.getActionStatus(); |
|||
this.actionFailureDetails = auditLog.getActionFailureDetails(); |
|||
} |
|||
|
|||
@Override |
|||
public AuditLog toData() { |
|||
AuditLog auditLog = new AuditLog(new AuditLogId(getId())); |
|||
auditLog.setCreatedTime(UUIDs.unixTimestamp(getId())); |
|||
if (tenantId != null) { |
|||
auditLog.setTenantId(new TenantId(toUUID(tenantId))); |
|||
} |
|||
if (customerId != null) { |
|||
auditLog.setCustomerId(new CustomerId(toUUID(customerId))); |
|||
} |
|||
if (entityId != null) { |
|||
auditLog.setEntityId(EntityIdFactory.getByTypeAndId(entityType.name(), toUUID(entityId).toString())); |
|||
} |
|||
if (userId != null) { |
|||
auditLog.setUserId(new UserId(toUUID(entityId))); |
|||
} |
|||
auditLog.setEntityName(this.entityName); |
|||
auditLog.setUserName(this.userName); |
|||
auditLog.setActionType(this.actionType); |
|||
auditLog.setActionData(this.actionData); |
|||
auditLog.setActionStatus(this.actionStatus); |
|||
auditLog.setActionFailureDetails(this.actionFailureDetails); |
|||
return auditLog; |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
/** |
|||
* Copyright © 2016-2017 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.dao.model.type; |
|||
|
|||
import com.datastax.driver.extras.codecs.enums.EnumNameCodec; |
|||
import org.thingsboard.server.common.data.audit.ActionStatus; |
|||
|
|||
public class ActionStatusCodec extends EnumNameCodec<ActionStatus> { |
|||
|
|||
public ActionStatusCodec() { |
|||
super(ActionStatus.class); |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
/** |
|||
* Copyright © 2016-2017 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.dao.model.type; |
|||
|
|||
import com.datastax.driver.extras.codecs.enums.EnumNameCodec; |
|||
import org.thingsboard.server.common.data.audit.ActionType; |
|||
|
|||
public class ActionTypeCodec extends EnumNameCodec<ActionType> { |
|||
|
|||
public ActionTypeCodec() { |
|||
super(ActionType.class); |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
/** |
|||
* Copyright © 2016-2017 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.dao.sql.audit; |
|||
|
|||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor; |
|||
import org.springframework.data.repository.CrudRepository; |
|||
import org.thingsboard.server.dao.model.sql.AuditLogEntity; |
|||
|
|||
public interface AuditLogRepository extends CrudRepository<AuditLogEntity, String>, JpaSpecificationExecutor<AuditLogEntity> { |
|||
|
|||
} |
|||
@ -0,0 +1,155 @@ |
|||
/** |
|||
* Copyright © 2016-2017 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.dao.sql.audit; |
|||
|
|||
import com.google.common.util.concurrent.ListenableFuture; |
|||
import com.google.common.util.concurrent.ListeningExecutorService; |
|||
import com.google.common.util.concurrent.MoreExecutors; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.data.domain.PageRequest; |
|||
import org.springframework.data.domain.Pageable; |
|||
import org.springframework.data.domain.Sort; |
|||
import org.springframework.data.jpa.domain.Specification; |
|||
import org.springframework.data.repository.CrudRepository; |
|||
import org.springframework.stereotype.Component; |
|||
import org.thingsboard.server.common.data.UUIDConverter; |
|||
import org.thingsboard.server.common.data.audit.AuditLog; |
|||
import org.thingsboard.server.common.data.id.CustomerId; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.data.id.UserId; |
|||
import org.thingsboard.server.common.data.page.TimePageLink; |
|||
import org.thingsboard.server.dao.DaoUtil; |
|||
import org.thingsboard.server.dao.audit.AuditLogDao; |
|||
import org.thingsboard.server.dao.model.sql.AuditLogEntity; |
|||
import org.thingsboard.server.dao.sql.JpaAbstractDao; |
|||
import org.thingsboard.server.dao.sql.JpaAbstractSearchTimeDao; |
|||
import org.thingsboard.server.dao.util.SqlDao; |
|||
|
|||
import javax.annotation.PreDestroy; |
|||
import javax.persistence.criteria.Predicate; |
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
import java.util.UUID; |
|||
import java.util.concurrent.Executors; |
|||
|
|||
import static org.springframework.data.jpa.domain.Specifications.where; |
|||
import static org.thingsboard.server.dao.model.ModelConstants.ID_PROPERTY; |
|||
|
|||
@Component |
|||
@SqlDao |
|||
public class JpaAuditLogDao extends JpaAbstractDao<AuditLogEntity, AuditLog> implements AuditLogDao { |
|||
|
|||
private ListeningExecutorService insertService = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor()); |
|||
|
|||
@Autowired |
|||
private AuditLogRepository auditLogRepository; |
|||
|
|||
@Override |
|||
protected Class<AuditLogEntity> getEntityClass() { |
|||
return AuditLogEntity.class; |
|||
} |
|||
|
|||
@Override |
|||
protected CrudRepository<AuditLogEntity, String> getCrudRepository() { |
|||
return auditLogRepository; |
|||
} |
|||
|
|||
@PreDestroy |
|||
void onDestroy() { |
|||
insertService.shutdown(); |
|||
} |
|||
|
|||
@Override |
|||
public ListenableFuture<Void> saveByTenantId(AuditLog auditLog) { |
|||
return insertService.submit(() -> { |
|||
save(auditLog); |
|||
return null; |
|||
}); |
|||
} |
|||
|
|||
@Override |
|||
public ListenableFuture<Void> saveByTenantIdAndEntityId(AuditLog auditLog) { |
|||
return insertService.submit(() -> null); |
|||
} |
|||
|
|||
@Override |
|||
public ListenableFuture<Void> saveByTenantIdAndCustomerId(AuditLog auditLog) { |
|||
return insertService.submit(() -> null); |
|||
} |
|||
|
|||
@Override |
|||
public ListenableFuture<Void> saveByTenantIdAndUserId(AuditLog auditLog) { |
|||
return insertService.submit(() -> null); |
|||
} |
|||
|
|||
@Override |
|||
public ListenableFuture<Void> savePartitionsByTenantId(AuditLog auditLog) { |
|||
return insertService.submit(() -> null); |
|||
} |
|||
|
|||
@Override |
|||
public List<AuditLog> findAuditLogsByTenantIdAndEntityId(UUID tenantId, EntityId entityId, TimePageLink pageLink) { |
|||
return findAuditLogs(tenantId, entityId, null, null, pageLink); |
|||
} |
|||
|
|||
@Override |
|||
public List<AuditLog> findAuditLogsByTenantIdAndCustomerId(UUID tenantId, CustomerId customerId, TimePageLink pageLink) { |
|||
return findAuditLogs(tenantId, null, customerId, null, pageLink); |
|||
} |
|||
|
|||
@Override |
|||
public List<AuditLog> findAuditLogsByTenantIdAndUserId(UUID tenantId, UserId userId, TimePageLink pageLink) { |
|||
return findAuditLogs(tenantId, null, null, userId, pageLink); |
|||
} |
|||
|
|||
@Override |
|||
public List<AuditLog> findAuditLogsByTenantId(UUID tenantId, TimePageLink pageLink) { |
|||
return findAuditLogs(tenantId, null, null, null, pageLink); |
|||
} |
|||
|
|||
private List<AuditLog> findAuditLogs(UUID tenantId, EntityId entityId, CustomerId customerId, UserId userId, TimePageLink pageLink) { |
|||
Specification<AuditLogEntity> timeSearchSpec = JpaAbstractSearchTimeDao.getTimeSearchPageSpec(pageLink, "id"); |
|||
Specification<AuditLogEntity> fieldsSpec = getEntityFieldsSpec(tenantId, entityId, customerId, userId); |
|||
Sort.Direction sortDirection = pageLink.isAscOrder() ? Sort.Direction.ASC : Sort.Direction.DESC; |
|||
Pageable pageable = new PageRequest(0, pageLink.getLimit(), sortDirection, ID_PROPERTY); |
|||
return DaoUtil.convertDataList(auditLogRepository.findAll(where(timeSearchSpec).and(fieldsSpec), pageable).getContent()); |
|||
} |
|||
|
|||
private Specification<AuditLogEntity> getEntityFieldsSpec(UUID tenantId, EntityId entityId, CustomerId customerId, UserId userId) { |
|||
return (root, criteriaQuery, criteriaBuilder) -> { |
|||
List<Predicate> predicates = new ArrayList<>(); |
|||
if (tenantId != null) { |
|||
Predicate tenantIdPredicate = criteriaBuilder.equal(root.get("tenantId"), UUIDConverter.fromTimeUUID(tenantId)); |
|||
predicates.add(tenantIdPredicate); |
|||
} |
|||
if (entityId != null) { |
|||
Predicate entityTypePredicate = criteriaBuilder.equal(root.get("entityType"), entityId.getEntityType()); |
|||
predicates.add(entityTypePredicate); |
|||
Predicate entityIdPredicate = criteriaBuilder.equal(root.get("entityId"), UUIDConverter.fromTimeUUID(entityId.getId())); |
|||
predicates.add(entityIdPredicate); |
|||
} |
|||
if (customerId != null) { |
|||
Predicate tenantIdPredicate = criteriaBuilder.equal(root.get("customerId"), UUIDConverter.fromTimeUUID(customerId.getId())); |
|||
predicates.add(tenantIdPredicate); |
|||
} |
|||
if (userId != null) { |
|||
Predicate tenantIdPredicate = criteriaBuilder.equal(root.get("userId"), UUIDConverter.fromTimeUUID(userId.getId())); |
|||
predicates.add(tenantIdPredicate); |
|||
} |
|||
return criteriaBuilder.and(predicates.toArray(new Predicate[]{})); |
|||
}; |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
/** |
|||
* Copyright © 2016-2017 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.dao.sql.audit; |
|||
|
|||
import org.thingsboard.server.dao.AbstractJpaDaoTest; |
|||
|
|||
public class JpaAuditLogDaoTest extends AbstractJpaDaoTest { |
|||
} |
|||
@ -0,0 +1,116 @@ |
|||
/* |
|||
* Copyright © 2016-2017 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
export default angular.module('thingsboard.api.auditLog', []) |
|||
.factory('auditLogService', AuditLogService) |
|||
.name; |
|||
|
|||
/*@ngInject*/ |
|||
function AuditLogService($http, $q) { |
|||
|
|||
var service = { |
|||
getAuditLogsByEntityId: getAuditLogsByEntityId, |
|||
getAuditLogsByUserId: getAuditLogsByUserId, |
|||
getAuditLogsByCustomerId: getAuditLogsByCustomerId, |
|||
getAuditLogs: getAuditLogs |
|||
} |
|||
|
|||
return service; |
|||
|
|||
function getAuditLogsByEntityId (entityType, entityId, pageLink) { |
|||
var deferred = $q.defer(); |
|||
var url = `/api/audit/logs/entity/${entityType}/${entityId}?limit=${pageLink.limit}`; |
|||
|
|||
if (angular.isDefined(pageLink.startTime) && pageLink.startTime != null) { |
|||
url += '&startTime=' + pageLink.startTime; |
|||
} |
|||
if (angular.isDefined(pageLink.endTime) && pageLink.endTime != null) { |
|||
url += '&endTime=' + pageLink.endTime; |
|||
} |
|||
if (angular.isDefined(pageLink.idOffset) && pageLink.idOffset != null) { |
|||
url += '&offset=' + pageLink.idOffset; |
|||
} |
|||
$http.get(url, null).then(function success(response) { |
|||
deferred.resolve(response.data); |
|||
}, function fail() { |
|||
deferred.reject(); |
|||
}); |
|||
return deferred.promise; |
|||
} |
|||
|
|||
function getAuditLogsByUserId (userId, pageLink) { |
|||
var deferred = $q.defer(); |
|||
var url = `/api/audit/logs/user/${userId}?limit=${pageLink.limit}`; |
|||
|
|||
if (angular.isDefined(pageLink.startTime) && pageLink.startTime != null) { |
|||
url += '&startTime=' + pageLink.startTime; |
|||
} |
|||
if (angular.isDefined(pageLink.endTime) && pageLink.endTime != null) { |
|||
url += '&endTime=' + pageLink.endTime; |
|||
} |
|||
if (angular.isDefined(pageLink.idOffset) && pageLink.idOffset != null) { |
|||
url += '&offset=' + pageLink.idOffset; |
|||
} |
|||
$http.get(url, null).then(function success(response) { |
|||
deferred.resolve(response.data); |
|||
}, function fail() { |
|||
deferred.reject(); |
|||
}); |
|||
return deferred.promise; |
|||
} |
|||
|
|||
function getAuditLogsByCustomerId (customerId, pageLink) { |
|||
var deferred = $q.defer(); |
|||
var url = `/api/audit/logs/customer/${customerId}?limit=${pageLink.limit}`; |
|||
|
|||
if (angular.isDefined(pageLink.startTime) && pageLink.startTime != null) { |
|||
url += '&startTime=' + pageLink.startTime; |
|||
} |
|||
if (angular.isDefined(pageLink.endTime) && pageLink.endTime != null) { |
|||
url += '&endTime=' + pageLink.endTime; |
|||
} |
|||
if (angular.isDefined(pageLink.idOffset) && pageLink.idOffset != null) { |
|||
url += '&offset=' + pageLink.idOffset; |
|||
} |
|||
$http.get(url, null).then(function success(response) { |
|||
deferred.resolve(response.data); |
|||
}, function fail() { |
|||
deferred.reject(); |
|||
}); |
|||
return deferred.promise; |
|||
} |
|||
|
|||
function getAuditLogs (pageLink) { |
|||
var deferred = $q.defer(); |
|||
var url = `/api/audit/logs?limit=${pageLink.limit}`; |
|||
|
|||
if (angular.isDefined(pageLink.startTime) && pageLink.startTime != null) { |
|||
url += '&startTime=' + pageLink.startTime; |
|||
} |
|||
if (angular.isDefined(pageLink.endTime) && pageLink.endTime != null) { |
|||
url += '&endTime=' + pageLink.endTime; |
|||
} |
|||
if (angular.isDefined(pageLink.idOffset) && pageLink.idOffset != null) { |
|||
url += '&offset=' + pageLink.idOffset; |
|||
} |
|||
$http.get(url, null).then(function success(response) { |
|||
deferred.resolve(response.data); |
|||
}, function fail() { |
|||
deferred.reject(); |
|||
}); |
|||
return deferred.promise; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,103 @@ |
|||
/* |
|||
* Copyright © 2016-2017 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
import $ from 'jquery'; |
|||
import 'brace/ext/language_tools'; |
|||
import 'brace/mode/java'; |
|||
import 'brace/theme/github'; |
|||
|
|||
/* eslint-disable angular/angularelement */ |
|||
|
|||
import './audit-log-details-dialog.scss'; |
|||
|
|||
/*@ngInject*/ |
|||
export default function AuditLogDetailsDialogController($mdDialog, types, auditLog, showingCallback) { |
|||
|
|||
var vm = this; |
|||
|
|||
showingCallback.onShowing = function(scope, element) { |
|||
updateEditorSize(element, vm.actionData, 'tb-audit-log-action-data'); |
|||
vm.actionDataEditor.resize(); |
|||
if (vm.displayFailureDetails) { |
|||
updateEditorSize(element, vm.actionFailureDetails, 'tb-audit-log-failure-details'); |
|||
vm.failureDetailsEditor.resize(); |
|||
} |
|||
}; |
|||
|
|||
vm.types = types; |
|||
vm.auditLog = auditLog; |
|||
vm.displayFailureDetails = auditLog.actionStatus == types.auditLogActionStatus.FAILURE.value; |
|||
vm.actionData = auditLog.actionDataText; |
|||
vm.actionFailureDetails = auditLog.actionFailureDetails; |
|||
|
|||
vm.actionDataContentOptions = { |
|||
useWrapMode: false, |
|||
mode: 'java', |
|||
showGutter: false, |
|||
showPrintMargin: false, |
|||
theme: 'github', |
|||
advanced: { |
|||
enableSnippets: false, |
|||
enableBasicAutocompletion: false, |
|||
enableLiveAutocompletion: false |
|||
}, |
|||
onLoad: function (_ace) { |
|||
vm.actionDataEditor = _ace; |
|||
} |
|||
}; |
|||
|
|||
vm.failureDetailsContentOptions = { |
|||
useWrapMode: false, |
|||
mode: 'java', |
|||
showGutter: false, |
|||
showPrintMargin: false, |
|||
theme: 'github', |
|||
advanced: { |
|||
enableSnippets: false, |
|||
enableBasicAutocompletion: false, |
|||
enableLiveAutocompletion: false |
|||
}, |
|||
onLoad: function (_ace) { |
|||
vm.failureDetailsEditor = _ace; |
|||
} |
|||
}; |
|||
|
|||
function updateEditorSize(element, content, editorId) { |
|||
var newHeight = 200; |
|||
var newWidth = 600; |
|||
if (content && content.length > 0) { |
|||
var lines = content.split('\n'); |
|||
newHeight = 16 * lines.length + 16; |
|||
var maxLineLength = 0; |
|||
for (var i in lines) { |
|||
var line = lines[i].replace(/\t/g, ' ').replace(/\n/g, ''); |
|||
var lineLength = line.length; |
|||
maxLineLength = Math.max(maxLineLength, lineLength); |
|||
} |
|||
newWidth = 8 * maxLineLength + 16; |
|||
} |
|||
$('#'+editorId, element).height(newHeight.toString() + "px").css('min-height', newHeight.toString() + "px") |
|||
.width(newWidth.toString() + "px"); |
|||
} |
|||
|
|||
vm.close = close; |
|||
|
|||
function close () { |
|||
$mdDialog.hide(); |
|||
} |
|||
|
|||
} |
|||
|
|||
/* eslint-enable angular/angularelement */ |
|||
@ -0,0 +1,23 @@ |
|||
/** |
|||
* Copyright © 2016-2017 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0 |
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
|
|||
#tb-audit-log-action-data, #tb-audit-log-failure-details { |
|||
min-width: 400px; |
|||
min-height: 50px; |
|||
width: 100%; |
|||
height: 100%; |
|||
border: 1px solid #C0C0C0; |
|||
} |
|||
@ -0,0 +1,49 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2017 The Thingsboard Authors |
|||
|
|||
Licensed under the Apache License, Version 2.0 (the "License"); |
|||
you may not use this file except in compliance with the License. |
|||
You may obtain a copy of the License at |
|||
|
|||
http://www.apache.org/licenses/LICENSE-2.0 |
|||
|
|||
Unless required by applicable law or agreed to in writing, software |
|||
distributed under the License is distributed on an "AS IS" BASIS, |
|||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
See the License for the specific language governing permissions and |
|||
limitations under the License. |
|||
|
|||
--> |
|||
<md-dialog aria-label="{{ 'audit-log.audit-log-details' | translate }}"> |
|||
<md-toolbar> |
|||
<div class="md-toolbar-tools"> |
|||
<h2 translate>audit-log.audit-log-details</h2> |
|||
<span flex></span> |
|||
<md-button class="md-icon-button" ng-click="vm.close()"> |
|||
<ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon> |
|||
</md-button> |
|||
</div> |
|||
</md-toolbar> |
|||
<md-dialog-content> |
|||
<div class="md-dialog-content" layout="column"> |
|||
<label translate class="tb-title no-padding">audit-log.action-data</label> |
|||
<div flex id="tb-audit-log-action-data" readonly |
|||
ui-ace="vm.actionDataContentOptions" |
|||
ng-model="vm.actionData"> |
|||
</div> |
|||
<span style="height: 30px;"></span> |
|||
<label ng-show="vm.displayFailureDetails" translate class="tb-title no-padding">audit-log.failure-details</label> |
|||
<div ng-show="vm.displayFailureDetails" flex id="tb-audit-log-failure-details" readonly |
|||
ui-ace="vm.failureDetailsContentOptions" |
|||
ng-model="vm.actionFailureDetails"> |
|||
</div> |
|||
</div> |
|||
</md-dialog-content> |
|||
<md-dialog-actions layout="row"> |
|||
<span flex></span> |
|||
<md-button ng-disabled="$root.loading" ng-click="vm.close()" style="margin-right:20px;">{{ 'action.close' | |
|||
translate }} |
|||
</md-button> |
|||
</md-dialog-actions> |
|||
</md-dialog> |
|||
@ -0,0 +1,41 @@ |
|||
/* |
|||
* Copyright © 2016-2017 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
/* eslint-disable import/no-unresolved, import/default */ |
|||
|
|||
import auditLogHeaderTemplate from './audit-log-header.tpl.html'; |
|||
|
|||
/* eslint-enable import/no-unresolved, import/default */ |
|||
|
|||
/*@ngInject*/ |
|||
export default function AuditLogHeaderDirective($compile, $templateCache, types) { |
|||
|
|||
var linker = function (scope, element, attrs) { |
|||
|
|||
var template = $templateCache.get(auditLogHeaderTemplate); |
|||
element.html(template); |
|||
scope.auditLogMode = attrs.auditLogMode; |
|||
scope.types = types; |
|||
$compile(element.contents())(scope); |
|||
|
|||
}; |
|||
|
|||
return { |
|||
restrict: "A", |
|||
replace: false, |
|||
link: linker, |
|||
scope: false |
|||
}; |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2017 The Thingsboard Authors |
|||
|
|||
Licensed under the Apache License, Version 2.0 (the "License"); |
|||
you may not use this file except in compliance with the License. |
|||
You may obtain a copy of the License at |
|||
|
|||
http://www.apache.org/licenses/LICENSE-2.0 |
|||
|
|||
Unless required by applicable law or agreed to in writing, software |
|||
distributed under the License is distributed on an "AS IS" BASIS, |
|||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
See the License for the specific language governing permissions and |
|||
limitations under the License. |
|||
|
|||
--> |
|||
<div translate class="tb-cell" flex="30">audit-log.timestamp</div> |
|||
<div ng-if="auditLogMode != types.auditLogMode.entity" translate class="tb-cell" flex="10">audit-log.entity-type</div> |
|||
<div ng-if="auditLogMode != types.auditLogMode.entity" translate class="tb-cell" flex="30">audit-log.entity-name</div> |
|||
<div ng-if="auditLogMode != types.auditLogMode.user" translate class="tb-cell" flex="30">audit-log.user</div> |
|||
<div translate class="tb-cell" flex="15">audit-log.type</div> |
|||
<div translate class="tb-cell" flex="15">audit-log.status</div> |
|||
<div translate class="tb-cell" flex="10">audit-log.details</div> |
|||
@ -0,0 +1,67 @@ |
|||
/* |
|||
* Copyright © 2016-2017 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
/* eslint-disable import/no-unresolved, import/default */ |
|||
|
|||
import auditLogDetailsDialogTemplate from './audit-log-details-dialog.tpl.html'; |
|||
|
|||
import auditLogRowTemplate from './audit-log-row.tpl.html'; |
|||
|
|||
/* eslint-enable import/no-unresolved, import/default */ |
|||
|
|||
/*@ngInject*/ |
|||
export default function AuditLogRowDirective($compile, $templateCache, types, $mdDialog, $document) { |
|||
|
|||
var linker = function (scope, element, attrs) { |
|||
|
|||
var template = $templateCache.get(auditLogRowTemplate); |
|||
element.html(template); |
|||
|
|||
scope.auditLog = attrs.auditLog; |
|||
scope.auditLogMode = attrs.auditLogMode; |
|||
scope.types = types; |
|||
|
|||
scope.showAuditLogDetails = function($event) { |
|||
var onShowingCallback = { |
|||
onShowing: function(){} |
|||
} |
|||
$mdDialog.show({ |
|||
controller: 'AuditLogDetailsDialogController', |
|||
controllerAs: 'vm', |
|||
templateUrl: auditLogDetailsDialogTemplate, |
|||
locals: { |
|||
auditLog: scope.auditLog, |
|||
showingCallback: onShowingCallback |
|||
}, |
|||
parent: angular.element($document[0].body), |
|||
targetEvent: $event, |
|||
fullscreen: true, |
|||
skipHide: true, |
|||
onShowing: function(scope, element) { |
|||
onShowingCallback.onShowing(scope, element); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
$compile(element.contents())(scope); |
|||
} |
|||
|
|||
return { |
|||
restrict: "A", |
|||
replace: false, |
|||
link: linker, |
|||
scope: false |
|||
}; |
|||
} |
|||
@ -0,0 +1,36 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2017 The Thingsboard Authors |
|||
|
|||
Licensed under the Apache License, Version 2.0 (the "License"); |
|||
you may not use this file except in compliance with the License. |
|||
You may obtain a copy of the License at |
|||
|
|||
http://www.apache.org/licenses/LICENSE-2.0 |
|||
|
|||
Unless required by applicable law or agreed to in writing, software |
|||
distributed under the License is distributed on an "AS IS" BASIS, |
|||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
See the License for the specific language governing permissions and |
|||
limitations under the License. |
|||
|
|||
--> |
|||
<div class="tb-cell" flex="30">{{ auditLog.createdTime | date : 'yyyy-MM-dd HH:mm:ss' }}</div> |
|||
<div ng-if="auditLogMode != types.auditLogMode.entity" class="tb-cell" flex="10">{{ auditLog.entityTypeText }}</div> |
|||
<div ng-if="auditLogMode != types.auditLogMode.entity" class="tb-cell" flex="30">{{ auditLog.entityName }}</div> |
|||
<div ng-if="auditLogMode != types.auditLogMode.user" class="tb-cell" flex="30">{{ auditLog.userName }}</div> |
|||
<div class="tb-cell" flex="15">{{ auditLog.actionTypeText }}</div> |
|||
<div class="tb-cell" flex="15">{{ auditLog.actionStatusText }}</div> |
|||
<div class="tb-cell" flex="10"> |
|||
<md-button class="md-icon-button md-primary" |
|||
ng-click="showAuditLogDetails($event)" |
|||
aria-label="{{ 'action.view' | translate }}"> |
|||
<md-tooltip md-direction="top"> |
|||
{{ 'audit-log.details' | translate }} |
|||
</md-tooltip> |
|||
<md-icon aria-label="{{ 'action.view' | translate }}" |
|||
class="material-icons"> |
|||
more_horiz |
|||
</md-icon> |
|||
</md-button> |
|||
</div> |
|||
@ -0,0 +1,262 @@ |
|||
/* |
|||
* Copyright © 2016-2017 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
import './audit-log.scss'; |
|||
|
|||
/* eslint-disable import/no-unresolved, import/default */ |
|||
|
|||
import auditLogTableTemplate from './audit-log-table.tpl.html'; |
|||
|
|||
/* eslint-enable import/no-unresolved, import/default */ |
|||
|
|||
/*@ngInject*/ |
|||
export default function AuditLogTableDirective($compile, $templateCache, $rootScope, $filter, $translate, types, auditLogService) { |
|||
|
|||
var linker = function (scope, element) { |
|||
|
|||
var template = $templateCache.get(auditLogTableTemplate); |
|||
|
|||
element.html(template); |
|||
|
|||
scope.types = types; |
|||
|
|||
var pageSize = 20; |
|||
var startTime = 0; |
|||
var endTime = 0; |
|||
|
|||
scope.timewindow = { |
|||
history: { |
|||
timewindowMs: 24 * 60 * 60 * 1000 // 1 day
|
|||
} |
|||
} |
|||
|
|||
scope.topIndex = 0; |
|||
scope.searchText = ''; |
|||
|
|||
scope.theAuditLogs = { |
|||
getItemAtIndex: function (index) { |
|||
if (index > scope.auditLogs.filtered.length) { |
|||
scope.theAuditLogs.fetchMoreItems_(index); |
|||
return null; |
|||
} |
|||
return scope.auditLogs.filtered[index]; |
|||
}, |
|||
|
|||
getLength: function () { |
|||
if (scope.auditLogs.hasNext) { |
|||
return scope.auditLogs.filtered.length + scope.auditLogs.nextPageLink.limit; |
|||
} else { |
|||
return scope.auditLogs.filtered.length; |
|||
} |
|||
}, |
|||
|
|||
fetchMoreItems_: function () { |
|||
if (scope.auditLogs.hasNext && !scope.auditLogs.pending) { |
|||
var promise = getAuditLogsPromise(scope.auditLogs.nextPageLink); |
|||
if (promise) { |
|||
scope.auditLogs.pending = true; |
|||
promise.then( |
|||
function success(auditLogs) { |
|||
scope.auditLogs.data = scope.auditLogs.data.concat(prepareAuditLogsData(auditLogs.data)); |
|||
scope.auditLogs.filtered = $filter('filter')(scope.auditLogs.data, {$: scope.searchText}); |
|||
scope.auditLogs.nextPageLink = auditLogs.nextPageLink; |
|||
scope.auditLogs.hasNext = auditLogs.hasNext; |
|||
if (scope.auditLogs.hasNext) { |
|||
scope.auditLogs.nextPageLink.limit = pageSize; |
|||
} |
|||
scope.auditLogs.pending = false; |
|||
}, |
|||
function fail() { |
|||
scope.auditLogs.hasNext = false; |
|||
scope.auditLogs.pending = false; |
|||
}); |
|||
} else { |
|||
scope.auditLogs.hasNext = false; |
|||
} |
|||
} |
|||
} |
|||
}; |
|||
|
|||
function prepareAuditLogsData(data) { |
|||
data.forEach( |
|||
auditLog => { |
|||
auditLog.entityTypeText = $translate.instant(types.entityTypeTranslations[auditLog.entityId.entityType].type); |
|||
auditLog.actionTypeText = $translate.instant(types.auditLogActionType[auditLog.actionType].name); |
|||
auditLog.actionStatusText = $translate.instant(types.auditLogActionStatus[auditLog.actionStatus].name); |
|||
auditLog.actionDataText = auditLog.actionData ? angular.toJson(auditLog.actionData, true) : ''; |
|||
} |
|||
); |
|||
return data; |
|||
} |
|||
|
|||
scope.$watch("entityId", function(newVal, prevVal) { |
|||
if (newVal && !angular.equals(newVal, prevVal)) { |
|||
resetFilter(); |
|||
scope.reload(); |
|||
} |
|||
}); |
|||
|
|||
scope.$watch("userId", function(newVal, prevVal) { |
|||
if (newVal && !angular.equals(newVal, prevVal)) { |
|||
resetFilter(); |
|||
scope.reload(); |
|||
} |
|||
}); |
|||
|
|||
scope.$watch("customerId", function(newVal, prevVal) { |
|||
if (newVal && !angular.equals(newVal, prevVal)) { |
|||
resetFilter(); |
|||
scope.reload(); |
|||
} |
|||
}); |
|||
|
|||
function getAuditLogsPromise(pageLink) { |
|||
switch(scope.auditLogMode) { |
|||
case types.auditLogMode.tenant: |
|||
return auditLogService.getAuditLogs(pageLink); |
|||
case types.auditLogMode.entity: |
|||
if (scope.entityType && scope.entityId) { |
|||
return auditLogService.getAuditLogsByEntityId(scope.entityType, scope.entityId, |
|||
pageLink); |
|||
} else { |
|||
return null; |
|||
} |
|||
case types.auditLogMode.user: |
|||
if (scope.userId) { |
|||
return auditLogService.getAuditLogsByUserId(scope.userId, pageLink); |
|||
} else { |
|||
return null; |
|||
} |
|||
case types.auditLogMode.customer: |
|||
if (scope.customerId) { |
|||
return auditLogService.getAuditLogsByCustomerId(scope.customerId, pageLink); |
|||
} else { |
|||
return null; |
|||
} |
|||
} |
|||
} |
|||
|
|||
function destroyWatchers() { |
|||
if (scope.timewindowWatchHandle) { |
|||
scope.timewindowWatchHandle(); |
|||
scope.timewindowWatchHandle = null; |
|||
} |
|||
if (scope.searchTextWatchHandle) { |
|||
scope.searchTextWatchHandle(); |
|||
scope.searchTextWatchHandle = null; |
|||
} |
|||
} |
|||
|
|||
function initWatchers() { |
|||
scope.timewindowWatchHandle = scope.$watch("timewindow", function(newVal, prevVal) { |
|||
if (newVal && !angular.equals(newVal, prevVal)) { |
|||
scope.reload(); |
|||
} |
|||
}, true); |
|||
|
|||
scope.searchTextWatchHandle = scope.$watch("searchText", function(newVal, prevVal) { |
|||
if (!angular.equals(newVal, prevVal)) { |
|||
scope.searchTextUpdated(); |
|||
} |
|||
}, true); |
|||
} |
|||
|
|||
function resetFilter() { |
|||
destroyWatchers(); |
|||
scope.timewindow = { |
|||
history: { |
|||
timewindowMs: 24 * 60 * 60 * 1000 // 1 day
|
|||
} |
|||
}; |
|||
scope.searchText = ''; |
|||
initWatchers(); |
|||
} |
|||
|
|||
function updateTimeWindowRange () { |
|||
if (scope.timewindow.history.timewindowMs) { |
|||
var currentTime = (new Date).getTime(); |
|||
startTime = currentTime - scope.timewindow.history.timewindowMs; |
|||
endTime = currentTime; |
|||
} else { |
|||
startTime = scope.timewindow.history.fixedTimewindow.startTimeMs; |
|||
endTime = scope.timewindow.history.fixedTimewindow.endTimeMs; |
|||
} |
|||
} |
|||
|
|||
scope.reload = function() { |
|||
scope.topIndex = 0; |
|||
updateTimeWindowRange(); |
|||
scope.auditLogs = { |
|||
data: [], |
|||
filtered: [], |
|||
nextPageLink: { |
|||
limit: pageSize, |
|||
startTime: startTime, |
|||
endTime: endTime |
|||
}, |
|||
hasNext: true, |
|||
pending: false |
|||
}; |
|||
scope.theAuditLogs.getItemAtIndex(pageSize); |
|||
} |
|||
|
|||
scope.searchTextUpdated = function() { |
|||
scope.auditLogs.filtered = $filter('filter')(scope.auditLogs.data, {$: scope.searchText}); |
|||
scope.theAuditLogs.getItemAtIndex(pageSize); |
|||
} |
|||
|
|||
scope.noData = function() { |
|||
return scope.auditLogs.data.length == 0 && !scope.auditLogs.hasNext; |
|||
} |
|||
|
|||
scope.hasData = function() { |
|||
return scope.auditLogs.data.length > 0; |
|||
} |
|||
|
|||
scope.loading = function() { |
|||
return $rootScope.loading; |
|||
} |
|||
|
|||
scope.hasScroll = function() { |
|||
var repeatContainer = scope.repeatContainer[0]; |
|||
if (repeatContainer) { |
|||
var scrollElement = repeatContainer.children[0]; |
|||
if (scrollElement) { |
|||
return scrollElement.scrollHeight > scrollElement.clientHeight; |
|||
} |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
scope.reload(); |
|||
|
|||
initWatchers(); |
|||
|
|||
$compile(element.contents())(scope); |
|||
} |
|||
|
|||
return { |
|||
restrict: "E", |
|||
link: linker, |
|||
scope: { |
|||
entityType: '=?', |
|||
entityId: '=?', |
|||
userId: '=?', |
|||
customerId: '=?', |
|||
auditLogMode: '@', |
|||
pageMode: '@?' |
|||
} |
|||
}; |
|||
} |
|||
@ -0,0 +1,68 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2017 The Thingsboard Authors |
|||
|
|||
Licensed under the Apache License, Version 2.0 (the "License"); |
|||
you may not use this file except in compliance with the License. |
|||
You may obtain a copy of the License at |
|||
|
|||
http://www.apache.org/licenses/LICENSE-2.0 |
|||
|
|||
Unless required by applicable law or agreed to in writing, software |
|||
distributed under the License is distributed on an "AS IS" BASIS, |
|||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
See the License for the specific language governing permissions and |
|||
limitations under the License. |
|||
|
|||
--> |
|||
<md-content flex class="md-padding tb-absolute-fill" layout="column"> |
|||
<div flex layout="column" class="tb-audit-logs" ng-class="{'md-whiteframe-z1': pageMode}"> |
|||
<div layout="column" layout-gt-sm="row" layout-align-gt-sm="start center" class="tb-audit-log-toolbar" ng-class="{'md-padding': pageMode, 'tb-audit-log-margin-18px': !pageMode}"> |
|||
<tb-timewindow ng-model="timewindow" history-only as-button="true"></tb-timewindow> |
|||
<div flex layout="row" layout-align="start center"> |
|||
<md-button class="md-icon-button" aria-label="{{ 'action.search' | translate }}"> |
|||
<md-icon aria-label="{{ 'action.search' | translate }}" class="material-icons">search</md-icon> |
|||
<md-tooltip md-direction="top"> |
|||
{{'audit-log.search' | translate}} |
|||
</md-tooltip> |
|||
</md-button> |
|||
<md-input-container flex class="tb-audit-log-search-input"> |
|||
<label> </label> |
|||
<input ng-model="searchText" placeholder="{{'audit-log.search' | translate}}"/> |
|||
</md-input-container> |
|||
<md-button ng-disabled="$root.loading" class="md-icon-button" aria-label="Close" ng-click="searchText = ''"> |
|||
<md-icon aria-label="Close" class="material-icons">close</md-icon> |
|||
<md-tooltip md-direction="top"> |
|||
{{ 'audit-log.clear-search' | translate }} |
|||
</md-tooltip> |
|||
</md-button> |
|||
<md-button ng-disabled="$root.loading" |
|||
class="md-icon-button" ng-click="reload()"> |
|||
<md-icon>refresh</md-icon> |
|||
<md-tooltip md-direction="top"> |
|||
{{ 'action.refresh' | translate }} |
|||
</md-tooltip> |
|||
</md-button> |
|||
</div> |
|||
</div> |
|||
<div flex layout="column" class="tb-audit-log-container" ng-class="{'md-whiteframe-z1': !pageMode}"> |
|||
<md-list flex layout="column" class="tb-audit-log-table" ng-class="{'tb-audit-log-table-full': pageMode}"> |
|||
<md-list class="tb-row tb-header" layout="row" layout-align="start center" tb-audit-log-header audit-log-mode="{{auditLogMode}}"> |
|||
</md-list> |
|||
<md-progress-linear style="max-height: 0px;" md-mode="indeterminate" ng-disabled="!$root.loading" |
|||
ng-show="$root.loading"></md-progress-linear> |
|||
<md-divider></md-divider> |
|||
<span translate layout-align="center center" |
|||
style="margin-top: 25px;" |
|||
class="tb-prompt" ng-show="noData()">audit-log.no-audit-logs-prompt</span> |
|||
<md-virtual-repeat-container ng-show="hasData()" flex md-top-index="topIndex" tb-scope-element="repeatContainer"> |
|||
<md-list-item md-virtual-repeat="auditLog in theAuditLogs" md-on-demand flex ng-style="hasScroll() ? {'margin-right':'-15px'} : {}"> |
|||
<md-list class="tb-row" flex layout="row" layout-align="start center" tb-audit-log-row audit-log-mode="{{auditLogMode}}" audit-log="{{auditLog}}"> |
|||
</md-list> |
|||
<md-divider flex></md-divider> |
|||
</md-list-item> |
|||
</md-virtual-repeat-container> |
|||
</md-list> |
|||
</div> |
|||
</div> |
|||
</md-content> |
|||
@ -0,0 +1,44 @@ |
|||
/* |
|||
* Copyright © 2016-2017 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
/* eslint-disable import/no-unresolved, import/default */ |
|||
|
|||
import auditLogsTemplate from './audit-logs.tpl.html'; |
|||
|
|||
/* eslint-enable import/no-unresolved, import/default */ |
|||
|
|||
/*@ngInject*/ |
|||
export default function AuditLogRoutes($stateProvider) { |
|||
$stateProvider |
|||
.state('home.auditLogs', { |
|||
url: '/auditLogs', |
|||
module: 'private', |
|||
auth: ['TENANT_ADMIN'], |
|||
views: { |
|||
"content@home": { |
|||
templateUrl: auditLogsTemplate, |
|||
controller: 'AuditLogsController', |
|||
controllerAs: 'vm' |
|||
} |
|||
}, |
|||
data: { |
|||
searchEnabled: false, |
|||
pageTitle: 'audit-log.audit-logs' |
|||
}, |
|||
ncyBreadcrumb: { |
|||
label: '{"icon": "track_changes", "label": "audit-log.audit-logs"}' |
|||
} |
|||
}); |
|||
} |
|||
@ -0,0 +1,91 @@ |
|||
/** |
|||
* Copyright © 2016-2017 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0 |
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
|
|||
.tb-audit-logs { |
|||
background-color: #fff; |
|||
.tb-audit-log-margin-18px { |
|||
margin-bottom: 18px; |
|||
} |
|||
.tb-audit-log-toolbar { |
|||
font-size: 20px; |
|||
} |
|||
md-input-container.tb-audit-log-search-input { |
|||
.md-errors-spacer { |
|||
min-height: 0px; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.tb-audit-log-container { |
|||
overflow-x: auto; |
|||
} |
|||
|
|||
|
|||
|
|||
md-list.tb-audit-log-table { |
|||
padding: 0px; |
|||
min-width: 700px; |
|||
&.tb-audit-log-table-full { |
|||
min-width: 900px; |
|||
} |
|||
|
|||
md-list-item { |
|||
padding: 0px; |
|||
} |
|||
|
|||
.tb-row { |
|||
height: 48px; |
|||
padding: 0px; |
|||
overflow: hidden; |
|||
} |
|||
|
|||
.tb-row:hover { |
|||
background-color: #EEEEEE; |
|||
} |
|||
|
|||
.tb-header:hover { |
|||
background: none; |
|||
} |
|||
|
|||
.tb-header { |
|||
.tb-cell { |
|||
color: rgba(0,0,0,.54); |
|||
font-size: 12px; |
|||
font-weight: 700; |
|||
white-space: nowrap; |
|||
background: none; |
|||
} |
|||
} |
|||
|
|||
.tb-cell { |
|||
padding: 0 24px; |
|||
margin: auto 0; |
|||
color: rgba(0,0,0,.87); |
|||
font-size: 13px; |
|||
vertical-align: middle; |
|||
text-align: left; |
|||
overflow: hidden; |
|||
.md-button { |
|||
padding: 0; |
|||
margin: 0; |
|||
} |
|||
} |
|||
|
|||
.tb-cell.tb-number { |
|||
text-align: right; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
/* |
|||
* Copyright © 2016-2017 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
|
|||
/*@ngInject*/ |
|||
export default function AuditLogsController(types) { |
|||
|
|||
var vm = this; |
|||
|
|||
vm.types = types; |
|||
|
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2017 The Thingsboard Authors |
|||
|
|||
Licensed under the Apache License, Version 2.0 (the "License"); |
|||
you may not use this file except in compliance with the License. |
|||
You may obtain a copy of the License at |
|||
|
|||
http://www.apache.org/licenses/LICENSE-2.0 |
|||
|
|||
Unless required by applicable law or agreed to in writing, software |
|||
distributed under the License is distributed on an "AS IS" BASIS, |
|||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
See the License for the specific language governing permissions and |
|||
limitations under the License. |
|||
|
|||
--> |
|||
|
|||
<tb-audit-log-table class="md-whiteframe-z1" |
|||
flex |
|||
audit-log-mode="{{vm.types.auditLogMode.tenant}}" |
|||
page-mode="true"> |
|||
</tb-audit-log-table> |
|||
@ -0,0 +1,31 @@ |
|||
/* |
|||
* Copyright © 2016-2017 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
|
|||
import AuditLogRoutes from './audit-log.routes'; |
|||
import AuditLogsController from './audit-logs.controller'; |
|||
import AuditLogDetailsDialogController from './audit-log-details-dialog.controller'; |
|||
import AuditLogHeaderDirective from './audit-log-header.directive'; |
|||
import AuditLogRowDirective from './audit-log-row.directive'; |
|||
import AuditLogTableDirective from './audit-log-table.directive'; |
|||
|
|||
export default angular.module('thingsboard.auditLog', []) |
|||
.config(AuditLogRoutes) |
|||
.controller('AuditLogsController', AuditLogsController) |
|||
.controller('AuditLogDetailsDialogController', AuditLogDetailsDialogController) |
|||
.directive('tbAuditLogHeader', AuditLogHeaderDirective) |
|||
.directive('tbAuditLogRow', AuditLogRowDirective) |
|||
.directive('tbAuditLogTable', AuditLogTableDirective) |
|||
.name; |
|||
@ -0,0 +1,128 @@ |
|||
/* |
|||
* Copyright © 2016-2017 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
export default angular.module('thingsboard.clipboard', []) |
|||
.factory('clipboardService', ClipboardService) |
|||
.name; |
|||
|
|||
/*@ngInject*/ |
|||
function ClipboardService($q) { |
|||
|
|||
var fakeHandler, fakeHandlerCallback, fakeElem; |
|||
|
|||
var service = { |
|||
copyToClipboard: copyToClipboard |
|||
}; |
|||
|
|||
return service; |
|||
|
|||
/* eslint-disable */ |
|||
function copyToClipboard(trigger, text) { |
|||
var deferred = $q.defer(); |
|||
const isRTL = document.documentElement.getAttribute('dir') == 'rtl'; |
|||
removeFake(); |
|||
fakeHandlerCallback = () => removeFake(); |
|||
fakeHandler = document.body.addEventListener('click', fakeHandlerCallback) || true; |
|||
fakeElem = document.createElement('textarea'); |
|||
fakeElem.style.fontSize = '12pt'; |
|||
fakeElem.style.border = '0'; |
|||
fakeElem.style.padding = '0'; |
|||
fakeElem.style.margin = '0'; |
|||
fakeElem.style.position = 'absolute'; |
|||
fakeElem.style[ isRTL ? 'right' : 'left' ] = '-9999px'; |
|||
let yPosition = window.pageYOffset || document.documentElement.scrollTop; |
|||
fakeElem.style.top = `${yPosition}px`; |
|||
fakeElem.setAttribute('readonly', ''); |
|||
fakeElem.value = text; |
|||
document.body.appendChild(fakeElem); |
|||
var selectedText = select(fakeElem); |
|||
|
|||
let succeeded; |
|||
try { |
|||
succeeded = document.execCommand('copy'); |
|||
} |
|||
catch (err) { |
|||
succeeded = false; |
|||
} |
|||
if (trigger) { |
|||
trigger.focus(); |
|||
} |
|||
window.getSelection().removeAllRanges(); |
|||
removeFake(); |
|||
if (succeeded) { |
|||
deferred.resolve(selectedText); |
|||
} else { |
|||
deferred.reject(); |
|||
} |
|||
return deferred.promise; |
|||
} |
|||
|
|||
function removeFake() { |
|||
if (fakeHandler) { |
|||
document.body.removeEventListener('click', fakeHandlerCallback); |
|||
fakeHandler = null; |
|||
fakeHandlerCallback = null; |
|||
} |
|||
if (fakeElem) { |
|||
document.body.removeChild(fakeElem); |
|||
fakeElem = null; |
|||
} |
|||
} |
|||
|
|||
function select(element) { |
|||
var selectedText; |
|||
|
|||
if (element.nodeName === 'SELECT') { |
|||
element.focus(); |
|||
|
|||
selectedText = element.value; |
|||
} |
|||
else if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') { |
|||
var isReadOnly = element.hasAttribute('readonly'); |
|||
|
|||
if (!isReadOnly) { |
|||
element.setAttribute('readonly', ''); |
|||
} |
|||
|
|||
element.select(); |
|||
element.setSelectionRange(0, element.value.length); |
|||
|
|||
if (!isReadOnly) { |
|||
element.removeAttribute('readonly'); |
|||
} |
|||
|
|||
selectedText = element.value; |
|||
} |
|||
else { |
|||
if (element.hasAttribute('contenteditable')) { |
|||
element.focus(); |
|||
} |
|||
|
|||
var selection = window.getSelection(); |
|||
var range = document.createRange(); |
|||
|
|||
range.selectNodeContents(element); |
|||
selection.removeAllRanges(); |
|||
selection.addRange(range); |
|||
|
|||
selectedText = selection.toString(); |
|||
} |
|||
|
|||
return selectedText; |
|||
} |
|||
|
|||
/* eslint-enable */ |
|||
|
|||
} |
|||
Loading…
Reference in new issue