improvements
This commit is contained in:
parent
287e4bbb5e
commit
fea2f8799e
@ -595,11 +595,11 @@ public abstract class BaseController {
|
||||
case ALARM_CLEAR:
|
||||
msgType = DataConstants.ALARM_CLEAR;
|
||||
break;
|
||||
case SWAPPED_FROM_TENANT:
|
||||
msgType = DataConstants.ENTITY_SWAPPED_FROM;
|
||||
case ASSIGNED_FROM_TENANT:
|
||||
msgType = DataConstants.ENTITY_ASSIGNED_FROM_TENANT;
|
||||
break;
|
||||
case SWAPPED_TO_TENANT:
|
||||
msgType = DataConstants.ENTITY_SWAPPED_TO;
|
||||
case ASSIGNED_TO_TENANT:
|
||||
msgType = DataConstants.ENTITY_ASSIGNED_TO_TENANT;
|
||||
break;
|
||||
}
|
||||
if (!StringUtils.isEmpty(msgType)) {
|
||||
@ -620,16 +620,16 @@ public abstract class BaseController {
|
||||
String strCustomerName = extractParameter(String.class, 2, additionalInfo);
|
||||
metaData.putValue("unassignedCustomerId", strCustomerId);
|
||||
metaData.putValue("unassignedCustomerName", strCustomerName);
|
||||
} else if (actionType == ActionType.SWAPPED_FROM_TENANT) {
|
||||
} else if (actionType == ActionType.ASSIGNED_FROM_TENANT) {
|
||||
String strTenantId = extractParameter(String.class, 0, additionalInfo);
|
||||
String strTenantName = extractParameter(String.class, 1, additionalInfo);
|
||||
metaData.putValue("swappedFromTenantId", strTenantId);
|
||||
metaData.putValue("swappedFromTenantName", strTenantName);
|
||||
} else if (actionType == ActionType.SWAPPED_TO_TENANT) {
|
||||
metaData.putValue("assignedFromTenantId", strTenantId);
|
||||
metaData.putValue("assignedFromTenantName", strTenantName);
|
||||
} else if (actionType == ActionType.ASSIGNED_TO_TENANT) {
|
||||
String strTenantId = extractParameter(String.class, 0, additionalInfo);
|
||||
String strTenantName = extractParameter(String.class, 1, additionalInfo);
|
||||
metaData.putValue("swappedToTenantId", strTenantId);
|
||||
metaData.putValue("swappedToTenantName", strTenantName);
|
||||
metaData.putValue("assignedToTenantId", strTenantId);
|
||||
metaData.putValue("assignedToTenantName", strTenantName);
|
||||
}
|
||||
ObjectNode entityNode;
|
||||
if (entity != null) {
|
||||
|
||||
@ -491,13 +491,13 @@ public class DeviceController extends BaseController {
|
||||
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
|
||||
@RequestMapping(value = "/tenant/{tenantId}/device/{deviceId}", method = RequestMethod.POST)
|
||||
@ResponseBody
|
||||
public Device swapDevice(@PathVariable(TENANT_ID) String strTenantId,
|
||||
public Device assignDeviceToTenant(@PathVariable(TENANT_ID) String strTenantId,
|
||||
@PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException {
|
||||
checkParameter(TENANT_ID, strTenantId);
|
||||
checkParameter(DEVICE_ID, strDeviceId);
|
||||
try {
|
||||
DeviceId deviceId = new DeviceId(toUUID(strDeviceId));
|
||||
Device device = checkDeviceId(deviceId, Operation.WRITE);
|
||||
Device device = checkDeviceId(deviceId, Operation.ASSIGN_TO_TENANT);
|
||||
|
||||
TenantId newTenantId = new TenantId(toUUID(strTenantId));
|
||||
Tenant newTenant = tenantService.findTenantById(newTenantId);
|
||||
@ -505,36 +505,36 @@ public class DeviceController extends BaseController {
|
||||
throw new ThingsboardException("Could not find the specified Tenant!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
|
||||
}
|
||||
|
||||
Device swappedDevice = deviceService.swapDevice(newTenantId, device);
|
||||
Device assignedDevice = deviceService.assignDeviceToTenant(newTenantId, device);
|
||||
|
||||
logEntityAction(getCurrentUser(), deviceId, swappedDevice,
|
||||
swappedDevice.getCustomerId(),
|
||||
ActionType.SWAPPED_TO_TENANT, null, strTenantId, newTenant.getName());
|
||||
logEntityAction(getCurrentUser(), deviceId, assignedDevice,
|
||||
assignedDevice.getCustomerId(),
|
||||
ActionType.ASSIGNED_TO_TENANT, null, strTenantId, newTenant.getName());
|
||||
|
||||
Tenant currentTenant = tenantService.findTenantById(getTenantId());
|
||||
pushSwappedFromNotification(currentTenant, newTenantId, swappedDevice);
|
||||
pushAssignedFromNotification(currentTenant, newTenantId, assignedDevice);
|
||||
|
||||
return swappedDevice;
|
||||
return assignedDevice;
|
||||
} catch (Exception e) {
|
||||
logEntityAction(getCurrentUser(), emptyId(EntityType.DEVICE), null,
|
||||
null,
|
||||
ActionType.SWAPPED_TO_TENANT, e, strTenantId);
|
||||
ActionType.ASSIGNED_TO_TENANT, e, strTenantId);
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void pushSwappedFromNotification(Tenant currentTenant, TenantId newTenantId, Device swappedDevice) {
|
||||
String data = entityToStr(swappedDevice);
|
||||
private void pushAssignedFromNotification(Tenant currentTenant, TenantId newTenantId, Device assignedDevice) {
|
||||
String data = entityToStr(assignedDevice);
|
||||
if (data != null) {
|
||||
TbMsg tbMsg = TbMsg.newMsg(DataConstants.ENTITY_SWAPPED_FROM, swappedDevice.getId(), getMetaDataForSwappedFrom(currentTenant), TbMsgDataType.JSON, data);
|
||||
tbClusterService.pushMsgToRuleEngine(newTenantId, swappedDevice.getId(), tbMsg, null);
|
||||
TbMsg tbMsg = TbMsg.newMsg(DataConstants.ENTITY_ASSIGNED_FROM_TENANT, assignedDevice.getId(), getMetaDataForAssignedFrom(currentTenant), TbMsgDataType.JSON, data);
|
||||
tbClusterService.pushMsgToRuleEngine(newTenantId, assignedDevice.getId(), tbMsg, null);
|
||||
}
|
||||
}
|
||||
|
||||
private TbMsgMetaData getMetaDataForSwappedFrom(Tenant tenant) {
|
||||
private TbMsgMetaData getMetaDataForAssignedFrom(Tenant tenant) {
|
||||
TbMsgMetaData metaData = new TbMsgMetaData();
|
||||
metaData.putValue("swappedFromTenantId", tenant.getId().getId().toString());
|
||||
metaData.putValue("swappedFromTenantName", tenant.getName());
|
||||
metaData.putValue("assignedFromTenantId", tenant.getId().getId().toString());
|
||||
metaData.putValue("assignedFromTenantName", tenant.getName());
|
||||
return metaData;
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,6 +18,6 @@ package org.thingsboard.server.service.security.permission;
|
||||
public enum Operation {
|
||||
|
||||
ALL, CREATE, READ, WRITE, DELETE, ASSIGN_TO_CUSTOMER, UNASSIGN_FROM_CUSTOMER, RPC_CALL,
|
||||
READ_CREDENTIALS, WRITE_CREDENTIALS, READ_ATTRIBUTES, WRITE_ATTRIBUTES, READ_TELEMETRY, WRITE_TELEMETRY, CLAIM_DEVICES
|
||||
READ_CREDENTIALS, WRITE_CREDENTIALS, READ_ATTRIBUTES, WRITE_ATTRIBUTES, READ_TELEMETRY, WRITE_TELEMETRY, CLAIM_DEVICES, ASSIGN_TO_TENANT
|
||||
|
||||
}
|
||||
|
||||
@ -800,7 +800,7 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSwapDeviceFromOneTenantToAnother() throws Exception {
|
||||
public void testAssignDeviceToTenant() throws Exception {
|
||||
Device device = new Device();
|
||||
device.setName("My device");
|
||||
device.setType("default");
|
||||
@ -816,7 +816,7 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
|
||||
relation.setTo(savedAnotherDevice.getId());
|
||||
relation.setTypeGroup(RelationTypeGroup.COMMON);
|
||||
relation.setType("Contains");
|
||||
doPost("/api/relation", relation);
|
||||
doPost("/api/relation", relation).andExpect(status().isOk());
|
||||
|
||||
loginSysAdmin();
|
||||
Tenant tenant = new Tenant();
|
||||
@ -834,13 +834,13 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
|
||||
createUserAndLogin(user, "testPassword1");
|
||||
|
||||
login("tenant2@thingsboard.org", "testPassword1");
|
||||
Device swappedDevice = doPost("/api/tenant/" + savedDifferentTenant.getId().getId() + "/device/" + savedDevice.getId().getId(), Device.class);
|
||||
Device assignedDevice = doPost("/api/tenant/" + savedDifferentTenant.getId().getId() + "/device/" + savedDevice.getId().getId(), Device.class);
|
||||
|
||||
doGet("/api/device/" + swappedDevice.getId().getId().toString(), Device.class, status().isNotFound());
|
||||
doGet("/api/device/" + assignedDevice.getId().getId().toString(), Device.class, status().isNotFound());
|
||||
|
||||
login("tenant9@thingsboard.org", "testPassword1");
|
||||
|
||||
Device foundDevice1 = doGet("/api/device/" + swappedDevice.getId().getId().toString(), Device.class);
|
||||
Device foundDevice1 = doGet("/api/device/" + assignedDevice.getId().getId().toString(), Device.class);
|
||||
Assert.assertNotNull(foundDevice1);
|
||||
|
||||
doGet("/api/relation?fromId=" + savedDevice.getId().getId() + "&fromType=DEVICE&relationType=Contains&toId=" + savedAnotherDevice.getId().getId() + "&toType=DEVICE", EntityRelation.class, status().isNotFound());
|
||||
|
||||
@ -47,6 +47,4 @@ public interface AuditLogService {
|
||||
E entity,
|
||||
ActionType actionType,
|
||||
Exception e, Object... additionalInfo);
|
||||
|
||||
void removeAuditLogs(TenantId tenantId, EntityId entityId);
|
||||
}
|
||||
|
||||
@ -65,6 +65,6 @@ public interface DeviceService {
|
||||
|
||||
ListenableFuture<List<EntitySubtype>> findDeviceTypesByTenantId(TenantId tenantId);
|
||||
|
||||
Device swapDevice(TenantId tenantId, Device device);
|
||||
Device assignDeviceToTenant(TenantId tenantId, Device device);
|
||||
|
||||
}
|
||||
|
||||
@ -57,8 +57,8 @@ public class DataConstants {
|
||||
public static final String ATTRIBUTES_DELETED = "ATTRIBUTES_DELETED";
|
||||
public static final String ALARM_ACK = "ALARM_ACK";
|
||||
public static final String ALARM_CLEAR = "ALARM_CLEAR";
|
||||
public static final String ENTITY_SWAPPED_FROM = "ENTITY_SWAPPED_FROM";
|
||||
public static final String ENTITY_SWAPPED_TO = "ENTITY_SWAPPED_TO";
|
||||
public static final String ENTITY_ASSIGNED_FROM_TENANT = "ENTITY_ASSIGNED_FROM_TENANT";
|
||||
public static final String ENTITY_ASSIGNED_TO_TENANT = "ENTITY_ASSIGNED_TO_TENANT";
|
||||
|
||||
public static final String RPC_CALL_FROM_SERVER_TO_DEVICE = "RPC_CALL_FROM_SERVER_TO_DEVICE";
|
||||
|
||||
|
||||
@ -41,8 +41,8 @@ public enum ActionType {
|
||||
LOGIN(false),
|
||||
LOGOUT(false),
|
||||
LOCKOUT(false),
|
||||
SWAPPED_FROM_TENANT(false),
|
||||
SWAPPED_TO_TENANT(false);
|
||||
ASSIGNED_FROM_TENANT(false),
|
||||
ASSIGNED_TO_TENANT(false);
|
||||
|
||||
private final boolean isRead;
|
||||
|
||||
|
||||
@ -159,7 +159,7 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ
|
||||
try {
|
||||
List<EntityView> entityViews = entityViewService.findEntityViewsByTenantIdAndEntityIdAsync(asset.getTenantId(), assetId).get();
|
||||
if (entityViews != null && !entityViews.isEmpty()) {
|
||||
throw new DataValidationException("Can't delete asset that is assigned to entity views!");
|
||||
throw new DataValidationException("Can't delete asset that has entity views!");
|
||||
}
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
log.error("Exception while finding entity views for assetId [{}]", assetId, e);
|
||||
|
||||
@ -51,8 +51,6 @@ import org.thingsboard.server.dao.service.DataValidator;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.thingsboard.server.dao.service.Validator.validateEntityId;
|
||||
@ -158,25 +156,6 @@ public class AuditLogServiceImpl implements AuditLogService {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAuditLogs(TenantId tenantId, EntityId entityId) {
|
||||
List<AuditLog> auditLogs = new ArrayList<>();
|
||||
TimePageData<AuditLog> auditLogPageData;
|
||||
TimePageLink auditLogPageLink = new TimePageLink(1000);
|
||||
do {
|
||||
auditLogPageData = findAuditLogsByTenantIdAndEntityId(tenantId, entityId,
|
||||
new ArrayList<>(Arrays.asList(ActionType.values())), auditLogPageLink);
|
||||
auditLogs.addAll(auditLogPageData.getData());
|
||||
if (auditLogPageData.hasNext()) {
|
||||
auditLogPageLink = auditLogPageData.getNextPageLink();
|
||||
}
|
||||
} while (auditLogPageData.hasNext());
|
||||
|
||||
for (AuditLog auditLog : auditLogs) {
|
||||
auditLogDao.removeById(tenantId, auditLog.getUuidId());
|
||||
}
|
||||
}
|
||||
|
||||
private <E extends HasName, I extends EntityId> JsonNode constructActionData(I entityId, E entity,
|
||||
ActionType actionType,
|
||||
Object... additionalInfo) {
|
||||
@ -187,7 +166,7 @@ public class AuditLogServiceImpl implements AuditLogService {
|
||||
case ALARM_ACK:
|
||||
case ALARM_CLEAR:
|
||||
case RELATIONS_DELETED:
|
||||
case SWAPPED_TO_TENANT:
|
||||
case ASSIGNED_TO_TENANT:
|
||||
if (entity != null) {
|
||||
ObjectNode entityNode = objectMapper.valueToTree(entity);
|
||||
if (entityId.getEntityType() == EntityType.DASHBOARD) {
|
||||
|
||||
@ -58,8 +58,4 @@ public class DummyAuditLogServiceImpl implements AuditLogService {
|
||||
public <E extends HasName, I extends 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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAuditLogs(TenantId tenantId, EntityId entityId) {
|
||||
}
|
||||
}
|
||||
|
||||
@ -48,7 +48,6 @@ import org.thingsboard.server.common.data.relation.EntityRelation;
|
||||
import org.thingsboard.server.common.data.relation.EntitySearchDirection;
|
||||
import org.thingsboard.server.common.data.security.DeviceCredentials;
|
||||
import org.thingsboard.server.common.data.security.DeviceCredentialsType;
|
||||
import org.thingsboard.server.dao.audit.AuditLogService;
|
||||
import org.thingsboard.server.dao.customer.CustomerDao;
|
||||
import org.thingsboard.server.dao.entity.AbstractEntityService;
|
||||
import org.thingsboard.server.dao.entityview.EntityViewService;
|
||||
@ -104,9 +103,6 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
|
||||
@Autowired
|
||||
private EventService eventService;
|
||||
|
||||
@Autowired
|
||||
private AuditLogService auditLogService;
|
||||
|
||||
@Override
|
||||
public Device findDeviceById(TenantId tenantId, DeviceId deviceId) {
|
||||
log.trace("Executing findDeviceById [{}]", deviceId);
|
||||
@ -201,7 +197,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
|
||||
try {
|
||||
List<EntityView> entityViews = entityViewService.findEntityViewsByTenantIdAndEntityIdAsync(device.getTenantId(), deviceId).get();
|
||||
if (entityViews != null && !entityViews.isEmpty()) {
|
||||
throw new DataValidationException("Can't delete device that is assigned to entity views!");
|
||||
throw new DataValidationException("Can't delete device that has entity views!");
|
||||
}
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
log.error("Exception while finding entity views for deviceId [{}]", deviceId, e);
|
||||
@ -338,13 +334,13 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
|
||||
@Transactional
|
||||
@CacheEvict(cacheNames = DEVICE_CACHE, key = "{#device.tenantId, #device.name}")
|
||||
@Override
|
||||
public Device swapDevice(TenantId tenantId, Device device) {
|
||||
log.trace("Executing swapDevice [{}]", device);
|
||||
public Device assignDeviceToTenant(TenantId tenantId, Device device) {
|
||||
log.trace("Executing assignDeviceToTenant [{}][{}]", tenantId, device);
|
||||
|
||||
try {
|
||||
List<EntityView> entityViews = entityViewService.findEntityViewsByTenantIdAndEntityIdAsync(device.getTenantId(), device.getId()).get();
|
||||
if (!CollectionUtils.isEmpty(entityViews)) {
|
||||
throw new DataValidationException("Can't swap device that is assigned to entity views!");
|
||||
throw new DataValidationException("Can't assign device that has entity views to another tenant!");
|
||||
}
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
log.error("Exception while finding entity views for deviceId [{}]", device.getId(), e);
|
||||
@ -355,11 +351,6 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
|
||||
|
||||
relationService.removeRelations(device.getTenantId(), device.getId());
|
||||
|
||||
// TODO: 30/07/2020 implement for Cassandra
|
||||
if (sqlDatabaseUsed) {
|
||||
auditLogService.removeAuditLogs(device.getTenantId(), device.getId());
|
||||
}
|
||||
|
||||
device.setTenantId(tenantId);
|
||||
device.setCustomerId(null);
|
||||
return doSaveDevice(device, null);
|
||||
|
||||
@ -28,7 +28,6 @@ import org.thingsboard.server.common.data.page.TimePageLink;
|
||||
import org.thingsboard.server.dao.exception.DataValidationException;
|
||||
import org.thingsboard.server.dao.service.DataValidator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@ -97,20 +96,17 @@ public class BaseEventService implements EventService {
|
||||
|
||||
@Override
|
||||
public void removeEvents(TenantId tenantId, EntityId entityId) {
|
||||
List<Event> events = new ArrayList<>();
|
||||
TimePageData<Event> eventPageData;
|
||||
TimePageLink eventPageLink = new TimePageLink(1000);
|
||||
do {
|
||||
eventPageData = findEvents(tenantId, entityId, eventPageLink);
|
||||
events.addAll(eventPageData.getData());
|
||||
for (Event event : eventPageData.getData()) {
|
||||
eventDao.removeById(tenantId, event.getUuidId());
|
||||
}
|
||||
if (eventPageData.hasNext()) {
|
||||
eventPageLink = eventPageData.getNextPageLink();
|
||||
}
|
||||
} while (eventPageData.hasNext());
|
||||
|
||||
for (Event event : events) {
|
||||
eventDao.removeById(tenantId, event.getUuidId());
|
||||
}
|
||||
}
|
||||
|
||||
private DataValidator<Event> eventValidator =
|
||||
|
||||
@ -35,7 +35,7 @@ import org.thingsboard.server.common.msg.session.SessionMsgType;
|
||||
configClazz = EmptyNodeConfiguration.class,
|
||||
relationTypes = {"Post attributes", "Post telemetry", "RPC Request from Device", "RPC Request to Device", "Activity Event", "Inactivity Event",
|
||||
"Connect Event", "Disconnect Event", "Entity Created", "Entity Updated", "Entity Deleted", "Entity Assigned",
|
||||
"Entity Unassigned", "Attributes Updated", "Attributes Deleted", "Alarm Acknowledged", "Alarm Cleared", "Other", "Entity Swapped From", "Entity Swapped To"},
|
||||
"Entity Unassigned", "Attributes Updated", "Attributes Deleted", "Alarm Acknowledged", "Alarm Cleared", "Other", "Entity Assigned From Tenant", "Entity Assigned To Tenant"},
|
||||
nodeDescription = "Route incoming messages by Message Type",
|
||||
nodeDetails = "Sends messages with message types <b>\"Post attributes\", \"Post telemetry\", \"RPC Request\"</b> etc. via corresponding chain, otherwise <b>Other</b> chain is used.",
|
||||
uiResources = {"static/rulenode/rulenode-core-config.js"},
|
||||
@ -86,10 +86,10 @@ public class TbMsgTypeSwitchNode implements TbNode {
|
||||
relationType = "Alarm Cleared";
|
||||
} else if (msg.getType().equals(DataConstants.RPC_CALL_FROM_SERVER_TO_DEVICE)) {
|
||||
relationType = "RPC Request to Device";
|
||||
} else if (msg.getType().equals(DataConstants.ENTITY_SWAPPED_FROM)) {
|
||||
relationType = "Entity Swapped From";
|
||||
} else if (msg.getType().equals(DataConstants.ENTITY_SWAPPED_TO)) {
|
||||
relationType = "Entity Swapped To";
|
||||
} else if (msg.getType().equals(DataConstants.ENTITY_ASSIGNED_FROM_TENANT)) {
|
||||
relationType = "Entity Assigned From Tenant";
|
||||
} else if (msg.getType().equals(DataConstants.ENTITY_ASSIGNED_TO_TENANT)) {
|
||||
relationType = "Entity Assigned To Tenant";
|
||||
} else {
|
||||
relationType = "Other";
|
||||
}
|
||||
|
||||
@ -223,11 +223,11 @@ export default angular.module('thingsboard.types', [])
|
||||
"LOCKOUT": {
|
||||
name: "audit-log.type-lockout"
|
||||
},
|
||||
"SWAPPED_FROM_TENANT": {
|
||||
name: "audit-log.type-swapped-from-tenant"
|
||||
"ASSIGNED_FROM_TENANT": {
|
||||
name: "audit-log.type-assigned-from-tenant"
|
||||
},
|
||||
"SWAPPED_TO_TENANT": {
|
||||
name: "audit-log.type-swapped-to-tenant"
|
||||
"ASSIGNED_TO_TENANT": {
|
||||
name: "audit-log.type-assigned-to-tenant"
|
||||
}
|
||||
},
|
||||
auditLogActionStatus: {
|
||||
|
||||
@ -357,8 +357,8 @@
|
||||
"failure-details": "Failure details",
|
||||
"search": "Search audit logs",
|
||||
"clear-search": "Clear search",
|
||||
"type-swapped-from-tenant": "Swapped from Tenant",
|
||||
"type-swapped-to-tenant": "Swapped to Tenant"
|
||||
"type-assigned-from-tenant": "Assigned from Tenant",
|
||||
"type-assigned-to-tenant": "Assigned to Tenant"
|
||||
},
|
||||
"confirm-on-exit": {
|
||||
"message": "You have unsaved changes. Are you sure you want to leave this page?",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user