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