Merge branch 'develop/3.4' into refactoring_tests_alarm_customer

refactoring: alarm resolving the conflict
This commit is contained in:
nickAS21 2022-06-15 12:24:29 +03:00
commit eceec2af0c
23 changed files with 2730 additions and 1927 deletions

View File

@ -19,6 +19,8 @@ server:
address: "${HTTP_BIND_ADDRESS:0.0.0.0}"
# Server bind port
port: "${HTTP_BIND_PORT:8080}"
# Server forward headers strategy
forward_headers_strategy: "${HTTP_FORWARD_HEADERS_STRATEGY:NONE}"
# Server SSL configuration
ssl:
# Enable/disable SSL support

View File

@ -32,8 +32,6 @@ import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.dao.audit.AuditLogService;
import org.thingsboard.server.dao.model.ModelConstants;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Locale;
import static org.mockito.Mockito.never;
@ -49,40 +47,47 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest {
@SpyBean
protected AuditLogService auditLogService;
protected void testNotifyEntityOne(HasName entity, EntityId entityId, EntityId originatorId,
TenantId tenantId, CustomerId customerId, UserId userId, String userName,
ActionType actionType, Object... additionalInfo) {
testSendNotificationMsgToEdgeServiceOne(entityId, tenantId, actionType);
testLogEntityActionOne(entity, originatorId, tenantId, customerId, userId, userName, actionType, additionalInfo);
testPushMsgToRuleEngineOne(originatorId, tenantId);
protected void testNotifyEntityAllOneTime(HasName entity, EntityId entityId, EntityId originatorId,
TenantId tenantId, CustomerId customerId, UserId userId, String userName,
ActionType actionType, Object... additionalInfo) {
testSendNotificationMsgToEdgeServiceOneTime(entityId, tenantId, actionType);
testLogEntityActionOneTime(entity, originatorId, tenantId, customerId, userId, userName, actionType, additionalInfo);
testPushMsgToRuleEngineOneTime(originatorId, tenantId);
Mockito.reset(tbClusterService, auditLogService);
}
protected void testNotifyEntityDeleteOneMsgToEdgeServiceNever(HasName entity, EntityId entityId, EntityId originatorId,
TenantId tenantId, CustomerId customerId, UserId userId, String userName,
ActionType actionType, Object... additionalInfo) {
protected void testNotifyEntityDeleteOneTimeMsgToEdgeServiceNever(HasName entity, EntityId entityId, EntityId originatorId,
TenantId tenantId, CustomerId customerId, UserId userId, String userName,
ActionType actionType, Object... additionalInfo) {
testNotificationMsgToEdgeServiceNever(entityId);
testLogEntityActionOne(entity, originatorId, tenantId, customerId, userId, userName, actionType, additionalInfo);
testPushMsgToRuleEngineOne(entityId, tenantId);
testBroadcastEntityStateChangeEventOne(entityId, tenantId);
testLogEntityActionOneTime(entity, originatorId, tenantId, customerId, userId, userName, actionType, additionalInfo);
testPushMsgToRuleEngineOneTime(entityId, tenantId);
testBroadcastEntityStateChangeEventOneTime(entityId, tenantId);
Mockito.reset(tbClusterService, auditLogService);
}
protected void testNotifyEntityOneMsgToEdgeServiceNever(HasName entity, EntityId entityId, EntityId originatorId,
TenantId tenantId, CustomerId customerId, UserId userId, String userName,
ActionType actionType, Object... additionalInfo) {
testNotificationMsgToEdgeServiceNever(entityId);
testLogEntityActionOne(entity, originatorId, tenantId, customerId, userId, userName, actionType, additionalInfo);
testPushMsgToRuleEngineOne(originatorId, tenantId);
protected void testNotifyEntityNeverMsgToEdgeServiceOneTime(HasName entity, EntityId entityId, TenantId tenantId, ActionType actionType) {
testSendNotificationMsgToEdgeServiceOneTime(entityId, tenantId, actionType);
testLogEntityActionNever(entityId, entity);
testPushMsgToRuleEngineNever(entityId);
Mockito.reset(tbClusterService, auditLogService);
}
protected void testNotifyEntityBroadcastEntityStateChangeEventOneMsgToEdgeServiceNever(HasName entity, EntityId entityId, EntityId originatorId,
TenantId tenantId, CustomerId customerId, UserId userId, String userName,
ActionType actionType, Object... additionalInfo) {
protected void testNotifyEntityOneTimeMsgToEdgeServiceNever(HasName entity, EntityId entityId, EntityId originatorId,
TenantId tenantId, CustomerId customerId, UserId userId, String userName,
ActionType actionType, Object... additionalInfo) {
testNotificationMsgToEdgeServiceNever(entityId);
testLogEntityActionOne(entity, originatorId, tenantId, customerId, userId, userName, actionType, additionalInfo);
testPushMsgToRuleEngineOne(originatorId, tenantId);
testBroadcastEntityStateChangeEventOne(entityId, tenantId);
testLogEntityActionOneTime(entity, originatorId, tenantId, customerId, userId, userName, actionType, additionalInfo);
testPushMsgToRuleEngineOneTime(originatorId, tenantId);
Mockito.reset(tbClusterService, auditLogService);
}
protected void testNotifyEntityBroadcastEntityStateChangeEventOneTimeMsgToEdgeServiceNever(HasName entity, EntityId entityId, EntityId originatorId,
TenantId tenantId, CustomerId customerId, UserId userId, String userName,
ActionType actionType, Object... additionalInfo) {
testNotificationMsgToEdgeServiceNever(entityId);
testLogEntityActionOneTime(entity, originatorId, tenantId, customerId, userId, userName, actionType, additionalInfo);
testPushMsgToRuleEngineOneTime(originatorId, tenantId);
testBroadcastEntityStateChangeEventOneTime(entityId, tenantId);
Mockito.reset(tbClusterService, auditLogService);
}
@ -107,8 +112,7 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest {
Mockito.argThat(argument ->
argument.getMessage().equals(exp.getMessage())));
}
Mockito.verify(tbClusterService, never()).pushMsgToRuleEngine(Mockito.any(), Mockito.any(entity_NULL_UUID.getClass()),
Mockito.any(), Mockito.any());
testPushMsgToRuleEngineNever(entity_NULL_UUID);
Mockito.reset(tbClusterService, auditLogService);
}
@ -116,26 +120,27 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest {
testNotificationMsgToEdgeServiceNever(entityId);
testLogEntityActionNever(entityId, entity);
testPushMsgToRuleEngineNever(entityId);
Mockito.reset(tbClusterService, auditLogService);
}
protected void testNotificationMsgToEdgeServiceNever(EntityId entityId) {
private void testNotificationMsgToEdgeServiceNever(EntityId entityId) {
Mockito.verify(tbClusterService, never()).sendNotificationMsgToEdgeService(Mockito.any(),
Mockito.any(), Mockito.any(entityId.getClass()), Mockito.any(), Mockito.any(), Mockito.any());
}
protected void testLogEntityActionNever(EntityId entityId, HasName entity) {
private void testLogEntityActionNever(EntityId entityId, HasName entity) {
Mockito.verify(auditLogService, never()).logEntityAction(Mockito.any(), Mockito.any(),
Mockito.any(), Mockito.any(), Mockito.any(entityId.getClass()), Mockito.any(entity.getClass()),
Mockito.any(), Mockito.any());
}
protected void testPushMsgToRuleEngineNever(EntityId entityId) {
private void testPushMsgToRuleEngineNever(EntityId entityId) {
Mockito.verify(tbClusterService, never()).pushMsgToRuleEngine(Mockito.any(),
Mockito.any(entityId.getClass()), Mockito.any(), Mockito.any());
}
private void testLogEntityActionOne(HasName entity, EntityId originatorId, TenantId tenantId, CustomerId customerId,
UserId userId, String userName, ActionType actionType, Object... additionalInfo) {
private void testLogEntityActionOneTime(HasName entity, EntityId originatorId, TenantId tenantId, CustomerId customerId,
UserId userId, String userName, ActionType actionType, Object... additionalInfo) {
if (additionalInfo.length == 0) {
Mockito.verify(auditLogService, times(1)).logEntityAction(Mockito.eq(tenantId), Mockito.eq(customerId),
Mockito.eq(userId), Mockito.eq(userName), Mockito.eq(originatorId),
@ -148,18 +153,18 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest {
}
}
private void testPushMsgToRuleEngineOne(EntityId originatorId, TenantId tenantId) {
private void testPushMsgToRuleEngineOneTime(EntityId originatorId, TenantId tenantId) {
Mockito.verify(tbClusterService, times(1)).pushMsgToRuleEngine(Mockito.eq(tenantId),
Mockito.eq(originatorId), Mockito.any(TbMsg.class), Mockito.isNull());
}
private void testSendNotificationMsgToEdgeServiceOne(EntityId entityId, TenantId tenantId, ActionType actionType) {
private void testSendNotificationMsgToEdgeServiceOneTime(EntityId entityId, TenantId tenantId, ActionType actionType) {
Mockito.verify(tbClusterService, times(1)).sendNotificationMsgToEdgeService(Mockito.eq(tenantId),
Mockito.isNull(), Mockito.eq(entityId), Mockito.isNull(), Mockito.isNull(),
Mockito.eq(edgeTypeByActionType(actionType)));
}
private void testBroadcastEntityStateChangeEventOne(EntityId entityId, TenantId tenantId) {
private void testBroadcastEntityStateChangeEventOneTime(EntityId entityId, TenantId tenantId) {
Mockito.verify(tbClusterService, times(1)).broadcastEntityStateChangeEvent(Mockito.eq(tenantId),
Mockito.any(entityId.getClass()), Mockito.any(ComponentLifecycleEvent.class));
}
@ -174,10 +179,4 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest {
}
return result;
}
private String getFailureStack(Exception e) {
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
return sw.toString();
}
}

View File

@ -107,6 +107,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
protected ObjectMapper mapper = new ObjectMapper();
protected static final String TEST_TENANT_NAME = "TEST TENANT";
protected static final String TEST_DIFFERENT_TENANT_NAME = "TEST DIFFERENT TENANT";
protected static final String SYS_ADMIN_EMAIL = "sysadmin@thingsboard.org";
private static final String SYS_ADMIN_PASSWORD = "sysadmin";
@ -114,9 +115,15 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
protected static final String TENANT_ADMIN_EMAIL = "testtenant@thingsboard.org";
protected static final String TENANT_ADMIN_PASSWORD = "tenant";
protected static final String DIFFERENT_TENANT_ADMIN_EMAIL = "testdifftenant@thingsboard.org";
private static final String DIFFERENT_TENANT_ADMIN_PASSWORD = "difftenant";
protected static final String CUSTOMER_USER_EMAIL = "testcustomer@thingsboard.org";
private static final String CUSTOMER_USER_PASSWORD = "customer";
protected static final String DIFFERENT_CUSTOMER_USER_EMAIL = "testdifferentcustomer@thingsboard.org";
private static final String DIFFERENT_CUSTOMER_USER_PASSWORD = "diffcustomer";
/** See {@link org.springframework.test.web.servlet.DefaultMvcResult#getAsyncResult(long)}
* and {@link org.springframework.mock.web.MockAsyncContext#getTimeout()}
*/
@ -134,6 +141,8 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
protected UserId tenantAdminUserId;
protected CustomerId tenantAdminCustomerId;
protected CustomerId customerId;
protected TenantId differentTenantId;
protected CustomerId differentCustomerId;
protected UserId customerUserId;
@SuppressWarnings("rawtypes")
@ -272,36 +281,52 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
private Customer savedDifferentCustomer;
protected void loginDifferentTenant() throws Exception {
loginSysAdmin();
if (savedDifferentTenant != null) {
deleteDifferentTenant();
login(savedDifferentTenant.getEmail(), TENANT_ADMIN_PASSWORD);
} else {
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle(TEST_DIFFERENT_TENANT_NAME);
savedDifferentTenant = doPost("/api/tenant", tenant, Tenant.class);
differentTenantId = savedDifferentTenant.getId();
Assert.assertNotNull(savedDifferentTenant);
User differentTenantAdmin = new User();
differentTenantAdmin.setAuthority(Authority.TENANT_ADMIN);
differentTenantAdmin.setTenantId(savedDifferentTenant.getId());
differentTenantAdmin.setEmail(DIFFERENT_TENANT_ADMIN_EMAIL);
createUserAndLogin(differentTenantAdmin, DIFFERENT_TENANT_ADMIN_PASSWORD);
}
Tenant tenant = new Tenant();
tenant.setTitle("Different tenant");
savedDifferentTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedDifferentTenant);
User differentTenantAdmin = new User();
differentTenantAdmin.setAuthority(Authority.TENANT_ADMIN);
differentTenantAdmin.setTenantId(savedDifferentTenant.getId());
differentTenantAdmin.setEmail("different_tenant@thingsboard.org");
createUserAndLogin(differentTenantAdmin, "testPassword");
}
protected void loginDifferentCustomer() throws Exception {
if (savedDifferentCustomer != null) {
login(savedDifferentCustomer.getEmail(), CUSTOMER_USER_PASSWORD);
} else {
createDifferentCustomer();
loginTenantAdmin();
User differentCustomerUser = new User();
differentCustomerUser.setAuthority(Authority.CUSTOMER_USER);
differentCustomerUser.setTenantId(tenantId);
differentCustomerUser.setCustomerId(savedDifferentCustomer.getId());
differentCustomerUser.setEmail(DIFFERENT_CUSTOMER_USER_EMAIL);
createUserAndLogin(differentCustomerUser, DIFFERENT_CUSTOMER_USER_PASSWORD);
}
}
protected void createDifferentCustomer() throws Exception {
loginTenantAdmin();
Customer customer = new Customer();
customer.setTitle("Different customer");
savedDifferentCustomer = doPost("/api/customer", customer, Customer.class);
Assert.assertNotNull(savedDifferentCustomer);
User differentCustomerUser = new User();
differentCustomerUser.setAuthority(Authority.CUSTOMER_USER);
differentCustomerUser.setTenantId(tenantId);
differentCustomerUser.setCustomerId(savedDifferentCustomer.getId());
differentCustomerUser.setEmail("different_customer@thingsboard.org");
differentCustomerId = savedDifferentCustomer.getId();
createUserAndLogin(differentCustomerUser, "testPassword");
logout();
}
protected void deleteDifferentTenant() throws Exception {

View File

@ -15,17 +15,26 @@
*/
package org.thingsboard.server.controller;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.extern.slf4j.Slf4j;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.page.PageData;
import java.util.LinkedList;
import java.util.List;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ -65,9 +74,8 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
testNotifyEntityOne(alarm, alarm.getId(), alarm.getOriginator(),
testNotifyEntityAllOneTime(alarm, alarm.getId(), alarm.getOriginator(),
tenantId, customerId, customerUserId, CUSTOMER_USER_EMAIL, ActionType.ADDED);
logout();
}
@Test
@ -78,15 +86,13 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
testNotifyEntityOne(alarm, alarm.getId(), alarm.getOriginator(),
testNotifyEntityAllOneTime(alarm, alarm.getId(), alarm.getOriginator(),
tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ADDED);
logout();
}
@Test
public void testUpdateAlarmViaCustomer() throws Exception {
loginCustomerUser();
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
Mockito.reset(tbClusterService, auditLogService);
@ -96,15 +102,13 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
Assert.assertNotNull(updatedAlarm);
Assert.assertEquals(AlarmSeverity.MAJOR, updatedAlarm.getSeverity());
testNotifyEntityOne(alarm, alarm.getId(), alarm.getOriginator(),
testNotifyEntityAllOneTime(updatedAlarm, updatedAlarm.getId(), updatedAlarm.getOriginator(),
tenantId, customerId, customerUserId, CUSTOMER_USER_EMAIL, ActionType.UPDATED);
logout();
}
@Test
public void testUpdateAlarmViaTenant() throws Exception {
loginTenantAdmin();
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
Mockito.reset(tbClusterService, auditLogService);
@ -114,15 +118,13 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
Assert.assertNotNull(updatedAlarm);
Assert.assertEquals(AlarmSeverity.MAJOR, updatedAlarm.getSeverity());
testNotifyEntityOne(updatedAlarm, updatedAlarm.getId(), updatedAlarm.getOriginator(),
testNotifyEntityAllOneTime(updatedAlarm, updatedAlarm.getId(), updatedAlarm.getOriginator(),
tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.UPDATED);
logout();
}
@Test
public void testUpdateAlarmViaDifferentTenant() throws Exception {
loginTenantAdmin();
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
alarm.setSeverity(AlarmSeverity.MAJOR);
@ -133,13 +135,11 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
doPost("/api/alarm", alarm).andExpect(status().isForbidden());
testNotifyEntityNever(alarm.getId(), alarm);
logout();
}
@Test
public void testUpdateAlarmViaDifferentCustomer() throws Exception {
loginCustomerUser();
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
loginDifferentCustomer();
@ -150,43 +150,37 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
doPost("/api/alarm", alarm).andExpect(status().isForbidden());
testNotifyEntityNever(alarm.getId(), alarm);
logout();
}
@Test
public void testDeleteAlarmViaCustomer() throws Exception {
loginCustomerUser();
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
Mockito.reset(tbClusterService, auditLogService);
doDelete("/api/alarm/" + alarm.getId()).andExpect(status().isOk());
testNotifyEntityOneMsgToEdgeServiceNever(alarm, alarm.getId(), alarm.getOriginator(),
testNotifyEntityOneTimeMsgToEdgeServiceNever(alarm, alarm.getId(), alarm.getOriginator(),
tenantId, customerId, customerUserId, CUSTOMER_USER_EMAIL, ActionType.DELETED);
logout();
}
@Test
public void testDeleteAlarmViaTenant() throws Exception {
loginTenantAdmin();
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
Mockito.reset(tbClusterService, auditLogService);
doDelete("/api/alarm/" + alarm.getId()).andExpect(status().isOk());
testNotifyEntityOneMsgToEdgeServiceNever(alarm, alarm.getId(), alarm.getOriginator(),
testNotifyEntityOneTimeMsgToEdgeServiceNever(alarm, alarm.getId(), alarm.getOriginator(),
tenantId, tenantAdminCustomerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.DELETED);
logout();
}
@Test
public void testDeleteAlarmViaDifferentTenant() throws Exception {
loginTenantAdmin();
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
loginDifferentTenant();
@ -196,14 +190,11 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
doDelete("/api/alarm/" + alarm.getId()).andExpect(status().isForbidden());
testNotifyEntityNever(alarm.getId(), alarm);
logout();
}
@Test
public void testDeleteAlarmViaAnotherCustomer() throws Exception {
public void testDeleteAlarmViaDifferentCustomer() throws Exception {
loginCustomerUser();
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
loginDifferentCustomer();
@ -213,14 +204,11 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
doDelete("/api/alarm/" + alarm.getId()).andExpect(status().isForbidden());
testNotifyEntityNever(alarm.getId(), alarm);
logout();
}
@Test
public void testClearAlarmViaCustomer() throws Exception {
loginCustomerUser();
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
Mockito.reset(tbClusterService, auditLogService);
@ -231,15 +219,13 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
Assert.assertNotNull(foundAlarm);
Assert.assertEquals(AlarmStatus.CLEARED_UNACK, foundAlarm.getStatus());
testNotifyEntityOne(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
tenantId, customerId, customerUserId, CUSTOMER_USER_EMAIL, ActionType.ALARM_CLEAR);
logout();
}
@Test
public void testClearAlarmViaTenant() throws Exception {
loginTenantAdmin();
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
Mockito.reset(tbClusterService, auditLogService);
@ -249,15 +235,13 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
Assert.assertNotNull(foundAlarm);
Assert.assertEquals(AlarmStatus.CLEARED_UNACK, foundAlarm.getStatus());
testNotifyEntityOne(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_CLEAR);
logout();
}
@Test
public void testAcknowledgeAlarmViaCustomer() throws Exception {
loginCustomerUser();
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
Mockito.reset(tbClusterService, auditLogService);
@ -268,15 +252,13 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
Assert.assertNotNull(foundAlarm);
Assert.assertEquals(AlarmStatus.ACTIVE_ACK, foundAlarm.getStatus());
testNotifyEntityOne(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
tenantId, customerId, customerUserId, CUSTOMER_USER_EMAIL, ActionType.ALARM_ACK);
logout();
}
@Test
public void testClearAlarmViaDifferentCustomer() throws Exception {
loginCustomerUser();
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
loginDifferentCustomer();
@ -286,13 +268,11 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
doPost("/api/alarm/" + alarm.getId() + "/clear").andExpect(status().isForbidden());
testNotifyEntityNever(alarm.getId(), alarm);
logout();
}
@Test
public void testClearAlarmViaDifferentTenant() throws Exception {
loginTenantAdmin();
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
loginDifferentTenant();
@ -302,13 +282,11 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
doPost("/api/alarm/" + alarm.getId() + "/clear").andExpect(status().isForbidden());
testNotifyEntityNever(alarm.getId(), alarm);
logout();
}
@Test
public void testAcknowledgeAlarmViaDifferentCustomer() throws Exception {
loginCustomerUser();
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
loginDifferentCustomer();
@ -318,13 +296,11 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
doPost("/api/alarm/" + alarm.getId() + "/ack").andExpect(status().isForbidden());
testNotifyEntityNever(alarm.getId(), alarm);
logout();
}
@Test
public void testAcknowledgeAlarmViaDifferentTenant() throws Exception {
loginTenantAdmin();
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
loginDifferentTenant();
@ -332,9 +308,99 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
Mockito.reset(tbClusterService, auditLogService);
doPost("/api/alarm/" + alarm.getId() + "/ack").andExpect(status().isForbidden());
}
@Test
public void testFindAlarmsViaCustomerUser() throws Exception {
loginCustomerUser();
List<Alarm> createdAlarms = new LinkedList<>();
final int size = 10;
for (int i = 0; i < size; i++) {
createdAlarms.add(
createAlarm(TEST_ALARM_TYPE + i)
);
}
var response = doGetTyped(
"/api/alarm/" + EntityType.DEVICE + "/"
+ customerDevice.getUuidId() + "?page=0&pageSize=" + size,
new TypeReference<PageData<AlarmInfo>>() {}
);
var foundAlarmInfos = response.getData();
Assert.assertNotNull("Found pageData is null", foundAlarmInfos);
Assert.assertNotEquals(
"Expected alarms are not found!",
0, foundAlarmInfos.size()
);
boolean allMatch = createdAlarms.stream()
.allMatch(alarm -> foundAlarmInfos.stream()
.map(Alarm::getType)
.anyMatch(type -> alarm.getType().equals(type))
);
Assert.assertTrue("Created alarm doesn't match any found!", allMatch);
}
@Test
public void testFindAlarmsViaDifferentCustomerUser() throws Exception {
loginCustomerUser();
final int size = 10;
for (int i = 0; i < size; i++) {
createAlarm(TEST_ALARM_TYPE + i);
}
loginDifferentCustomer();
doGet("/api/alarm/" + EntityType.DEVICE + "/"
+ customerDevice.getUuidId() + "?page=0&pageSize=" + size)
.andExpect(status().isForbidden());
}
@Test
public void testFindAlarmsViaPublicCustomer() throws Exception {
loginTenantAdmin();
Device device = new Device();
device.setName("Test Public Device");
device.setLabel("Label");
device.setCustomerId(customerId);
device = doPost("/api/device", device, Device.class);
device = doPost("/api/customer/public/device/" + device.getUuidId(), Device.class);
String publicId = device.getCustomerId().toString();
Alarm alarm = Alarm.builder()
.originator(device.getId())
.status(AlarmStatus.ACTIVE_UNACK)
.severity(AlarmSeverity.CRITICAL)
.type("Test")
.build();
Mockito.reset(tbClusterService, auditLogService);
alarm = doPost("/api/alarm", alarm, Alarm.class);
Assert.assertNotNull("Saved alarm is null!", alarm);
testNotifyEntityNeverMsgToEdgeServiceOneTime(alarm, alarm.getId(), tenantId, ActionType.ADDED);
testNotifyEntityNever(alarm.getId(), alarm);
logout();
JsonNode publicLoginRequest = JacksonUtil.toJsonNode("{\"publicId\": \"" + publicId + "\"}");
JsonNode tokens = doPost("/api/auth/login/public", publicLoginRequest, JsonNode.class);
this.token = tokens.get("token").asText();
PageData<AlarmInfo> pageData = doGetTyped(
"/api/alarm/DEVICE/" + device.getUuidId() + "?page=0&pageSize=1", new TypeReference<PageData<AlarmInfo>>() {}
);
Assert.assertNotNull("Found pageData is null", pageData);
Assert.assertNotEquals("Expected alarms are not found!", 0, pageData.getTotalElements());
AlarmInfo alarmInfo = pageData.getData().get(0);
boolean equals = alarm.getId().equals(alarmInfo.getId()) && alarm.getType().equals(alarmInfo.getType());
Assert.assertTrue("Created alarm doesn't match the found one!", equals);
}
private Alarm createAlarm(String type) throws Exception {
@ -346,8 +412,10 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
.severity(AlarmSeverity.CRITICAL)
.type(type)
.build();
alarm = doPost("/api/alarm", alarm, Alarm.class);
Assert.assertNotNull(alarm);
return alarm;
}
}

View File

@ -94,7 +94,7 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
Customer savedCustomer = doPost("/api/customer", customer, Customer.class);
testNotifyEntityOneMsgToEdgeServiceNever(savedCustomer, savedCustomer.getId(), savedCustomer.getId(),
testNotifyEntityOneTimeMsgToEdgeServiceNever(savedCustomer, savedCustomer.getId(), savedCustomer.getId(),
savedCustomer.getTenantId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(),
ActionType.ADDED);
@ -103,14 +103,11 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
Assert.assertTrue(savedCustomer.getCreatedTime() > 0);
Assert.assertEquals(customer.getTitle(), savedCustomer.getTitle());
savedCustomer.setTitle("My new customer");
Mockito.reset(tbClusterService, auditLogService);
doPost("/api/customer", savedCustomer, Customer.class);
testNotifyEntityOne(savedCustomer, savedCustomer.getId(), savedCustomer.getId(), savedCustomer.getTenantId(),
testNotifyEntityAllOneTime(savedCustomer, savedCustomer.getId(), savedCustomer.getId(), savedCustomer.getTenantId(),
savedCustomer.getId(), tenantAdmin.getId(), tenantAdmin.getEmail(),
ActionType.UPDATED);
@ -119,10 +116,6 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
doDelete("/api/customer/" + savedCustomer.getId().getId().toString())
.andExpect(status().isOk());
testNotifyEntityBroadcastEntityStateChangeEventOneMsgToEdgeServiceNever(savedCustomer, savedCustomer.getId(),
savedCustomer.getId(), savedCustomer.getTenantId(), savedCustomer.getId(), tenantAdmin.getId(),
tenantAdmin.getEmail(), ActionType.DELETED, savedCustomer.getId().getId().toString());
}
@Test
@ -199,14 +192,12 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
deleteDifferentTenant();
login(tenantAdmin.getName(), "testPassword1");
Mockito.reset(tbClusterService, auditLogService);
doDelete("/api/customer/" + savedCustomer.getId().getId().toString())
.andExpect(status().isOk());
testNotifyEntityDeleteOneMsgToEdgeServiceNever(savedCustomer, savedCustomer.getId(), savedCustomer.getId(),
savedCustomer.getTenantId(), savedCustomer.getId(), tenantAdmin.getId(), tenantAdmin.getEmail(),
ActionType.DELETED, savedCustomer.getId().getId().toString());
testNotifyEntityBroadcastEntityStateChangeEventOneTimeMsgToEdgeServiceNever(savedCustomer, savedCustomer.getId(),
savedCustomer.getId(), savedCustomer.getTenantId(), savedCustomer.getId(), tenantAdmin.getId(),
tenantAdmin.getEmail(), ActionType.DELETED, savedCustomer.getId().getId().toString());
}
@Test
@ -223,10 +214,6 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
doDelete("/api/customer/" + savedCustomer.getId().getId().toString())
.andExpect(status().isOk());
testNotifyEntityBroadcastEntityStateChangeEventOneMsgToEdgeServiceNever(savedCustomer, savedCustomer.getId(),
savedCustomer.getId(), savedCustomer.getTenantId(), savedCustomer.getId(), tenantAdmin.getId(),
tenantAdmin.getEmail(), ActionType.DELETED, savedCustomer.getId().getId().toString());
}
@Test
@ -240,7 +227,7 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
doDelete("/api/customer/" + savedCustomer.getId().getId().toString())
.andExpect(status().isOk());
testNotifyEntityBroadcastEntityStateChangeEventOneMsgToEdgeServiceNever(savedCustomer, savedCustomer.getId(),
testNotifyEntityBroadcastEntityStateChangeEventOneTimeMsgToEdgeServiceNever(savedCustomer, savedCustomer.getId(),
savedCustomer.getId(), savedCustomer.getTenantId(), savedCustomer.getId(), tenantAdmin.getId(),
tenantAdmin.getEmail(), ActionType.DELETED, savedCustomer.getId().getId().toString());

View File

@ -12,20 +12,19 @@
"start-prod": "NODE_ENV=production nodemon server.js"
},
"dependencies": {
"@azure/service-bus": "^1.1.9",
"@google-cloud/pubsub": "^2.5.0",
"amqplib": "^0.6.0",
"aws-sdk": "^2.741.0",
"azure-sb": "^0.11.1",
"config": "^3.3.1",
"express": "^4.17.1",
"js-yaml": "^3.14.0",
"kafkajs": "^1.15.0",
"long": "^4.0.0",
"@azure/service-bus": "^7.5.1",
"@google-cloud/pubsub": "^3.0.1",
"amqplib": "^0.10.0",
"aws-sdk": "^2.1152.0",
"config": "^3.3.7",
"express": "^4.18.1",
"js-yaml": "^4.1.0",
"kafkajs": "^2.0.2",
"long": "^5.2.0",
"uuid-parse": "^1.1.0",
"uuid-random": "^1.3.2",
"winston": "^3.3.3",
"winston-daily-rotate-file": "^4.5.0"
"winston": "^3.7.2",
"winston-daily-rotate-file": "^4.7.1"
},
"nyc": {
"exclude": [
@ -36,13 +35,19 @@
]
},
"devDependencies": {
"fs-extra": "^10.0.0",
"nodemon": "^2.0.12",
"pkg": "^5.3.1"
"fs-extra": "^10.1.0",
"nodemon": "^2.0.16",
"pkg": "^5.7.0"
},
"pkg": {
"assets": [
"node_modules/config/**/*.*"
]
},
"resolutions": {
"ansi-regex": "^5.0.1",
"color-string": "^1.5.5",
"minimist": "^1.2.6",
"node-fetch": "^2.6.7"
}
}

View File

@ -71,8 +71,8 @@
<goal>install-node-and-yarn</goal>
</goals>
<configuration>
<nodeVersion>v12.16.1</nodeVersion>
<yarnVersion>v1.22.4</yarnVersion>
<nodeVersion>v16.13.1</nodeVersion>
<yarnVersion>v1.22.17</yarnVersion>
</configuration>
</execution>
<execution>

View File

@ -18,8 +18,7 @@
const config = require('config'),
JsInvokeMessageProcessor = require('../api/jsInvokeMessageProcessor'),
logger = require('../config/logger')._logger('serviceBusTemplate');
const {ServiceBusClient, ReceiveMode} = require("@azure/service-bus");
const azure = require('azure-sb');
const {ServiceBusClient, ServiceBusAdministrationClient} = require("@azure/service-bus");
const requestTopic = config.get('request_topic');
const namespaceName = config.get('service_bus.namespace_name');
@ -28,7 +27,6 @@ const sasKey = config.get('service_bus.sas_key');
const queueProperties = config.get('service_bus.queue_properties');
let sbClient;
let receiverClient;
let receiver;
let serviceBusService;
@ -61,11 +59,10 @@ function ServiceBusProducer() {
}
function CustomSender(topic) {
this.queueClient = sbClient.createQueueClient(topic);
this.sender = this.queueClient.createSender();
this.sender = sbClient.createSender(topic);
this.send = async (message) => {
return this.sender.send(message);
return this.sender.sendMessages(message);
}
}
@ -74,8 +71,8 @@ function CustomSender(topic) {
logger.info('Starting ThingsBoard JavaScript Executor Microservice...');
const connectionString = `Endpoint=sb://${namespaceName}.servicebus.windows.net/;SharedAccessKeyName=${sasKeyName};SharedAccessKey=${sasKey}`;
sbClient = ServiceBusClient.createFromConnectionString(connectionString);
serviceBusService = azure.createServiceBusService(connectionString);
sbClient = new ServiceBusClient(connectionString)
serviceBusService = new ServiceBusAdministrationClient(connectionString);
parseQueueProperties();
@ -84,9 +81,9 @@ function CustomSender(topic) {
if (err) {
reject(err);
} else {
data.forEach(queue => {
queues.push(queue.QueueName);
});
for (const queue of data) {
queues.push(queue.name);
}
resolve();
}
});
@ -97,8 +94,7 @@ function CustomSender(topic) {
queues.push(requestTopic);
}
receiverClient = sbClient.createQueueClient(requestTopic);
receiver = receiverClient.createReceiver(ReceiveMode.peekLock);
receiver = sbClient.createReceiver(requestTopic, {receiveMode: 'peekLock'});
const messageProcessor = new JsInvokeMessageProcessor(new ServiceBusProducer());
@ -111,18 +107,18 @@ function CustomSender(topic) {
const errorHandler = (error) => {
logger.error('Failed to receive message from queue.', error);
};
receiver.registerMessageHandler(messageHandler, errorHandler);
receiver.subscribe({processMessage: messageHandler, processError: errorHandler})
} catch (e) {
logger.error('Failed to start ThingsBoard JavaScript Executor Microservice: %s', e.message);
logger.error(e.stack);
exit(-1);
await exit(-1);
}
})();
async function createQueueIfNotExist(topic) {
return new Promise((resolve, reject) => {
serviceBusService.createQueueIfNotExists(topic, queueOptions, (err) => {
if (err) {
serviceBusService.createQueue(topic, queueOptions, (err) => {
if (err && err.code !== "MessageEntityAlreadyExistsError") {
reject(err);
} else {
resolve();
@ -139,10 +135,10 @@ function parseQueueProperties() {
properties[p.substring(0, delimiterPosition)] = p.substring(delimiterPosition + 1);
});
queueOptions = {
DuplicateDetection: 'false',
MaxSizeInMegabytes: properties['maxSizeInMb'],
DefaultMessageTimeToLive: `PT${properties['messageTimeToLiveInSec']}S`,
LockDuration: `PT${properties['lockDurationInSec']}S`
requiresDuplicateDetection: false,
maxSizeInMegabytes: properties['maxSizeInMb'],
defaultMessageTimeToLive: `PT${properties['messageTimeToLiveInSec']}S`,
lockDuration: `PT${properties['lockDurationInSec']}S`
};
}
@ -161,24 +157,11 @@ async function exit(status) {
}
}
if (receiverClient) {
try {
await receiverClient.close();
} catch (e) {
}
}
senderMap.forEach((k, v) => {
try {
v.sender.close();
} catch (e) {
}
try {
v.queueClient.close();
} catch (e) {
}
});
@ -191,4 +174,4 @@ async function exit(status) {
}
logger.info('Azure Service Bus resources stopped.')
process.exit(status);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -13,14 +13,14 @@
},
"dependencies": {
"compression": "^1.7.4",
"config": "^3.3.1",
"config": "^3.3.7",
"connect-history-api-fallback": "^1.6.0",
"express": "^4.17.1",
"express": "^4.18.1",
"http": "0.0.0",
"http-proxy": "^1.18.1",
"js-yaml": "^3.14.0",
"winston": "^3.3.3",
"winston-daily-rotate-file": "^4.5.0"
"js-yaml": "^4.1.0",
"winston": "^3.7.2",
"winston-daily-rotate-file": "^4.7.1"
},
"nyc": {
"exclude": [
@ -31,13 +31,18 @@
]
},
"devDependencies": {
"fs-extra": "^10.0.0",
"nodemon": "^2.0.12",
"pkg": "^5.3.1"
"fs-extra": "^10.1.0",
"nodemon": "^2.0.16",
"pkg": "^5.7.0"
},
"pkg": {
"assets": [
"node_modules/config/**/*.*"
]
},
"resolutions": {
"color-string": "^1.5.5",
"follow-redirects": "^1.14.8",
"minimist": "^1.2.6"
}
}

View File

@ -80,8 +80,8 @@
<goal>install-node-and-yarn</goal>
</goals>
<configuration>
<nodeVersion>v12.16.1</nodeVersion>
<yarnVersion>v1.22.4</yarnVersion>
<nodeVersion>v16.13.1</nodeVersion>
<yarnVersion>v1.22.17</yarnVersion>
</configuration>
</execution>
<execution>

File diff suppressed because it is too large Load Diff

View File

@ -62,6 +62,7 @@
"leaflet-providers": "^1.13.0",
"leaflet.gridlayer.googlemutant": "^0.13.4",
"leaflet.markercluster": "^1.5.3",
"libphonenumber-js": "^1.10.4",
"messageformat": "^2.3.0",
"moment": "^2.29.1",
"moment-timezone": "^0.5.34",

View File

@ -30,17 +30,11 @@
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
<div mat-dialog-content tb-toast toastTarget="sendTestSmsDialogContent">
<fieldset [disabled]="(isLoading$ | async)">
<mat-form-field class="mat-block">
<mat-label translate>admin.number-to</mat-label>
<input type="tel" required [pattern]="phoneNumberPattern" matInput formControlName="numberTo">
<mat-error *ngIf="sendTestSmsFormGroup.get('numberTo').hasError('required')">
{{ 'admin.number-to-required' | translate }}
</mat-error>
<mat-error *ngIf="sendTestSmsFormGroup.get('numberTo').hasError('pattern')">
{{ 'admin.phone-number-pattern' | translate }}
</mat-error>
<mat-hint innerHTML="{{ 'admin.phone-number-hint' | translate }}"></mat-hint>
</mat-form-field>
<tb-phone-input required
formControlName="numberTo"
[enableFlagsSelect]="false"
[label]="'admin.number-to'">
</tb-phone-input>
<mat-form-field class="mat-block">
<mat-label translate>admin.sms-message</mat-label>
<textarea required matInput rows="3" [maxLength]="1600" formControlName="message"></textarea>

View File

@ -36,21 +36,12 @@
<ng-template matStepLabel>{{ 'security.2fa.dialog.sms-step-label' | translate }}</ng-template>
<form [formGroup]="smsConfigForm" (ngSubmit)="nextStep()">
<p class="mat-body step-description input" translate>security.2fa.dialog.sms-step-description</p>
<div fxLayout="row" fxLayoutAlign="space-between center" fxLayoutGap="8px">
<mat-form-field fxFlex class="mat-block input-container" floatLabel="always" hideRequiredMarker>
<mat-label></mat-label>
<input type="tel" required
[pattern]="phoneNumberPattern"
matInput formControlName="phone"
placeholder="{{ 'security.2fa.dialog.sms-step-label' | translate }}">
<mat-error *ngIf="smsConfigForm.get('phone').hasError('required')">
{{ 'admin.number-to-required' | translate }}
</mat-error>
<mat-error *ngIf="smsConfigForm.get('phone').hasError('pattern')">
{{ 'admin.phone-number-pattern' | translate }}
</mat-error>
<mat-hint innerHTML="{{ 'admin.phone-number-hint' | translate }}"></mat-hint>
</mat-form-field>
<div fxLayout="row" fxLayoutAlign="space-between center" fxLayoutGap="15px">
<tb-phone-input fxFlex
formControlName="phone"
[floatLabel]="'never'"
[placeholder]="'security.2fa.dialog.sms-step-label'">
</tb-phone-input>
<button mat-raised-button
type="submit"
color="primary"

View File

@ -55,13 +55,11 @@
<mat-label translate>contact.address2</mat-label>
<input matInput formControlName="address2">
</mat-form-field>
<mat-form-field class="mat-block">
<mat-label translate>contact.phone</mat-label>
<input matInput formControlName="phone">
<mat-error *ngIf="parentForm.get('phone').hasError('maxlength')">
{{ 'contact.phone-max-length' | translate }}
</mat-error>
</mat-form-field>
<tb-phone-input [required]="false"
[label]="'contact.phone'"
[enableFlagsSelect]="false"
formControlName="phone">
</tb-phone-input>
<mat-form-field class="mat-block">
<mat-label translate>contact.email</mat-label>
<input matInput formControlName="email">

View File

@ -0,0 +1,50 @@
<!--
Copyright © 2016-2022 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.
-->
<form [formGroup]="phoneFormGroup">
<div class="phone-input-container">
<div class="flags-select-container" *ngIf="enableFlagsSelect">
<span class="flag-container">{{ flagIcon }}</span>
<mat-select class="country-select" formControlName="country">
<mat-option *ngFor="let country of allCountries" [value]="country.iso2">
<span style="font-size: 20px;">{{country.flag}}</span>
<span>{{' ' + country.name + ' +' + country.dialCode }}</span>
</mat-option>
</mat-select>
</div>
<mat-form-field class="phone-input" [appearance]="appearance" [floatLabel]="floatLabel">
<mat-label>{{ label | translate }}</mat-label>
<input
formControlName="phoneNumber"
type="tel"
matInput
placeholder="{{ placeholder | translate }}"
[pattern]="phoneNumberPattern"
(focus)="focus()"
autocomplete="off"
[required]="required">
<mat-hint innerHTML="{{ 'phone-input.phone-input-hint' | translate: {phoneNumber: phonePlaceholder} }}"></mat-hint>
<mat-error *ngIf="phoneFormGroup.get('phoneNumber').hasError('required')">
{{ 'phone-input.phone-input-required' | translate }}
</mat-error>
<mat-error *ngIf="phoneFormGroup.get('phoneNumber').hasError('invalidPhoneNumber')">
{{ 'phone-input.phone-input-validation' | translate }}
</mat-error>
</mat-form-field>
</div>
</form>

View File

@ -0,0 +1,55 @@
/**
* Copyright © 2016-2022 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.
*/
:host ::ng-deep {
.phone-input-container {
display: flex;
align-items: center;
.phone-input {
width: 100%;
}
}
.flags-select-container {
display: inline-block;
position: relative;
width: 50px;
height: 100%;
margin-right: 5px;
}
.flag-container {
position: absolute;
font-size: 20px;
top: 50%;
left: 0;
transform: translate(0, -50%);
}
.country-select {
width: 45px;
height: 30px;
.mat-select-trigger {
height: 100%;
width: 100%;
}
.mat-select-value {
visibility: hidden;
}
}
}

View File

@ -0,0 +1,222 @@
///
/// Copyright © 2016-2022 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 { Component, forwardRef, Input, OnInit } from '@angular/core';
import {
ControlValueAccessor,
FormBuilder,
FormControl,
FormGroup,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
ValidationErrors,
Validator,
ValidatorFn,
Validators
} from '@angular/forms';
import { TranslateService } from '@ngx-translate/core';
import { Country, CountryData } from '@shared/models/country.models';
import examples from 'libphonenumber-js/examples.mobile.json';
import { CountryCode, getExampleNumber, parsePhoneNumberFromString } from 'libphonenumber-js';
import { phoneNumberPattern } from '@shared/models/settings.models';
import { Subscription } from 'rxjs';
import { FloatLabelType, MatFormFieldAppearance } from '@angular/material/form-field/form-field';
@Component({
selector: 'tb-phone-input',
templateUrl: './phone-input.component.html',
styleUrls: ['./phone-input.component.scss'],
providers: [
CountryData,
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => PhoneInputComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => PhoneInputComponent),
multi: true
}
]
})
export class PhoneInputComponent implements OnInit, ControlValueAccessor, Validator {
@Input()
disabled: boolean;
@Input()
defaultCountry: CountryCode = 'US';
@Input()
enableFlagsSelect = true;
@Input()
required = true;
@Input()
floatLabel: FloatLabelType = 'auto';
@Input()
appearance: MatFormFieldAppearance = 'legacy';
@Input()
placeholder;
@Input()
label = 'phone-input.phone-input-label';
allCountries: Array<Country> = this.countryCodeData.allCountries;
phonePlaceholder: string;
flagIcon: string;
phoneFormGroup: FormGroup;
phoneNumberPattern = phoneNumberPattern;
private baseCode = 127397;
private countryCallingCode: string;
private modelValue: string;
private valueChange$: Subscription = null;
private propagateChange = (v: any) => { };
constructor(private translate: TranslateService,
private fb: FormBuilder,
private countryCodeData: CountryData) {
}
ngOnInit(): void {
const validators: ValidatorFn[] = [Validators.pattern(phoneNumberPattern), this.validatePhoneNumber()];
if (this.required) {
validators.push(Validators.required);
}
this.phoneFormGroup = this.fb.group({
country: [this.defaultCountry, []],
phoneNumber: [null, validators]
});
this.valueChange$ = this.phoneFormGroup.get('phoneNumber').valueChanges.subscribe(value => {
this.updateModel();
if (value) {
const parsedPhoneNumber = parsePhoneNumberFromString(value);
const country = this.phoneFormGroup.get('country').value;
if (parsedPhoneNumber?.country && parsedPhoneNumber?.country !== country) {
this.phoneFormGroup.get('country').patchValue(parsedPhoneNumber.country, {emitEvent: true});
}
}
});
this.phoneFormGroup.get('country').valueChanges.subscribe(value => {
if (value) {
const code = this.countryCallingCode;
this.getFlagAndPhoneNumberData(value);
let phoneNumber = this.phoneFormGroup.get('phoneNumber').value;
if (phoneNumber) {
if (code !== this.countryCallingCode && phoneNumber.includes(code)) {
phoneNumber = phoneNumber.replace(code, this.countryCallingCode);
this.phoneFormGroup.get('phoneNumber').patchValue(phoneNumber);
}
}
}
});
}
ngOnDestroy() {
if (this.valueChange$) {
this.valueChange$.unsubscribe();
}
}
focus() {
const phoneNumber = this.phoneFormGroup.get('phoneNumber');
this.phoneFormGroup.markAsPristine();
this.phoneFormGroup.markAsUntouched();
if (!phoneNumber.value) {
phoneNumber.patchValue(this.countryCallingCode);
}
}
private getFlagAndPhoneNumberData(country) {
if (this.enableFlagsSelect) {
this.flagIcon = this.getFlagIcon(country);
}
this.getPhoneNumberData(country);
}
private getPhoneNumberData(country): void {
const phoneData = getExampleNumber(country, examples);
this.phonePlaceholder = phoneData.number;
this.countryCallingCode = '+' + phoneData.countryCallingCode;
}
private getFlagIcon(countryCode) {
return String.fromCodePoint(...countryCode.split('').map(country => this.baseCode + country.charCodeAt(0)));
}
validatePhoneNumber(): ValidatorFn {
return (c: FormControl) => {
const phoneNumber = c.value;
if (phoneNumber) {
const parsedPhoneNumber = parsePhoneNumberFromString(phoneNumber);
if (!parsedPhoneNumber?.isValid() || !parsedPhoneNumber?.isPossible()) {
return {
invalidPhoneNumber: {
valid: false
}
};
}
}
return null;
};
}
validate(): ValidationErrors | null {
return this.phoneFormGroup.get('phoneNumber').valid ? null : {
phoneFormGroup: false
};
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (isDisabled) {
this.phoneFormGroup.disable({emitEvent: false});
} else {
this.phoneFormGroup.enable({emitEvent: false});
}
}
writeValue(phoneNumber): void {
this.modelValue = phoneNumber;
const country = phoneNumber ? parsePhoneNumberFromString(phoneNumber)?.country : this.defaultCountry;
this.getFlagAndPhoneNumberData(country);
this.phoneFormGroup.patchValue({phoneNumber, country}, {emitEvent: !phoneNumber});
}
private updateModel() {
const phoneNumber = this.phoneFormGroup.get('phoneNumber');
if (phoneNumber.valid && phoneNumber.value) {
this.modelValue = phoneNumber.value;
this.propagateChange(this.modelValue);
} else {
this.propagateChange(null);
}
}
}

View File

@ -0,0 +1,520 @@
///
/// Copyright © 2016-2022 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 { Injectable } from '@angular/core';
export interface Country {
name: string;
iso2: string;
dialCode: string;
areaCodes?: string[];
flag: string;
}
export enum CountryISO {
Afghanistan = 'AF',
Albania = 'AL',
Algeria = 'DZ',
AmericanSamoa = 'AS',
Andorra = 'AD',
Angola = 'AO',
Anguilla = 'AI',
AntiguaAndBarbuda = 'AG',
Argentina = 'AR',
Armenia = 'AM',
Aruba = 'AW',
Australia = 'AU',
Austria = 'AT',
Azerbaijan = 'AZ',
Bahamas = 'BS',
Bahrain = 'BH',
Bangladesh = 'BD',
Barbados = 'BB',
Belarus = 'BY',
Belgium = 'BE',
Belize = 'BZ',
Benin = 'BJ',
Bermuda = 'BM',
Bhutan = 'BT',
Bolivia = 'BO',
BosniaAndHerzegovina = 'BA',
Botswana = 'BW',
Brazil = 'BR',
BritishIndianOceanTerritory = 'IO',
BritishVirginIslands = 'VG',
Brunei = 'BN',
Bulgaria = 'BG',
BurkinaFaso = 'BF',
Burundi = 'BI',
Cambodia = 'KH',
Cameroon = 'CM',
Canada = 'CA',
CapeVerde = 'CV',
CaribbeanNetherlands = 'BQ',
CaymanIslands = 'KY',
CentralAfricanRepublic = 'CF',
Chad = 'TD',
Chile = 'CL',
China = 'CN',
ChristmasIsland = 'CX',
Cocos = 'CC',
Colombia = 'CC',
Comoros = 'KM',
CongoDRCJamhuriYaKidemokrasiaYaKongo = 'CD',
CongoRepublicCongoBrazzaville = 'CG',
CookIslands = 'CK',
CostaRica = 'CR',
CôteDIvoire = 'CI',
Croatia = 'HR',
Cuba = 'CU',
Curaçao = 'CW',
Cyprus = 'CY',
CzechRepublic = 'CZ',
Denmark = 'DK',
Djibouti = 'DJ',
Dominica = 'DM',
DominicanRepublic = 'DO',
Ecuador = 'EC',
Egypt = 'EG',
ElSalvador = 'SV',
EquatorialGuinea = 'GQ',
Eritrea = 'ER',
Estonia = 'EE',
Ethiopia = 'ET',
FalklandIslands = 'FK',
FaroeIslands = 'FO',
Fiji = 'FJ',
Finland = 'FI',
France = 'FR',
FrenchGuiana = 'GF',
FrenchPolynesia = 'PF',
Gabon = 'GA',
Gambia = 'GM',
Georgia = 'GE',
Germany = 'DE',
Ghana = 'GH',
Gibraltar = 'GI',
Greece = 'GR',
Greenland = 'GL',
Grenada = 'GD',
Guadeloupe = 'GP',
Guam = 'GU',
Guatemala = 'GT',
Guernsey = 'GG',
Guinea = 'GN',
GuineaBissau = 'GW',
Guyana = 'GY',
Haiti = 'HT',
Honduras = 'HN',
HongKong = 'HK',
Hungary = 'HU',
Iceland = 'IS',
India = 'IN',
Indonesia = 'ID',
Iran = 'IR',
Iraq = 'IQ',
Ireland = 'IE',
IsleOfMan = 'IM',
Israel = 'IL',
Italy = 'IT',
Jamaica = 'JM',
Japan = 'JP',
Jersey = 'JE',
Jordan = 'JO',
Kazakhstan = 'KZ',
Kenya = 'KE',
Kiribati = 'KI',
Kosovo = 'XK',
Kuwait = 'KW',
Kyrgyzstan = 'KG',
Laos = 'LA',
Latvia = 'LV',
Lebanon = 'LB',
Lesotho = 'LS',
Liberia = 'LR',
Libya = 'LY',
Liechtenstein = 'LI',
Lithuania = 'LT',
Luxembourg = 'LU',
Macau = 'MO',
Macedonia = 'MK',
Madagascar = 'MG',
Malawi = 'MW',
Malaysia = 'MY',
Maldives = 'MV',
Mali = 'ML',
Malta = 'MT',
MarshallIslands = 'MH',
Martinique = 'MQ',
Mauritania = 'MR',
Mauritius = 'MU',
Mayotte = 'YT',
Mexico = 'MX',
Micronesia = 'FM',
Moldova = 'MD',
Monaco = 'MC',
Mongolia = 'MN',
Montenegro = 'ME',
Montserrat = 'MS',
Morocco = 'MA',
Mozambique = 'MZ',
Myanmar = 'MM',
Namibia = 'NA',
Nauru = 'NR',
Nepal = 'NP',
Netherlands = 'NL',
NewCaledonia = 'NC',
NewZealand = 'NZ',
Nicaragua = 'NI',
Niger = 'NE',
Nigeria = 'NG',
Niue = 'NU',
NorfolkIsland = 'NF',
NorthKorea = 'KP',
NorthernMarianaIslands = 'MP',
Norway = 'NO',
Oman = 'OM',
Pakistan = 'PK',
Palau = 'PW',
Palestine = 'PS',
Panama = 'PA',
PapuaNewGuinea = 'PG',
Paraguay = 'PY',
Peru = 'PE',
Philippines = 'PH',
Poland = 'PL',
Portugal = 'PT',
PuertoRico = 'PR',
Qatar = 'QA',
Réunion = 'RE',
Romania = 'RO',
Russia = 'RU',
Rwanda = 'RW',
SaintBarthélemy = 'BL',
SaintHelena = 'SH',
SaintKittsAndNevis = 'KN',
SaintLucia = 'LC',
SaintMartin = 'MF',
SaintPierreAndMiquelon = 'PM',
SaintVincentAndTheGrenadines = 'VC',
Samoa = 'WS',
SanMarino = 'SM',
SãoToméAndPríncipe = 'ST',
SaudiArabia = 'SA',
Senegal = 'SN',
Serbia = 'RS',
Seychelles = 'SC',
SierraLeone = 'SL',
Singapore = 'SG',
SintMaarten = 'SX',
Slovakia = 'SK',
Slovenia = 'SI',
SolomonIslands = 'SB',
Somalia = 'SO',
SouthAfrica = 'ZA',
SouthKorea = 'KR',
SouthSudan = 'SS',
Spain = 'ES',
SriLanka = 'LK',
Sudan = 'SD',
Suriname = 'SR',
SvalbardAndJanMayen = 'SJ',
Swaziland = 'SZ',
Sweden = 'SE',
Switzerland = 'CH',
Syria = 'SY',
Taiwan = 'TW',
Tajikistan = 'TJ',
Tanzania = 'TZ',
Thailand = 'TH',
TimorLeste = 'TL',
Togo = 'TG',
Tokelau = 'TK',
Tonga = 'TO',
TrinidadAndTobago = 'TT',
Tunisia = 'TN',
Turkey = 'TR',
Turkmenistan = 'TM',
TurksAndCaicosIslands = 'TC',
Tuvalu = 'TV',
USVirginIslands = 'VI',
Uganda = 'UG',
Ukraine = 'UA',
UnitedArabEmirates = 'AE',
UnitedKingdom = 'GB',
UnitedStates = 'US',
Uruguay = 'UY',
Uzbekistan = 'UZ',
Vanuatu = 'VU',
VaticanCity = 'VA',
Venezuela = 'VE',
Vietnam = 'VN',
WallisAndFutuna = 'WF',
WesternSahara = 'EH',
Yemen = 'YE',
Zambia = 'ZM',
Zimbabwe = 'ZW',
ÅlandIslands = 'AX',
}
@Injectable()
export class CountryData {
public allCountries: Array<Country> = [
{name: 'Afghanistan', iso2: CountryISO.Afghanistan, dialCode: '93', flag: '🇦🇫'},
{name: 'Albania', iso2: CountryISO.Albania, dialCode: '355', flag: '🇦🇱'},
{name: 'Algeria', iso2: CountryISO.Algeria, dialCode: '213', flag: '🇩🇿'},
{name: 'American Samoa', iso2: CountryISO.AmericanSamoa, dialCode: '1', flag: '🇦🇸'},
{name: 'Andorra', iso2: CountryISO.Andorra, dialCode: '376', flag: '🇦🇩'},
{name: 'Angola', iso2: CountryISO.Angola, dialCode: '244', flag: '🇦🇴'},
{name: 'Anguilla', iso2: CountryISO.Anguilla, dialCode: '1', flag: '🇦🇮'},
{name: 'Antigua and Barbuda', iso2: CountryISO.AntiguaAndBarbuda, dialCode: '1', flag: '🇦🇬'},
{name: 'Argentina', iso2: CountryISO.Argentina, dialCode: '54', flag: '🇦🇷'},
{name: 'Armenia', iso2: CountryISO.Armenia, dialCode: '374', flag: '🇦🇲'},
{name: 'Aruba', iso2: CountryISO.Aruba, dialCode: '297', flag: '🇦🇼'},
{name: 'Australia', iso2: CountryISO.Australia, dialCode: '61', flag: '🇦🇺'},
{name: 'Austria', iso2: CountryISO.Austria, dialCode: '43', flag: '🇦🇹'},
{name: 'Azerbaijan', iso2: CountryISO.Azerbaijan, dialCode: '994', flag: '🇦🇿'},
{name: 'Bahamas', iso2: CountryISO.Bahamas, dialCode: '1', flag: '🇧🇸'},
{name: 'Bahrain', iso2: CountryISO.Bahrain, dialCode: '973', flag: '🇧🇭'},
{name: 'Bangladesh', iso2: CountryISO.Bangladesh, dialCode: '880', flag: '🇧🇩'},
{name: 'Barbados', iso2: CountryISO.Barbados, dialCode: '1', flag: '🇧🇧'},
{name: 'Belarus', iso2: CountryISO.Belarus, dialCode: '375', flag: '🇧🇾'},
{name: 'Belgium', iso2: CountryISO.Belgium, dialCode: '32', flag: '🇧🇪'},
{name: 'Belize', iso2: CountryISO.Belize, dialCode: '501', flag: '🇧🇿'},
{name: 'Benin', iso2: CountryISO.Benin, dialCode: '229', flag: '🇧🇯'},
{name: 'Bermuda', iso2: CountryISO.Bermuda, dialCode: '1', flag: '🇧🇲'},
{name: 'Bhutan', iso2: CountryISO.Bhutan, dialCode: '975', flag: '🇧🇹'},
{name: 'Bolivia', iso2: CountryISO.Bolivia, dialCode: '591', flag: '🇧🇴'},
{name: 'Bosnia and Herzegovina', iso2: CountryISO.BosniaAndHerzegovina, dialCode: '387', flag: '🇧🇦'},
{name: 'Botswana', iso2: CountryISO.Botswana, dialCode: '267', flag: '🇧🇼'},
{name: 'Brazil', iso2: CountryISO.Brazil, dialCode: '55', flag: '🇧🇷'},
{name: 'British Indian Ocean Territory', iso2: CountryISO.BritishIndianOceanTerritory, dialCode: '246', flag: '🇮🇴'},
{name: 'British Virgin Islands', iso2: CountryISO.BritishVirginIslands, dialCode: '1', flag: '🇻🇬'},
{name: 'Brunei', iso2: CountryISO.Brunei, dialCode: '673', flag: '🇧🇳'},
{name: 'Bulgaria', iso2: CountryISO.Bulgaria, dialCode: '359', flag: '🇧🇬'},
{name: 'Burkina Faso', iso2: CountryISO.BurkinaFaso, dialCode: '226', flag: '🇧🇫'},
{name: 'Burundi', iso2: CountryISO.Burundi, dialCode: '257', flag: '🇧🇮'},
{name: 'Cambodia', iso2: CountryISO.Cambodia, dialCode: '855', flag: '🇰🇭'},
{name: 'Cameroon', iso2: CountryISO.Cameroon, dialCode: '237', flag: '🇨🇲'},
{name: 'Canada', iso2: CountryISO.Canada, dialCode: '1', flag: '🇨🇦'},
{name: 'Cape Verde', iso2: CountryISO.CapeVerde, dialCode: '238', flag: '🇨🇻'},
{name: 'Caribbean Netherlands', iso2: CountryISO.CaribbeanNetherlands, dialCode: '599', flag: '🇧🇶'},
{name: 'Cayman Islands', iso2: CountryISO.CaymanIslands, dialCode: '1', flag: '🇰🇾'},
{name: 'Central African Republic', iso2: CountryISO.CentralAfricanRepublic, dialCode: '236', flag: '🇨🇫'},
{name: 'Chad', iso2: CountryISO.Chad, dialCode: '235', flag: '🇹🇩'},
{name: 'Chile', iso2: CountryISO.Chile, dialCode: '56', flag: '🇨🇱'},
{name: 'China', iso2: CountryISO.China, dialCode: '86', flag: '🇨🇳'},
{name: 'Christmas Island', iso2: CountryISO.ChristmasIsland, dialCode: '61', flag: '🇨🇽'},
{name: 'Cocos Islands', iso2: CountryISO.Cocos, dialCode: '61', flag: '🇨🇨'},
{name: 'Colombia', iso2: CountryISO.Colombia, dialCode: '57', flag: '🇨🇨'},
{name: 'Comoros', iso2: CountryISO.Comoros, dialCode: '269', flag: '🇰🇲'},
{name: 'Congo-Kinshasa', iso2: CountryISO.CongoDRCJamhuriYaKidemokrasiaYaKongo, dialCode: '243', flag: '🇨🇩'},
{name: 'Congo-Brazzaville', iso2: CountryISO.CongoRepublicCongoBrazzaville, dialCode: '242', flag: '🇨🇬'},
{name: 'Cook Islands', iso2: CountryISO.CookIslands, dialCode: '682', flag: '🇨🇰'},
{name: 'Costa Rica', iso2: CountryISO.CostaRica, dialCode: '506', flag: '🇨🇷'},
{name: 'Côte dIvoire', iso2: CountryISO.CôteDIvoire, dialCode: '225', flag: '🇨🇮'},
{name: 'Croatia', iso2: CountryISO.Croatia, dialCode: '385', flag: '🇭🇷'},
{name: 'Cuba', iso2: CountryISO.Cuba, dialCode: '53', flag: '🇨🇺'},
{name: 'Curaçao', iso2: CountryISO.Curaçao, dialCode: '599', flag: '🇨🇼'},
{name: 'Cyprus', iso2: CountryISO.Cyprus, dialCode: '357', flag: '🇨🇾'},
{name: 'Czech Republic', iso2: CountryISO.CzechRepublic, dialCode: '420', flag: '🇨🇿'},
{name: 'Denmark', iso2: CountryISO.Denmark, dialCode: '45', flag: '🇩🇰'},
{name: 'Djibouti', iso2: CountryISO.Djibouti, dialCode: '253', flag: '🇩🇯'},
{name: 'Dominica', iso2: CountryISO.Dominica, dialCode: '1767', flag: '🇩🇲'},
{name: 'Dominican Republic', iso2: CountryISO.DominicanRepublic, dialCode: '1', flag: '🇩🇴'},
{name: 'Ecuador', iso2: CountryISO.Ecuador, dialCode: '593', flag: '🇪🇨'},
{name: 'Egypt', iso2: CountryISO.Egypt, dialCode: '20', flag: '🇪🇬'},
{name: 'El Salvador', iso2: CountryISO.ElSalvador, dialCode: '503', flag: '🇸🇻'},
{name: 'Equatorial Guinea', iso2: CountryISO.EquatorialGuinea, dialCode: '240', flag: '🇬🇶'},
{name: 'Eritrea', iso2: CountryISO.Eritrea, dialCode: '291', flag: '🇪🇷'},
{name: 'Estonia', iso2: CountryISO.Estonia, dialCode: '372', flag: '🇪🇪'},
{name: 'Ethiopia', iso2: CountryISO.Ethiopia, dialCode: '251', flag: '🇪🇹'},
{name: 'Falkland Islands', iso2: CountryISO.FalklandIslands, dialCode: '500', flag: '🇫🇰'},
{name: 'Faroe Islands', iso2: CountryISO.FaroeIslands, dialCode: '298', flag: '🇫🇴'},
{name: 'Fiji', iso2: CountryISO.Fiji, dialCode: '679', flag: '🇫🇯'},
{name: 'Finland', iso2: CountryISO.Finland, dialCode: '358', flag: '🇫🇮'},
{name: 'France', iso2: CountryISO.France, dialCode: '33', flag: '🇫🇷'},
{name: 'French Guiana', iso2: CountryISO.FrenchGuiana, dialCode: '594', flag: '🇬🇫'},
{name: 'French Polynesia', iso2: CountryISO.FrenchPolynesia, dialCode: '689', flag: '🇵🇫'},
{name: 'Gabon', iso2: CountryISO.Gabon, dialCode: '241', flag: '🇬🇦'},
{name: 'Gambia', iso2: CountryISO.Gambia, dialCode: '220', flag: '🇬🇲'},
{name: 'Georgia', iso2: CountryISO.Georgia, dialCode: '995', flag: '🇬🇪'},
{name: 'Germany', iso2: CountryISO.Germany, dialCode: '49', flag: '🇩🇪'},
{name: 'Ghana', iso2: CountryISO.Ghana, dialCode: '233', flag: '🇬🇭'},
{name: 'Gibraltar', iso2: CountryISO.Gibraltar, dialCode: '350', flag: '🇬🇮'},
{name: 'Greece', iso2: CountryISO.Greece, dialCode: '30', flag: '🇬🇷'},
{name: 'Greenland', iso2: CountryISO.Greenland, dialCode: '299', flag: '🇬🇱'},
{name: 'Grenada', iso2: CountryISO.Grenada, dialCode: '1473', flag: '🇬🇩'},
{name: 'Guadeloupe', iso2: CountryISO.Guadeloupe, dialCode: '590', flag: '🇬🇵'},
{name: 'Guam', iso2: CountryISO.Guam, dialCode: '1', flag: '🇬🇺'},
{name: 'Guatemala', iso2: CountryISO.Guatemala, dialCode: '502', flag: '🇬🇹'},
{name: 'Guernsey', iso2: CountryISO.Guernsey, dialCode: '44', flag: '🇬🇬'},
{name: 'Guinea', iso2: CountryISO.Guinea, dialCode: '224', flag: '🇬🇳'},
{name: 'Guinea-Bissau', iso2: CountryISO.GuineaBissau, dialCode: '245', flag: '🇬🇼'},
{name: 'Guyana', iso2: CountryISO.Guyana, dialCode: '592', flag: '🇬🇾'},
{name: 'Haiti', iso2: CountryISO.Haiti, dialCode: '509', flag: '🇭🇹'},
{name: 'Honduras', iso2: CountryISO.Honduras, dialCode: '504', flag: '🇭🇳'},
{name: 'Hong Kong', iso2: CountryISO.HongKong, dialCode: '852', flag: '🇭🇰'},
{name: 'Hungary', iso2: CountryISO.Hungary, dialCode: '36', flag: '🇭🇺'},
{name: 'Iceland', iso2: CountryISO.Iceland, dialCode: '354', flag: '🇮🇸'},
{name: 'India', iso2: CountryISO.India, dialCode: '91', flag: '🇮🇳'},
{name: 'Indonesia', iso2: CountryISO.Indonesia, dialCode: '62', flag: '🇮🇩'},
{name: 'Iran', iso2: CountryISO.Iran, dialCode: '98', flag: '🇮🇷'},
{name: 'Iraq', iso2: CountryISO.Iraq, dialCode: '964', flag: '🇮🇶'},
{name: 'Ireland', iso2: CountryISO.Ireland, dialCode: '353', flag: '🇮🇪'},
{name: 'Isle of Man', iso2: CountryISO.IsleOfMan, dialCode: '44', flag: '🇮🇲'},
{name: 'Israel', iso2: CountryISO.Israel, dialCode: '972', flag: '🇮🇱'},
{name: 'Italy', iso2: CountryISO.Italy, dialCode: '39', flag: '🇮🇹'},
{name: 'Jamaica', iso2: CountryISO.Jamaica, dialCode: '1', flag: '🇯🇲'},
{name: 'Japan', iso2: CountryISO.Japan, dialCode: '81', flag: '🇯🇵'},
{name: 'Jersey', iso2: CountryISO.Jersey, dialCode: '44', flag: '🇯🇪'},
{name: 'Jordan', iso2: CountryISO.Jordan, dialCode: '962', flag: '🇯🇴'},
{name: 'Kazakhstan', iso2: CountryISO.Kazakhstan, dialCode: '7', flag: '🇰🇿'},
{name: 'Kenya', iso2: CountryISO.Kenya, dialCode: '254', flag: '🇰🇪'},
{name: 'Kiribati', iso2: CountryISO.Kiribati, dialCode: '686', flag: '🇰🇮'},
{name: 'Kosovo', iso2: CountryISO.Kosovo, dialCode: '383', flag: '🇽🇰'},
{name: 'Kuwait', iso2: CountryISO.Kuwait, dialCode: '965', flag: '🇰🇼'},
{name: 'Kyrgyzstan', iso2: CountryISO.Kyrgyzstan, dialCode: '996', flag: '🇰🇬'},
{name: 'Laos', iso2: CountryISO.Laos, dialCode: '856', flag: '🇱🇦'},
{name: 'Latvia', iso2: CountryISO.Latvia, dialCode: '371', flag: '🇱🇻'},
{name: 'Lebanon', iso2: CountryISO.Lebanon, dialCode: '961', flag: '🇱🇧'},
{name: 'Lesotho', iso2: CountryISO.Lesotho, dialCode: '266', flag: '🇱🇸'},
{name: 'Liberia', iso2: CountryISO.Liberia, dialCode: '231', flag: '🇱🇷'},
{name: 'Libya', iso2: CountryISO.Libya, dialCode: '218', flag: '🇱🇾'},
{name: 'Liechtenstein', iso2: CountryISO.Liechtenstein, dialCode: '423', flag: '🇱🇮'},
{name: 'Lithuania', iso2: CountryISO.Lithuania, dialCode: '370', flag: '🇱🇹'},
{name: 'Luxembourg', iso2: CountryISO.Luxembourg, dialCode: '352', flag: '🇱🇺'},
{name: 'Macau', iso2: CountryISO.Macau, dialCode: '853', flag: '🇲🇴'},
{name: 'Macedonia', iso2: CountryISO.Macedonia, dialCode: '389', flag: '🇲🇰'},
{name: 'Madagascar', iso2: CountryISO.Madagascar, dialCode: '261', flag: '🇲🇬'},
{name: 'Malawi', iso2: CountryISO.Malawi, dialCode: '265', flag: '🇲🇼'},
{name: 'Malaysia', iso2: CountryISO.Malaysia, dialCode: '60', flag: '🇲🇾'},
{name: 'Maldives', iso2: CountryISO.Maldives, dialCode: '960', flag: '🇲🇻'},
{name: 'Mali', iso2: CountryISO.Mali, dialCode: '223', flag: '🇲🇱'},
{name: 'Malta', iso2: CountryISO.Malta, dialCode: '356', flag: '🇲🇹'},
{name: 'Marshall Islands', iso2: CountryISO.MarshallIslands, dialCode: '692', flag: '🇲🇭'},
{name: 'Martinique', iso2: CountryISO.Martinique, dialCode: '596', flag: '🇲🇶'},
{name: 'Mauritania', iso2: CountryISO.Mauritania, dialCode: '222', flag: '🇲🇷'},
{name: 'Mauritius', iso2: CountryISO.Mauritius, dialCode: '230', flag: '🇲🇺'},
{name: 'Mayotte', iso2: CountryISO.Mayotte, dialCode: '262', flag: '🇾🇹'},
{name: 'Mexico', iso2: CountryISO.Mexico, dialCode: '52', flag: '🇲🇽'},
{name: 'Micronesia', iso2: CountryISO.Micronesia, dialCode: '691', flag: '🇫🇲'},
{name: 'Moldova', iso2: CountryISO.Moldova, dialCode: '373', flag: '🇲🇩'},
{name: 'Monaco', iso2: CountryISO.Monaco, dialCode: '377', flag: '🇲🇨'},
{name: 'Mongolia', iso2: CountryISO.Mongolia, dialCode: '976', flag: '🇲🇳'},
{name: 'Montenegro', iso2: CountryISO.Montenegro, dialCode: '382', flag: '🇲🇪'},
{name: 'Montserrat', iso2: CountryISO.Montserrat, dialCode: '1', flag: '🇲🇸'},
{name: 'Morocco', iso2: CountryISO.Morocco, dialCode: '212', flag: '🇲🇦'},
{name: 'Mozambique', iso2: CountryISO.Mozambique, dialCode: '258', flag: '🇲🇿'},
{name: 'Myanmar', iso2: CountryISO.Myanmar, dialCode: '95', flag: '🇲🇲'},
{name: 'Namibia', iso2: CountryISO.Namibia, dialCode: '264', flag: '🇳🇦'},
{name: 'Nauru', iso2: CountryISO.Nauru, dialCode: '674', flag: '🇳🇷'},
{name: 'Nepal', iso2: CountryISO.Nepal, dialCode: '977', flag: '🇳🇵'},
{name: 'Netherlands', iso2: CountryISO.Netherlands, dialCode: '31', flag: '🇳🇱'},
{name: 'New Caledonia', iso2: CountryISO.NewCaledonia, dialCode: '687', flag: '🇳🇨'},
{name: 'New Zealand', iso2: CountryISO.NewZealand, dialCode: '64', flag: '🇳🇿'},
{name: 'Nicaragua', iso2: CountryISO.Nicaragua, dialCode: '505', flag: '🇳🇮'},
{name: 'Niger', iso2: CountryISO.Niger, dialCode: '227', flag: '🇳🇪'},
{name: 'Nigeria', iso2: CountryISO.Nigeria, dialCode: '234', flag: '🇳🇬'},
{name: 'Niue', iso2: CountryISO.Niue, dialCode: '683', flag: '🇳🇺'},
{name: 'Norfolk Island', iso2: CountryISO.NorfolkIsland, dialCode: '672', flag: '🇳🇫'},
{name: 'North Korea', iso2: CountryISO.NorthKorea, dialCode: '850', flag: '🇰🇵'},
{name: 'Northern Mariana Islands', iso2: CountryISO.NorthernMarianaIslands, dialCode: '1670', flag: '🇲🇵'},
{name: 'Norway', iso2: CountryISO.Norway, dialCode: '47', flag: '🇳🇴'},
{name: 'Oman', iso2: CountryISO.Oman, dialCode: '968', flag: '🇴🇲'},
{name: 'Pakistan', iso2: CountryISO.Pakistan, dialCode: '92', flag: '🇵🇰'},
{name: 'Palau', iso2: CountryISO.Palau, dialCode: '680', flag: '🇵🇼'},
{name: 'Palestine', iso2: CountryISO.Palestine, dialCode: '970', flag: '🇵🇸'},
{name: 'Panama', iso2: CountryISO.Panama, dialCode: '507', flag: '🇵🇦'},
{name: 'Papua New Guinea', iso2: CountryISO.PapuaNewGuinea, dialCode: '675', flag: '🇵🇬'},
{name: 'Paraguay', iso2: CountryISO.Paraguay, dialCode: '595', flag: '🇵🇾'},
{name: 'Peru', iso2: CountryISO.Peru, dialCode: '51', flag: '🇵🇪'},
{name: 'Philippines', iso2: CountryISO.Philippines, dialCode: '63', flag: '🇵🇭'},
{name: 'Poland', iso2: CountryISO.Poland, dialCode: '48', flag: '🇵🇱'},
{name: 'Portugal', iso2: CountryISO.Portugal, dialCode: '351', flag: '🇵🇹'},
{name: 'Puerto Rico', iso2: CountryISO.PuertoRico, dialCode: '1', flag: '🇵🇷'},
{name: 'Qatar', iso2: CountryISO.Qatar, dialCode: '974', flag: '🇶🇦'},
{name: 'Réunion', iso2: CountryISO.Réunion, dialCode: '262', flag: '🇷🇪'},
{name: 'Romania', iso2: CountryISO.Romania, dialCode: '40', flag: '🇷🇴'},
{name: 'Russia', iso2: CountryISO.Russia, dialCode: '7', flag: '🇷🇺'},
{name: 'Rwanda', iso2: CountryISO.Rwanda, dialCode: '250', flag: '🇷🇼'},
{name: 'Saint Barthélemy', iso2: CountryISO.SaintBarthélemy, dialCode: '590', flag: '🇧🇱'},
{name: 'Saint Helena', iso2: CountryISO.SaintHelena, dialCode: '290', flag: '🇸🇭'},
{name: 'Saint Kitts and Nevis', iso2: CountryISO.SaintKittsAndNevis, dialCode: '1869', flag: '🇰🇳'},
{name: 'Saint Lucia', iso2: CountryISO.SaintLucia, dialCode: '1', flag: '🇱🇨'},
{name: 'Saint Martin', iso2: CountryISO.SaintMartin, dialCode: '590', flag: '🇲🇫'},
{name: 'Saint Pierre and Miquelon', iso2: CountryISO.SaintPierreAndMiquelon, dialCode: '508', flag: '🇵🇲'},
{name: 'Saint Vincent and the Grenadines', iso2: CountryISO.SaintVincentAndTheGrenadines, dialCode: '1', flag: '🇻🇨'},
{name: 'Samoa', iso2: CountryISO.Samoa, dialCode: '685', flag: '🇼🇸'},
{name: 'San Marino', iso2: CountryISO.SanMarino, dialCode: '378', flag: '🇸🇲'},
{name: 'São Tomé and Príncipe', iso2: CountryISO.SãoToméAndPríncipe, dialCode: '239', flag: '🇸🇹'},
{name: 'Saudi Arabia', iso2: CountryISO.SaudiArabia, dialCode: '966', flag: '🇸🇦'},
{name: 'Senegal', iso2: CountryISO.Senegal, dialCode: '221', flag: '🇸🇳'},
{name: 'Serbia', iso2: CountryISO.Serbia, dialCode: '381', flag: '🇷🇸'},
{name: 'Seychelles', iso2: CountryISO.Seychelles, dialCode: '248', flag: '🇸🇨'},
{name: 'Sierra Leone', iso2: CountryISO.SierraLeone, dialCode: '232', flag: '🇸🇱'},
{name: 'Singapore', iso2: CountryISO.Singapore, dialCode: '65', flag: '🇸🇬'},
{name: 'Sint Maarten', iso2: CountryISO.SintMaarten, dialCode: '1', flag: '🇸🇽'},
{name: 'Slovakia', iso2: CountryISO.Slovakia, dialCode: '421', flag: '🇸🇰'},
{name: 'Slovenia', iso2: CountryISO.Slovenia, dialCode: '386', flag: '🇸🇮'},
{name: 'Solomon Islands', iso2: CountryISO.SolomonIslands, dialCode: '677', flag: '🇸🇧'},
{name: 'Somalia', iso2: CountryISO.Somalia, dialCode: '252', flag: '🇸🇴'},
{name: 'South Africa', iso2: CountryISO.SouthAfrica, dialCode: '27', flag: '🇿🇦'},
{name: 'South Korea', iso2: CountryISO.SouthKorea, dialCode: '82', flag: '🇰🇷'},
{name: 'South Sudan', iso2: CountryISO.SouthSudan, dialCode: '211', flag: '🇸🇸'},
{name: 'Spain', iso2: CountryISO.Spain, dialCode: '34', flag: '🇪🇸'},
{name: 'Sri Lanka', iso2: CountryISO.SriLanka, dialCode: '94', flag: '🇱🇰'},
{name: 'Sudan', iso2: CountryISO.Sudan, dialCode: '249', flag: '🇸🇩'},
{name: 'Suriname: ', iso2: CountryISO.Suriname, dialCode: '597', flag: '🇸🇷'},
{name: 'Svalbard and Jan Mayen', iso2: CountryISO.SvalbardAndJanMayen, dialCode: '47', flag: '🇸🇯'},
{name: 'Swaziland', iso2: CountryISO.Swaziland, dialCode: '268', flag: '🇸🇿'},
{name: 'Sweden', iso2: CountryISO.Sweden, dialCode: '46', flag: '🇸🇪'},
{name: 'Switzerland', iso2: CountryISO.Switzerland, dialCode: '41', flag: '🇨🇭'},
{name: 'Syria', iso2: CountryISO.Syria, dialCode: '963', flag: '🇸🇾'},
{name: 'Taiwan', iso2: CountryISO.Taiwan, dialCode: '886', flag: '🇹🇼'},
{name: 'Tajikistan', iso2: CountryISO.Tajikistan, dialCode: '992', flag: '🇹🇯'},
{name: 'Tanzania', iso2: CountryISO.Tanzania, dialCode: '255', flag: '🇹🇿'},
{name: 'Thailand', iso2: CountryISO.Thailand, dialCode: '66', flag: '🇹🇭'},
{name: 'Timor-Leste', iso2: CountryISO.TimorLeste, dialCode: '670', flag: '🇹🇱'},
{name: 'Togo', iso2: CountryISO.Togo, dialCode: '228', flag: '🇹🇬'},
{name: 'Tokelau', iso2: CountryISO.Tokelau, dialCode: '690', flag: '🇹🇰'},
{name: 'Tonga', iso2: CountryISO.Tonga, dialCode: '676', flag: '🇹🇴'},
{name: 'Trinidad and Tobago', iso2: CountryISO.TrinidadAndTobago, dialCode: '1', flag: '🇹🇹'},
{name: 'Tunisia', iso2: CountryISO.Tunisia, dialCode: '216', flag: '🇹🇳'},
{name: 'Turkey', iso2: CountryISO.Turkey, dialCode: '90', flag: '🇹🇷'},
{name: 'Turkmenistan', iso2: CountryISO.Turkmenistan, dialCode: '993', flag: '🇹🇲'},
{name: 'Turks and Caicos Islands', iso2: CountryISO.TurksAndCaicosIslands, dialCode: '1649', flag: '🇹🇨'},
{name: 'Tuvalu', iso2: CountryISO.Tuvalu, dialCode: '688', flag: '🇹🇻'},
{name: 'U.S. Virgin Islands', iso2: CountryISO.USVirginIslands, dialCode: '1', flag: '🇻🇮'},
{name: 'Uganda', iso2: CountryISO.Uganda, dialCode: '256', flag: '🇺🇬'},
{name: 'Ukraine', iso2: CountryISO.Ukraine, dialCode: '380', flag: '🇺🇦'},
{name: 'United Arab Emirates', iso2: CountryISO.UnitedArabEmirates, dialCode: '971', flag: '🇦🇪'},
{name: 'United Kingdom', iso2: CountryISO.UnitedKingdom, dialCode: '44', flag: '🇬🇧'},
{name: 'United States', iso2: CountryISO.UnitedStates, dialCode: '1', flag: '🇺🇸'},
{name: 'Uruguay', iso2: CountryISO.Uruguay, dialCode: '598', flag: '🇺🇾'},
{name: 'Uzbekistan', iso2: CountryISO.Uzbekistan, dialCode: '998', flag: '🇺🇿'},
{name: 'Vanuatu', iso2: CountryISO.Vanuatu, dialCode: '678', flag: '🇻🇺'},
{name: 'Vatican City', iso2: CountryISO.VaticanCity, dialCode: '39', flag: '🇻🇦'},
{name: 'Venezuela', iso2: CountryISO.Venezuela, dialCode: '58', flag: '🇻🇪'},
{name: 'Vietnam', iso2: CountryISO.Vietnam, dialCode: '84', flag: '🇻🇳'},
{name: 'Wallis and Futuna', iso2: CountryISO.WallisAndFutuna, dialCode: '681', flag: '🇼🇫'},
{name: 'Western Sahara', iso2: CountryISO.WesternSahara, dialCode: '212', flag: '🇪🇭'},
{name: 'Yemen', iso2: CountryISO.Yemen, dialCode: '967', flag: '🇾🇪'},
{name: 'Zambia', iso2: CountryISO.Zambia, dialCode: '260', flag: '🇿🇲'},
{name: 'Zimbabwe', iso2: CountryISO.Zimbabwe, dialCode: '263', flag: '🇿🇼'},
{name: 'Åland Islands', iso2: CountryISO.ÅlandIslands, dialCode: '358', flag: '🇦🇽'}
];
}

View File

@ -163,6 +163,7 @@ import { HtmlComponent } from '@shared/components/html.component';
import { SafePipe } from '@shared/pipe/safe.pipe';
import { DragDropModule } from '@angular/cdk/drag-drop';
import { MultipleImageInputComponent } from '@shared/components/multiple-image-input.component';
import { PhoneInputComponent } from '@shared/components/phone-input.component';
export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) {
return markedOptionsService;
@ -284,7 +285,8 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
WidgetsBundleSearchComponent,
CopyButtonComponent,
TogglePasswordComponent,
ProtobufContentComponent
ProtobufContentComponent,
PhoneInputComponent
],
imports: [
CommonModule,
@ -484,7 +486,8 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
WidgetsBundleSearchComponent,
CopyButtonComponent,
TogglePasswordComponent,
ProtobufContentComponent
ProtobufContentComponent,
PhoneInputComponent
]
})
export class SharedModule { }

View File

@ -4449,6 +4449,13 @@
"material-icons": "Material icons",
"show-all": "Show all icons"
},
"phone-input": {
"phone-input-label": "Phone number",
"phone-input-required": "Phone number is required",
"phone-input-validation": "Phone number is invalid or not possible",
"phone-input-pattern": "Invalid phone number. Should be in E.164 format, ex. {{phoneNumber}}",
"phone-input-hint": "Phone Number in E.164 format, ex. {{phoneNumber}}"
},
"custom": {
"widget-action": {
"action-cell-button": "Action cell button",

View File

@ -6027,6 +6027,11 @@ less@4.1.1:
needle "^2.5.2"
source-map "~0.6.0"
libphonenumber-js@^1.10.4:
version "1.10.4"
resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.10.4.tgz#90397f0ed620262570a32244c9fbc389cc417ce4"
integrity sha512-9QWxEk4GW5RDnFzt8UtyRENfFpAN8u7Sbf9wf32tcXY9tdtnz1dKHIBwW2Wnfx8ypXJb9zUnTpK9aQJ/B8AlnA==
license-webpack-plugin@2.3.20:
version "2.3.20"
resolved "https://registry.yarnpkg.com/license-webpack-plugin/-/license-webpack-plugin-2.3.20.tgz#f51fb674ca31519dbedbe1c7aabc036e5a7f2858"