Commit 7a34e2a3f53f3e6bba87427e379ed66132bc7360

Authored by Andrii Shvaika
2 parents 6ea39e83 a29aa644

Merge branch 'master' of github.com:thingsboard/thingsboard

Showing 63 changed files with 427 additions and 306 deletions
@@ -10,6 +10,7 @@ @@ -10,6 +10,7 @@
10 "mapperConfig": { 10 "mapperConfig": {
11 "type": "GITHUB", 11 "type": "GITHUB",
12 "basic": { 12 "basic": {
  13 + "firstNameAttributeKey": "name",
13 "tenantNameStrategy": "DOMAIN" 14 "tenantNameStrategy": "DOMAIN"
14 } 15 }
15 }, 16 },
@@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
16 package org.thingsboard.server.controller; 16 package org.thingsboard.server.controller;
17 17
18 import lombok.extern.slf4j.Slf4j; 18 import lombok.extern.slf4j.Slf4j;
  19 +import org.springframework.beans.factory.annotation.Autowired;
19 import org.springframework.http.HttpStatus; 20 import org.springframework.http.HttpStatus;
20 import org.springframework.security.access.prepost.PreAuthorize; 21 import org.springframework.security.access.prepost.PreAuthorize;
21 import org.springframework.web.bind.annotation.*; 22 import org.springframework.web.bind.annotation.*;
@@ -23,6 +24,7 @@ import org.thingsboard.server.common.data.exception.ThingsboardException; @@ -23,6 +24,7 @@ import org.thingsboard.server.common.data.exception.ThingsboardException;
23 import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; 24 import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo;
24 import org.thingsboard.server.common.data.oauth2.OAuth2ClientsParams; 25 import org.thingsboard.server.common.data.oauth2.OAuth2ClientsParams;
25 import org.thingsboard.server.common.data.oauth2.SchemeType; 26 import org.thingsboard.server.common.data.oauth2.SchemeType;
  27 +import org.thingsboard.server.dao.oauth2.OAuth2Configuration;
26 import org.thingsboard.server.queue.util.TbCoreComponent; 28 import org.thingsboard.server.queue.util.TbCoreComponent;
27 import org.thingsboard.server.service.security.permission.Operation; 29 import org.thingsboard.server.service.security.permission.Operation;
28 import org.thingsboard.server.service.security.permission.Resource; 30 import org.thingsboard.server.service.security.permission.Resource;
@@ -36,6 +38,10 @@ import java.util.List; @@ -36,6 +38,10 @@ import java.util.List;
36 @RequestMapping("/api") 38 @RequestMapping("/api")
37 @Slf4j 39 @Slf4j
38 public class OAuth2Controller extends BaseController { 40 public class OAuth2Controller extends BaseController {
  41 +
  42 + @Autowired
  43 + private OAuth2Configuration oAuth2Configuration;
  44 +
39 @RequestMapping(value = "/noauth/oauth2Clients", method = RequestMethod.POST) 45 @RequestMapping(value = "/noauth/oauth2Clients", method = RequestMethod.POST)
40 @ResponseBody 46 @ResponseBody
41 public List<OAuth2ClientInfo> getOAuth2Clients(HttpServletRequest request) throws ThingsboardException { 47 public List<OAuth2ClientInfo> getOAuth2Clients(HttpServletRequest request) throws ThingsboardException {
@@ -70,4 +76,16 @@ public class OAuth2Controller extends BaseController { @@ -70,4 +76,16 @@ public class OAuth2Controller extends BaseController {
70 throw handleException(e); 76 throw handleException(e);
71 } 77 }
72 } 78 }
  79 +
  80 + @PreAuthorize("hasAnyAuthority('SYS_ADMIN')")
  81 + @RequestMapping(value = "/oauth2/loginProcessingUrl", method = RequestMethod.GET)
  82 + @ResponseBody
  83 + public String getLoginProcessingUrl() throws ThingsboardException {
  84 + try {
  85 + accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_INFO, Operation.READ);
  86 + return "\"" + oAuth2Configuration.getLoginProcessingUrl() + "\"";
  87 + } catch (Exception e) {
  88 + throw handleException(e);
  89 + }
  90 + }
73 } 91 }
@@ -27,6 +27,6 @@ import java.util.Set; @@ -27,6 +27,6 @@ import java.util.Set;
27 @NoArgsConstructor 27 @NoArgsConstructor
28 @AllArgsConstructor 28 @AllArgsConstructor
29 public class OAuth2ClientsDomainParams { 29 public class OAuth2ClientsDomainParams {
30 - private Set<DomainInfo> domainInfos;  
31 - private Set<ClientRegistrationDto> clientRegistrations;  
32 -}  
  30 + private List<DomainInfo> domainInfos;
  31 + private List<ClientRegistrationDto> clientRegistrations;
  32 +}
@@ -16,6 +16,8 @@ @@ -16,6 +16,8 @@
16 package org.thingsboard.server.common.data.oauth2; 16 package org.thingsboard.server.common.data.oauth2;
17 17
18 import lombok.*; 18 import lombok.*;
  19 +
  20 +import java.util.List;
19 import java.util.Set; 21 import java.util.Set;
20 22
21 @EqualsAndHashCode 23 @EqualsAndHashCode
@@ -26,5 +28,5 @@ import java.util.Set; @@ -26,5 +28,5 @@ import java.util.Set;
26 @AllArgsConstructor 28 @AllArgsConstructor
27 public class OAuth2ClientsParams { 29 public class OAuth2ClientsParams {
28 private boolean enabled; 30 private boolean enabled;
29 - private Set<OAuth2ClientsDomainParams> domainsParams;  
30 -}  
  31 + private List<OAuth2ClientsDomainParams> domainsParams;
  32 +}
@@ -32,19 +32,19 @@ public class OAuth2Utils { @@ -32,19 +32,19 @@ public class OAuth2Utils {
32 } 32 }
33 33
34 public static OAuth2ClientsParams toOAuth2Params(List<ExtendedOAuth2ClientRegistrationInfo> extendedOAuth2ClientRegistrationInfos) { 34 public static OAuth2ClientsParams toOAuth2Params(List<ExtendedOAuth2ClientRegistrationInfo> extendedOAuth2ClientRegistrationInfos) {
35 - Map<OAuth2ClientRegistrationInfoId, Set<DomainInfo>> domainsByInfoId = new HashMap<>();  
36 - Map<OAuth2ClientRegistrationInfoId, OAuth2ClientRegistrationInfo> infoById = new HashMap<>(); 35 + Map<OAuth2ClientRegistrationInfoId, List<DomainInfo>> domainsByInfoId = new LinkedHashMap<>();
  36 + Map<OAuth2ClientRegistrationInfoId, OAuth2ClientRegistrationInfo> infoById = new LinkedHashMap<>();
37 for (ExtendedOAuth2ClientRegistrationInfo extendedClientRegistrationInfo : extendedOAuth2ClientRegistrationInfos) { 37 for (ExtendedOAuth2ClientRegistrationInfo extendedClientRegistrationInfo : extendedOAuth2ClientRegistrationInfos) {
38 String domainName = extendedClientRegistrationInfo.getDomainName(); 38 String domainName = extendedClientRegistrationInfo.getDomainName();
39 SchemeType domainScheme = extendedClientRegistrationInfo.getDomainScheme(); 39 SchemeType domainScheme = extendedClientRegistrationInfo.getDomainScheme();
40 - domainsByInfoId.computeIfAbsent(extendedClientRegistrationInfo.getId(), key -> new HashSet<>()) 40 + domainsByInfoId.computeIfAbsent(extendedClientRegistrationInfo.getId(), key -> new ArrayList<>())
41 .add(new DomainInfo(domainScheme, domainName)); 41 .add(new DomainInfo(domainScheme, domainName));
42 infoById.put(extendedClientRegistrationInfo.getId(), extendedClientRegistrationInfo); 42 infoById.put(extendedClientRegistrationInfo.getId(), extendedClientRegistrationInfo);
43 } 43 }
44 - Map<Set<DomainInfo>, OAuth2ClientsDomainParams> domainParamsMap = new HashMap<>(); 44 + Map<List<DomainInfo>, OAuth2ClientsDomainParams> domainParamsMap = new LinkedHashMap<>();
45 domainsByInfoId.forEach((clientRegistrationInfoId, domainInfos) -> { 45 domainsByInfoId.forEach((clientRegistrationInfoId, domainInfos) -> {
46 domainParamsMap.computeIfAbsent(domainInfos, 46 domainParamsMap.computeIfAbsent(domainInfos,
47 - key -> new OAuth2ClientsDomainParams(key, new HashSet<>()) 47 + key -> new OAuth2ClientsDomainParams(key, new ArrayList<>())
48 ) 48 )
49 .getClientRegistrations() 49 .getClientRegistrations()
50 .add(toClientRegistrationDto(infoById.get(clientRegistrationInfoId))); 50 .add(toClientRegistrationDto(infoById.get(clientRegistrationInfoId)));
@@ -52,7 +52,7 @@ public class OAuth2Utils { @@ -52,7 +52,7 @@ public class OAuth2Utils {
52 boolean enabled = extendedOAuth2ClientRegistrationInfos.stream() 52 boolean enabled = extendedOAuth2ClientRegistrationInfos.stream()
53 .map(OAuth2ClientRegistrationInfo::isEnabled) 53 .map(OAuth2ClientRegistrationInfo::isEnabled)
54 .findFirst().orElse(false); 54 .findFirst().orElse(false);
55 - return new OAuth2ClientsParams(enabled, new HashSet<>(domainParamsMap.values())); 55 + return new OAuth2ClientsParams(enabled, new ArrayList<>(domainParamsMap.values()));
56 } 56 }
57 57
58 public static ClientRegistrationDto toClientRegistrationDto(OAuth2ClientRegistrationInfo oAuth2ClientRegistrationInfo) { 58 public static ClientRegistrationDto toClientRegistrationDto(OAuth2ClientRegistrationInfo oAuth2ClientRegistrationInfo) {
@@ -15,6 +15,7 @@ @@ -15,6 +15,7 @@
15 */ 15 */
16 package org.thingsboard.server.dao.service; 16 package org.thingsboard.server.dao.service;
17 17
  18 +import com.google.common.collect.Lists;
18 import com.google.common.collect.Sets; 19 import com.google.common.collect.Sets;
19 import org.junit.After; 20 import org.junit.After;
20 import org.junit.Assert; 21 import org.junit.Assert;
@@ -29,7 +30,7 @@ import java.util.*; @@ -29,7 +30,7 @@ import java.util.*;
29 import java.util.stream.Collectors; 30 import java.util.stream.Collectors;
30 31
31 public class BaseOAuth2ServiceTest extends AbstractServiceTest { 32 public class BaseOAuth2ServiceTest extends AbstractServiceTest {
32 - private static final OAuth2ClientsParams EMPTY_PARAMS = new OAuth2ClientsParams(false, new HashSet<>()); 33 + private static final OAuth2ClientsParams EMPTY_PARAMS = new OAuth2ClientsParams(false, new ArrayList<>());
33 34
34 @Autowired 35 @Autowired
35 protected OAuth2Service oAuth2Service; 36 protected OAuth2Service oAuth2Service;
@@ -48,14 +49,14 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { @@ -48,14 +49,14 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest {
48 49
49 @Test(expected = DataValidationException.class) 50 @Test(expected = DataValidationException.class)
50 public void testSaveHttpAndMixedDomainsTogether() { 51 public void testSaveHttpAndMixedDomainsTogether() {
51 - OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Sets.newHashSet( 52 + OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Lists.newArrayList(
52 OAuth2ClientsDomainParams.builder() 53 OAuth2ClientsDomainParams.builder()
53 - .domainInfos(Sets.newHashSet( 54 + .domainInfos(Lists.newArrayList(
54 DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), 55 DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(),
55 DomainInfo.builder().name("first-domain").scheme(SchemeType.MIXED).build(), 56 DomainInfo.builder().name("first-domain").scheme(SchemeType.MIXED).build(),
56 DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() 57 DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build()
57 )) 58 ))
58 - .clientRegistrations(Sets.newHashSet( 59 + .clientRegistrations(Lists.newArrayList(
59 validClientRegistrationDto(), 60 validClientRegistrationDto(),
60 validClientRegistrationDto(), 61 validClientRegistrationDto(),
61 validClientRegistrationDto() 62 validClientRegistrationDto()
@@ -67,14 +68,14 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { @@ -67,14 +68,14 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest {
67 68
68 @Test(expected = DataValidationException.class) 69 @Test(expected = DataValidationException.class)
69 public void testSaveHttpsAndMixedDomainsTogether() { 70 public void testSaveHttpsAndMixedDomainsTogether() {
70 - OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Sets.newHashSet( 71 + OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Lists.newArrayList(
71 OAuth2ClientsDomainParams.builder() 72 OAuth2ClientsDomainParams.builder()
72 - .domainInfos(Sets.newHashSet( 73 + .domainInfos(Lists.newArrayList(
73 DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTPS).build(), 74 DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTPS).build(),
74 DomainInfo.builder().name("first-domain").scheme(SchemeType.MIXED).build(), 75 DomainInfo.builder().name("first-domain").scheme(SchemeType.MIXED).build(),
75 DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() 76 DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build()
76 )) 77 ))
77 - .clientRegistrations(Sets.newHashSet( 78 + .clientRegistrations(Lists.newArrayList(
78 validClientRegistrationDto(), 79 validClientRegistrationDto(),
79 validClientRegistrationDto(), 80 validClientRegistrationDto(),
80 validClientRegistrationDto() 81 validClientRegistrationDto()
@@ -131,20 +132,20 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { @@ -131,20 +132,20 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest {
131 Assert.assertNotNull(foundClientsParams); 132 Assert.assertNotNull(foundClientsParams);
132 Assert.assertEquals(clientsParams, foundClientsParams); 133 Assert.assertEquals(clientsParams, foundClientsParams);
133 134
134 - OAuth2ClientsParams newClientsParams = new OAuth2ClientsParams(true, Sets.newHashSet( 135 + OAuth2ClientsParams newClientsParams = new OAuth2ClientsParams(true, Lists.newArrayList(
135 OAuth2ClientsDomainParams.builder() 136 OAuth2ClientsDomainParams.builder()
136 - .domainInfos(Sets.newHashSet( 137 + .domainInfos(Lists.newArrayList(
137 DomainInfo.builder().name("another-domain").scheme(SchemeType.HTTPS).build() 138 DomainInfo.builder().name("another-domain").scheme(SchemeType.HTTPS).build()
138 )) 139 ))
139 - .clientRegistrations(Sets.newHashSet( 140 + .clientRegistrations(Lists.newArrayList(
140 validClientRegistrationDto() 141 validClientRegistrationDto()
141 )) 142 ))
142 .build(), 143 .build(),
143 OAuth2ClientsDomainParams.builder() 144 OAuth2ClientsDomainParams.builder()
144 - .domainInfos(Sets.newHashSet( 145 + .domainInfos(Lists.newArrayList(
145 DomainInfo.builder().name("test-domain").scheme(SchemeType.MIXED).build() 146 DomainInfo.builder().name("test-domain").scheme(SchemeType.MIXED).build()
146 )) 147 ))
147 - .clientRegistrations(Sets.newHashSet( 148 + .clientRegistrations(Lists.newArrayList(
148 validClientRegistrationDto() 149 validClientRegistrationDto()
149 )) 150 ))
150 .build() 151 .build()
@@ -157,22 +158,22 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { @@ -157,22 +158,22 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest {
157 158
158 @Test 159 @Test
159 public void testGetOAuth2Clients() { 160 public void testGetOAuth2Clients() {
160 - Set<ClientRegistrationDto> firstGroup = Sets.newHashSet( 161 + List<ClientRegistrationDto> firstGroup = Lists.newArrayList(
161 validClientRegistrationDto(), 162 validClientRegistrationDto(),
162 validClientRegistrationDto(), 163 validClientRegistrationDto(),
163 validClientRegistrationDto(), 164 validClientRegistrationDto(),
164 validClientRegistrationDto() 165 validClientRegistrationDto()
165 ); 166 );
166 - Set<ClientRegistrationDto> secondGroup = Sets.newHashSet( 167 + List<ClientRegistrationDto> secondGroup = Lists.newArrayList(
167 validClientRegistrationDto(), 168 validClientRegistrationDto(),
168 validClientRegistrationDto() 169 validClientRegistrationDto()
169 ); 170 );
170 - Set<ClientRegistrationDto> thirdGroup = Sets.newHashSet( 171 + List<ClientRegistrationDto> thirdGroup = Lists.newArrayList(
171 validClientRegistrationDto() 172 validClientRegistrationDto()
172 ); 173 );
173 - OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Sets.newHashSet( 174 + OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Lists.newArrayList(
174 OAuth2ClientsDomainParams.builder() 175 OAuth2ClientsDomainParams.builder()
175 - .domainInfos(Sets.newHashSet( 176 + .domainInfos(Lists.newArrayList(
176 DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), 177 DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(),
177 DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), 178 DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(),
178 DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() 179 DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build()
@@ -180,14 +181,14 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { @@ -180,14 +181,14 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest {
180 .clientRegistrations(firstGroup) 181 .clientRegistrations(firstGroup)
181 .build(), 182 .build(),
182 OAuth2ClientsDomainParams.builder() 183 OAuth2ClientsDomainParams.builder()
183 - .domainInfos(Sets.newHashSet( 184 + .domainInfos(Lists.newArrayList(
184 DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(), 185 DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(),
185 DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() 186 DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build()
186 )) 187 ))
187 .clientRegistrations(secondGroup) 188 .clientRegistrations(secondGroup)
188 .build(), 189 .build(),
189 OAuth2ClientsDomainParams.builder() 190 OAuth2ClientsDomainParams.builder()
190 - .domainInfos(Sets.newHashSet( 191 + .domainInfos(Lists.newArrayList(
191 DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTPS).build(), 192 DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTPS).build(),
192 DomainInfo.builder().name("fifth-domain").scheme(SchemeType.HTTP).build() 193 DomainInfo.builder().name("fifth-domain").scheme(SchemeType.HTTP).build()
193 )) 194 ))
@@ -285,15 +286,15 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { @@ -285,15 +286,15 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest {
285 286
286 @Test 287 @Test
287 public void testGetOAuth2ClientsForHttpAndHttps() { 288 public void testGetOAuth2ClientsForHttpAndHttps() {
288 - Set<ClientRegistrationDto> firstGroup = Sets.newHashSet( 289 + List<ClientRegistrationDto> firstGroup = Lists.newArrayList(
289 validClientRegistrationDto(), 290 validClientRegistrationDto(),
290 validClientRegistrationDto(), 291 validClientRegistrationDto(),
291 validClientRegistrationDto(), 292 validClientRegistrationDto(),
292 validClientRegistrationDto() 293 validClientRegistrationDto()
293 ); 294 );
294 - OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Sets.newHashSet( 295 + OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Lists.newArrayList(
295 OAuth2ClientsDomainParams.builder() 296 OAuth2ClientsDomainParams.builder()
296 - .domainInfos(Sets.newHashSet( 297 + .domainInfos(Lists.newArrayList(
297 DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), 298 DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(),
298 DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), 299 DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(),
299 DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTPS).build() 300 DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTPS).build()
@@ -335,25 +336,25 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { @@ -335,25 +336,25 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest {
335 336
336 @Test 337 @Test
337 public void testGetDisabledOAuth2Clients() { 338 public void testGetDisabledOAuth2Clients() {
338 - OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Sets.newHashSet( 339 + OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Lists.newArrayList(
339 OAuth2ClientsDomainParams.builder() 340 OAuth2ClientsDomainParams.builder()
340 - .domainInfos(Sets.newHashSet( 341 + .domainInfos(Lists.newArrayList(
341 DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), 342 DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(),
342 DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), 343 DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(),
343 DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() 344 DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build()
344 )) 345 ))
345 - .clientRegistrations(Sets.newHashSet( 346 + .clientRegistrations(Lists.newArrayList(
346 validClientRegistrationDto(), 347 validClientRegistrationDto(),
347 validClientRegistrationDto(), 348 validClientRegistrationDto(),
348 validClientRegistrationDto() 349 validClientRegistrationDto()
349 )) 350 ))
350 .build(), 351 .build(),
351 OAuth2ClientsDomainParams.builder() 352 OAuth2ClientsDomainParams.builder()
352 - .domainInfos(Sets.newHashSet( 353 + .domainInfos(Lists.newArrayList(
353 DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(), 354 DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(),
354 DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() 355 DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build()
355 )) 356 ))
356 - .clientRegistrations(Sets.newHashSet( 357 + .clientRegistrations(Lists.newArrayList(
357 validClientRegistrationDto(), 358 validClientRegistrationDto(),
358 validClientRegistrationDto() 359 validClientRegistrationDto()
359 )) 360 ))
@@ -374,35 +375,35 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { @@ -374,35 +375,35 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest {
374 375
375 @Test 376 @Test
376 public void testFindAllClientRegistrationInfos() { 377 public void testFindAllClientRegistrationInfos() {
377 - OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Sets.newHashSet( 378 + OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Lists.newArrayList(
378 OAuth2ClientsDomainParams.builder() 379 OAuth2ClientsDomainParams.builder()
379 - .domainInfos(Sets.newHashSet( 380 + .domainInfos(Lists.newArrayList(
380 DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), 381 DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(),
381 DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), 382 DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(),
382 DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() 383 DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build()
383 )) 384 ))
384 - .clientRegistrations(Sets.newHashSet( 385 + .clientRegistrations(Lists.newArrayList(
385 validClientRegistrationDto(), 386 validClientRegistrationDto(),
386 validClientRegistrationDto(), 387 validClientRegistrationDto(),
387 validClientRegistrationDto() 388 validClientRegistrationDto()
388 )) 389 ))
389 .build(), 390 .build(),
390 OAuth2ClientsDomainParams.builder() 391 OAuth2ClientsDomainParams.builder()
391 - .domainInfos(Sets.newHashSet( 392 + .domainInfos(Lists.newArrayList(
392 DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(), 393 DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(),
393 DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() 394 DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build()
394 )) 395 ))
395 - .clientRegistrations(Sets.newHashSet( 396 + .clientRegistrations(Lists.newArrayList(
396 validClientRegistrationDto(), 397 validClientRegistrationDto(),
397 validClientRegistrationDto() 398 validClientRegistrationDto()
398 )) 399 ))
399 .build(), 400 .build(),
400 OAuth2ClientsDomainParams.builder() 401 OAuth2ClientsDomainParams.builder()
401 - .domainInfos(Sets.newHashSet( 402 + .domainInfos(Lists.newArrayList(
402 DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTPS).build(), 403 DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTPS).build(),
403 DomainInfo.builder().name("fifth-domain").scheme(SchemeType.HTTP).build() 404 DomainInfo.builder().name("fifth-domain").scheme(SchemeType.HTTP).build()
404 )) 405 ))
405 - .clientRegistrations(Sets.newHashSet( 406 + .clientRegistrations(Lists.newArrayList(
406 validClientRegistrationDto() 407 validClientRegistrationDto()
407 )) 408 ))
408 .build() 409 .build()
@@ -423,35 +424,35 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { @@ -423,35 +424,35 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest {
423 424
424 @Test 425 @Test
425 public void testFindClientRegistrationById() { 426 public void testFindClientRegistrationById() {
426 - OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Sets.newHashSet( 427 + OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Lists.newArrayList(
427 OAuth2ClientsDomainParams.builder() 428 OAuth2ClientsDomainParams.builder()
428 - .domainInfos(Sets.newHashSet( 429 + .domainInfos(Lists.newArrayList(
429 DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), 430 DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(),
430 DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), 431 DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(),
431 DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() 432 DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build()
432 )) 433 ))
433 - .clientRegistrations(Sets.newHashSet( 434 + .clientRegistrations(Lists.newArrayList(
434 validClientRegistrationDto(), 435 validClientRegistrationDto(),
435 validClientRegistrationDto(), 436 validClientRegistrationDto(),
436 validClientRegistrationDto() 437 validClientRegistrationDto()
437 )) 438 ))
438 .build(), 439 .build(),
439 OAuth2ClientsDomainParams.builder() 440 OAuth2ClientsDomainParams.builder()
440 - .domainInfos(Sets.newHashSet( 441 + .domainInfos(Lists.newArrayList(
441 DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(), 442 DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(),
442 DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() 443 DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build()
443 )) 444 ))
444 - .clientRegistrations(Sets.newHashSet( 445 + .clientRegistrations(Lists.newArrayList(
445 validClientRegistrationDto(), 446 validClientRegistrationDto(),
446 validClientRegistrationDto() 447 validClientRegistrationDto()
447 )) 448 ))
448 .build(), 449 .build(),
449 OAuth2ClientsDomainParams.builder() 450 OAuth2ClientsDomainParams.builder()
450 - .domainInfos(Sets.newHashSet( 451 + .domainInfos(Lists.newArrayList(
451 DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTPS).build(), 452 DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTPS).build(),
452 DomainInfo.builder().name("fifth-domain").scheme(SchemeType.HTTP).build() 453 DomainInfo.builder().name("fifth-domain").scheme(SchemeType.HTTP).build()
453 )) 454 ))
454 - .clientRegistrations(Sets.newHashSet( 455 + .clientRegistrations(Lists.newArrayList(
455 validClientRegistrationDto() 456 validClientRegistrationDto()
456 )) 457 ))
457 .build() 458 .build()
@@ -466,14 +467,14 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { @@ -466,14 +467,14 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest {
466 } 467 }
467 468
468 private OAuth2ClientsParams createDefaultClientsParams() { 469 private OAuth2ClientsParams createDefaultClientsParams() {
469 - return new OAuth2ClientsParams(true, Sets.newHashSet( 470 + return new OAuth2ClientsParams(true, Lists.newArrayList(
470 OAuth2ClientsDomainParams.builder() 471 OAuth2ClientsDomainParams.builder()
471 - .domainInfos(Sets.newHashSet( 472 + .domainInfos(Lists.newArrayList(
472 DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), 473 DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(),
473 DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), 474 DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(),
474 DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() 475 DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build()
475 )) 476 ))
476 - .clientRegistrations(Sets.newHashSet( 477 + .clientRegistrations(Lists.newArrayList(
477 validClientRegistrationDto(), 478 validClientRegistrationDto(),
478 validClientRegistrationDto(), 479 validClientRegistrationDto(),
479 validClientRegistrationDto(), 480 validClientRegistrationDto(),
@@ -481,11 +482,11 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { @@ -481,11 +482,11 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest {
481 )) 482 ))
482 .build(), 483 .build(),
483 OAuth2ClientsDomainParams.builder() 484 OAuth2ClientsDomainParams.builder()
484 - .domainInfos(Sets.newHashSet( 485 + .domainInfos(Lists.newArrayList(
485 DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), 486 DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(),
486 DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() 487 DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build()
487 )) 488 ))
488 - .clientRegistrations(Sets.newHashSet( 489 + .clientRegistrations(Lists.newArrayList(
489 validClientRegistrationDto(), 490 validClientRegistrationDto(),
490 validClientRegistrationDto() 491 validClientRegistrationDto()
491 )) 492 ))
@@ -41,4 +41,8 @@ export class OAuth2Service { @@ -41,4 +41,8 @@ export class OAuth2Service {
41 return this.http.post<OAuth2ClientsParams>('/api/oauth2/config', OAuth2Setting, 41 return this.http.post<OAuth2ClientsParams>('/api/oauth2/config', OAuth2Setting,
42 defaultHttpOptionsFromConfig(config)); 42 defaultHttpOptionsFromConfig(config));
43 } 43 }
  44 +
  45 + public getLoginProcessingUrl(config?: RequestConfig): Observable<string> {
  46 + return this.http.get<string>(`/api/oauth2/loginProcessingUrl`, defaultHttpOptionsFromConfig(config));
  47 + }
44 } 48 }
@@ -191,6 +191,11 @@ export class MenuService { @@ -191,6 +191,11 @@ export class MenuService {
191 name: 'admin.security-settings', 191 name: 'admin.security-settings',
192 icon: 'security', 192 icon: 'security',
193 path: '/settings/security-settings' 193 path: '/settings/security-settings'
  194 + },
  195 + {
  196 + name: 'admin.oauth2.oauth2',
  197 + icon: 'security',
  198 + path: '/settings/oauth2'
194 } 199 }
195 ] 200 ]
196 } 201 }
@@ -88,14 +88,20 @@ @@ -88,14 +88,20 @@
88 </fieldset> 88 </fieldset>
89 </div> 89 </div>
90 <div mat-dialog-actions fxLayout="row"> 90 <div mat-dialog-actions fxLayout="row">
91 - <div fxLayout="row" *ngIf="alarm$ | async; let alarm;"> 91 + <button mat-button color="primary"
  92 + type="button"
  93 + [disabled]="(isLoading$ | async)"
  94 + (click)="close()" cdkFocusInitial>
  95 + {{ 'action.close' | translate }}
  96 + </button>
  97 + <span fxFlex></span>
  98 + <div fxLayout="row" *ngIf="alarm$ | async; let alarm;" fxLayoutGap="8px">
92 <button *ngIf="allowAcknowledgment && (alarm.status === alarmStatuses.ACTIVE_UNACK || 99 <button *ngIf="allowAcknowledgment && (alarm.status === alarmStatuses.ACTIVE_UNACK ||
93 alarm.status === alarmStatuses.CLEARED_UNACK)" 100 alarm.status === alarmStatuses.CLEARED_UNACK)"
94 mat-raised-button 101 mat-raised-button
95 color="primary" 102 color="primary"
96 type="button" 103 type="button"
97 (click)="acknowledge()" 104 (click)="acknowledge()"
98 - style="margin-right: 20px;"  
99 [disabled]="(isLoading$ | async)"> 105 [disabled]="(isLoading$ | async)">
100 {{ 'alarm.acknowledge' | translate }} 106 {{ 'alarm.acknowledge' | translate }}
101 </button> 107 </button>
@@ -109,12 +115,5 @@ @@ -109,12 +115,5 @@
109 {{ 'alarm.clear' | translate }} 115 {{ 'alarm.clear' | translate }}
110 </button> 116 </button>
111 </div> 117 </div>
112 - <span fxFlex></span>  
113 - <button mat-button color="primary"  
114 - type="button"  
115 - [disabled]="(isLoading$ | async)"  
116 - (click)="close()" cdkFocusInitial>  
117 - {{ 'action.close' | translate }}  
118 - </button>  
119 </div> 118 </div>
120 </form> 119 </form>
@@ -59,16 +59,16 @@ @@ -59,16 +59,16 @@
59 </fieldset> 59 </fieldset>
60 </div> 60 </div>
61 <div mat-dialog-actions fxLayoutAlign="end center"> 61 <div mat-dialog-actions fxLayoutAlign="end center">
62 - <button mat-raised-button color="primary"  
63 - type="submit"  
64 - [disabled]="(isLoading$ | async) || entityAliasFormGroup.invalid || !entityAliasFormGroup.dirty">  
65 - {{ (isAdd ? 'action.add' : 'action.save') | translate }}  
66 - </button>  
67 <button mat-button color="primary" 62 <button mat-button color="primary"
68 type="button" 63 type="button"
69 [disabled]="(isLoading$ | async)" 64 [disabled]="(isLoading$ | async)"
70 (click)="cancel()" cdkFocusInitial> 65 (click)="cancel()" cdkFocusInitial>
71 {{ 'action.cancel' | translate }} 66 {{ 'action.cancel' | translate }}
72 </button> 67 </button>
  68 + <button mat-raised-button color="primary"
  69 + type="submit"
  70 + [disabled]="(isLoading$ | async) || entityAliasFormGroup.invalid || !entityAliasFormGroup.dirty">
  71 + {{ (isAdd ? 'action.add' : 'action.save') | translate }}
  72 + </button>
73 </div> 73 </div>
74 </form> 74 </form>
@@ -95,11 +95,6 @@ @@ -95,11 +95,6 @@
95 {{ 'alias.add' | translate }} 95 {{ 'alias.add' | translate }}
96 </button> 96 </button>
97 <span fxFlex></span> 97 <span fxFlex></span>
98 - <button mat-raised-button color="primary"  
99 - type="submit"  
100 - [disabled]="(isLoading$ | async) || entityAliasesFormGroup.invalid || !entityAliasesFormGroup.dirty">  
101 - {{ 'action.save' | translate }}  
102 - </button>  
103 <button mat-button color="primary" 98 <button mat-button color="primary"
104 type="button" 99 type="button"
105 [disabled]="(isLoading$ | async)" 100 [disabled]="(isLoading$ | async)"
@@ -107,5 +102,10 @@ @@ -107,5 +102,10 @@
107 cdkFocusInitial> 102 cdkFocusInitial>
108 {{ 'action.cancel' | translate }} 103 {{ 'action.cancel' | translate }}
109 </button> 104 </button>
  105 + <button mat-raised-button color="primary"
  106 + type="submit"
  107 + [disabled]="(isLoading$ | async) || entityAliasesFormGroup.invalid || !entityAliasesFormGroup.dirty">
  108 + {{ 'action.save' | translate }}
  109 + </button>
110 </div> 110 </div>
111 </form> 111 </form>
@@ -44,16 +44,16 @@ @@ -44,16 +44,16 @@
44 </fieldset> 44 </fieldset>
45 </div> 45 </div>
46 <div mat-dialog-actions fxLayoutAlign="end center"> 46 <div mat-dialog-actions fxLayoutAlign="end center">
47 - <button mat-raised-button color="primary"  
48 - type="submit"  
49 - [disabled]="(isLoading$ | async) || attributeFormGroup.invalid || !attributeFormGroup.dirty">  
50 - {{ 'action.add' | translate }}  
51 - </button>  
52 <button mat-button color="primary" 47 <button mat-button color="primary"
53 type="button" 48 type="button"
54 [disabled]="(isLoading$ | async)" 49 [disabled]="(isLoading$ | async)"
55 (click)="cancel()" cdkFocusInitial> 50 (click)="cancel()" cdkFocusInitial>
56 {{ 'action.cancel' | translate }} 51 {{ 'action.cancel' | translate }}
57 </button> 52 </button>
  53 + <button mat-raised-button color="primary"
  54 + type="submit"
  55 + [disabled]="(isLoading$ | async) || attributeFormGroup.invalid || !attributeFormGroup.dirty">
  56 + {{ 'action.add' | translate }}
  57 + </button>
58 </div> 58 </div>
59 </form> 59 </form>
@@ -55,21 +55,22 @@ @@ -55,21 +55,22 @@
55 </mat-radio-group> 55 </mat-radio-group>
56 </fieldset> 56 </fieldset>
57 </div> 57 </div>
58 - <div mat-dialog-actions fxLayoutAlign="end center"> 58 + <div mat-dialog-actions fxLayout="row">
59 <mat-checkbox formControlName="openDashboard" 59 <mat-checkbox formControlName="openDashboard"
60 - style="margin-bottom: 0; padding-right: 20px;"> 60 + style="margin-bottom: 0;">
61 {{ 'dashboard.open-dashboard' | translate }} 61 {{ 'dashboard.open-dashboard' | translate }}
62 </mat-checkbox> 62 </mat-checkbox>
63 - <button mat-raised-button color="primary"  
64 - type="submit"  
65 - [disabled]="(isLoading$ | async) || addWidgetFormGroup.invalid || !addWidgetFormGroup.dirty">  
66 - {{ 'action.add' | translate }}  
67 - </button> 63 + <span fxFlex></span>
68 <button mat-button color="primary" 64 <button mat-button color="primary"
69 type="button" 65 type="button"
70 [disabled]="(isLoading$ | async)" 66 [disabled]="(isLoading$ | async)"
71 (click)="cancel()" cdkFocusInitial> 67 (click)="cancel()" cdkFocusInitial>
72 {{ 'action.cancel' | translate }} 68 {{ 'action.cancel' | translate }}
73 </button> 69 </button>
  70 + <button mat-raised-button color="primary"
  71 + type="submit"
  72 + [disabled]="(isLoading$ | async) || addWidgetFormGroup.invalid || !addWidgetFormGroup.dirty">
  73 + {{ 'action.add' | translate }}
  74 + </button>
74 </div> 75 </div>
75 </form> 76 </form>
@@ -26,7 +26,6 @@ @@ -26,7 +26,6 @@
26 <div fxLayout="row" class="tb-panel-actions"> 26 <div fxLayout="row" class="tb-panel-actions">
27 <span fxFlex></span> 27 <span fxFlex></span>
28 <button mat-button color="primary" 28 <button mat-button color="primary"
29 - style="margin-right: 20px;"  
30 type="button" 29 type="button"
31 [disabled]="(isLoading$ | async)" 30 [disabled]="(isLoading$ | async)"
32 (click)="cancel()" cdkFocusInitial> 31 (click)="cancel()" cdkFocusInitial>
@@ -40,11 +40,6 @@ @@ -40,11 +40,6 @@
40 </fieldset> 40 </fieldset>
41 </div> 41 </div>
42 <div mat-dialog-actions fxLayoutAlign="end center"> 42 <div mat-dialog-actions fxLayoutAlign="end center">
43 - <button mat-raised-button color="primary"  
44 - type="submit"  
45 - [disabled]="(isLoading$ | async) || stateFormGroup.invalid">  
46 - {{ 'action.select' | translate }}  
47 - </button>  
48 <button mat-button color="primary" 43 <button mat-button color="primary"
49 type="button" 44 type="button"
50 [disabled]="(isLoading$ | async)" 45 [disabled]="(isLoading$ | async)"
@@ -52,5 +47,10 @@ @@ -52,5 +47,10 @@
52 cdkFocusInitial> 47 cdkFocusInitial>
53 {{ 'action.cancel' | translate }} 48 {{ 'action.cancel' | translate }}
54 </button> 49 </button>
  50 + <button mat-raised-button color="primary"
  51 + type="submit"
  52 + [disabled]="(isLoading$ | async) || stateFormGroup.invalid">
  53 + {{ 'action.select' | translate }}
  54 + </button>
55 </div> 55 </div>
56 </form> 56 </form>
@@ -31,7 +31,6 @@ @@ -31,7 +31,6 @@
31 <div mat-dialog-actions fxLayout="row"> 31 <div mat-dialog-actions fxLayout="row">
32 <span fxFlex></span> 32 <span fxFlex></span>
33 <button mat-button color="primary" 33 <button mat-button color="primary"
34 - style="margin-right: 20px;"  
35 type="button" 34 type="button"
36 [disabled]="(isLoading$ | async)" 35 [disabled]="(isLoading$ | async)"
37 [mat-dialog-close]="false" cdkFocusInitial> 36 [mat-dialog-close]="false" cdkFocusInitial>
@@ -46,12 +46,6 @@ @@ -46,12 +46,6 @@
46 </fieldset> 46 </fieldset>
47 </div> 47 </div>
48 <div mat-dialog-actions fxLayoutAlign="end center"> 48 <div mat-dialog-actions fxLayoutAlign="end center">
49 - <button mat-raised-button color="primary"  
50 - *ngIf="!data.readonly"  
51 - type="submit"  
52 - [disabled]="(isLoading$ | async) || complexFilterFormGroup.invalid || !complexFilterFormGroup.dirty">  
53 - {{ (isAdd ? 'action.add' : 'action.update') | translate }}  
54 - </button>  
55 <button mat-button color="primary" 49 <button mat-button color="primary"
56 type="button" 50 type="button"
57 [disabled]="(isLoading$ | async)" 51 [disabled]="(isLoading$ | async)"
@@ -59,5 +53,11 @@ @@ -59,5 +53,11 @@
59 cdkFocusInitial> 53 cdkFocusInitial>
60 {{ (data.readonly ? 'action.close' : 'action.cancel') | translate }} 54 {{ (data.readonly ? 'action.close' : 'action.cancel') | translate }}
61 </button> 55 </button>
  56 + <button mat-raised-button color="primary"
  57 + *ngIf="!data.readonly"
  58 + type="submit"
  59 + [disabled]="(isLoading$ | async) || complexFilterFormGroup.invalid || !complexFilterFormGroup.dirty">
  60 + {{ (isAdd ? 'action.add' : 'action.update') | translate }}
  61 + </button>
62 </div> 62 </div>
63 </form> 63 </form>
@@ -55,16 +55,16 @@ @@ -55,16 +55,16 @@
55 </fieldset> 55 </fieldset>
56 </div> 56 </div>
57 <div mat-dialog-actions fxLayoutAlign="end center"> 57 <div mat-dialog-actions fxLayoutAlign="end center">
58 - <button mat-raised-button color="primary"  
59 - type="submit"  
60 - [disabled]="(isLoading$ | async) || filterFormGroup.invalid || !filterFormGroup.dirty">  
61 - {{ (isAdd ? 'action.add' : 'action.update') | translate }}  
62 - </button>  
63 <button mat-button color="primary" 58 <button mat-button color="primary"
64 type="button" 59 type="button"
65 [disabled]="(isLoading$ | async)" 60 [disabled]="(isLoading$ | async)"
66 (click)="cancel()" cdkFocusInitial> 61 (click)="cancel()" cdkFocusInitial>
67 {{ 'action.cancel' | translate }} 62 {{ 'action.cancel' | translate }}
68 </button> 63 </button>
  64 + <button mat-raised-button color="primary"
  65 + type="submit"
  66 + [disabled]="(isLoading$ | async) || filterFormGroup.invalid || !filterFormGroup.dirty">
  67 + {{ (isAdd ? 'action.add' : 'action.update') | translate }}
  68 + </button>
69 </div> 69 </div>
70 </form> 70 </form>
@@ -46,12 +46,6 @@ @@ -46,12 +46,6 @@
46 </fieldset> 46 </fieldset>
47 </div> 47 </div>
48 <div mat-dialog-actions fxLayoutAlign="end center"> 48 <div mat-dialog-actions fxLayoutAlign="end center">
49 - <button mat-raised-button color="primary"  
50 - *ngIf="!data.readonly"  
51 - type="submit"  
52 - [disabled]="(isLoading$ | async) || filterUserInfoFormGroup.invalid || !filterUserInfoFormGroup.dirty">  
53 - {{ 'action.update' | translate }}  
54 - </button>  
55 <button mat-button color="primary" 49 <button mat-button color="primary"
56 type="button" 50 type="button"
57 [disabled]="(isLoading$ | async)" 51 [disabled]="(isLoading$ | async)"
@@ -59,5 +53,11 @@ @@ -59,5 +53,11 @@
59 cdkFocusInitial> 53 cdkFocusInitial>
60 {{ (data.readonly ? 'action.close' : 'action.cancel') | translate }} 54 {{ (data.readonly ? 'action.close' : 'action.cancel') | translate }}
61 </button> 55 </button>
  56 + <button mat-raised-button color="primary"
  57 + *ngIf="!data.readonly"
  58 + type="submit"
  59 + [disabled]="(isLoading$ | async) || filterUserInfoFormGroup.invalid || !filterUserInfoFormGroup.dirty">
  60 + {{ 'action.update' | translate }}
  61 + </button>
62 </div> 62 </div>
63 </form> 63 </form>
@@ -88,11 +88,6 @@ @@ -88,11 +88,6 @@
88 {{ 'filter.add' | translate }} 88 {{ 'filter.add' | translate }}
89 </button> 89 </button>
90 <span fxFlex></span> 90 <span fxFlex></span>
91 - <button mat-raised-button color="primary"  
92 - type="submit"  
93 - [disabled]="(isLoading$ | async) || filtersFormGroup.invalid || !filtersFormGroup.dirty">  
94 - {{ 'action.save' | translate }}  
95 - </button>  
96 <button mat-button color="primary" 91 <button mat-button color="primary"
97 type="button" 92 type="button"
98 [disabled]="(isLoading$ | async)" 93 [disabled]="(isLoading$ | async)"
@@ -100,5 +95,10 @@ @@ -100,5 +95,10 @@
100 cdkFocusInitial> 95 cdkFocusInitial>
101 {{ 'action.cancel' | translate }} 96 {{ 'action.cancel' | translate }}
102 </button> 97 </button>
  98 + <button mat-raised-button color="primary"
  99 + type="submit"
  100 + [disabled]="(isLoading$ | async) || filtersFormGroup.invalid || !filtersFormGroup.dirty">
  101 + {{ 'action.save' | translate }}
  102 + </button>
103 </div> 103 </div>
104 </form> 104 </form>
@@ -79,12 +79,6 @@ @@ -79,12 +79,6 @@
79 </fieldset> 79 </fieldset>
80 </div> 80 </div>
81 <div mat-dialog-actions fxLayoutAlign="end center"> 81 <div mat-dialog-actions fxLayoutAlign="end center">
82 - <button mat-raised-button color="primary"  
83 - type="submit"  
84 - *ngIf="!data.readonly"  
85 - [disabled]="(isLoading$ | async) || keyFilterFormGroup.invalid || !keyFilterFormGroup.dirty">  
86 - {{ (data.isAdd ? 'action.add' : 'action.update') | translate }}  
87 - </button>  
88 <button mat-button color="primary" 82 <button mat-button color="primary"
89 type="button" 83 type="button"
90 [disabled]="(isLoading$ | async)" 84 [disabled]="(isLoading$ | async)"
@@ -92,5 +86,11 @@ @@ -92,5 +86,11 @@
92 cdkFocusInitial> 86 cdkFocusInitial>
93 {{ (data.readonly ? 'action.close' : 'action.cancel') | translate }} 87 {{ (data.readonly ? 'action.close' : 'action.cancel') | translate }}
94 </button> 88 </button>
  89 + <button mat-raised-button color="primary"
  90 + type="submit"
  91 + *ngIf="!data.readonly"
  92 + [disabled]="(isLoading$ | async) || keyFilterFormGroup.invalid || !keyFilterFormGroup.dirty">
  93 + {{ (data.isAdd ? 'action.add' : 'action.update') | translate }}
  94 + </button>
95 </div> 95 </div>
96 </form> 96 </form>
@@ -65,16 +65,16 @@ @@ -65,16 +65,16 @@
65 </fieldset> 65 </fieldset>
66 </div> 66 </div>
67 <div mat-dialog-actions fxLayoutAlign="end center"> 67 <div mat-dialog-actions fxLayoutAlign="end center">
68 - <button mat-raised-button color="primary"  
69 - type="submit"  
70 - [disabled]="(isLoading$ | async) || userFilterFormGroup.invalid || !userFilterFormGroup.dirty">  
71 - {{ 'action.update' | translate }}  
72 - </button>  
73 <button mat-button color="primary" 68 <button mat-button color="primary"
74 type="button" 69 type="button"
75 [disabled]="(isLoading$ | async)" 70 [disabled]="(isLoading$ | async)"
76 (click)="cancel()" cdkFocusInitial> 71 (click)="cancel()" cdkFocusInitial>
77 {{ 'action.cancel' | translate }} 72 {{ 'action.cancel' | translate }}
78 </button> 73 </button>
  74 + <button mat-raised-button color="primary"
  75 + type="submit"
  76 + [disabled]="(isLoading$ | async) || userFilterFormGroup.invalid || !userFilterFormGroup.dirty">
  77 + {{ 'action.update' | translate }}
  78 + </button>
79 </div> 79 </div>
80 </form> 80 </form>
@@ -43,16 +43,16 @@ @@ -43,16 +43,16 @@
43 </fieldset> 43 </fieldset>
44 </div> 44 </div>
45 <div mat-dialog-actions fxLayoutAlign="end center"> 45 <div mat-dialog-actions fxLayoutAlign="end center">
46 - <button mat-raised-button color="primary"  
47 - type="submit"  
48 - [disabled]="(isLoading$ | async) || importFormGroup.invalid || !importFormGroup.dirty">  
49 - {{ 'action.import' | translate }}  
50 - </button>  
51 <button mat-button color="primary" 46 <button mat-button color="primary"
52 type="button" 47 type="button"
53 [disabled]="(isLoading$ | async)" 48 [disabled]="(isLoading$ | async)"
54 (click)="cancel()" cdkFocusInitial> 49 (click)="cancel()" cdkFocusInitial>
55 {{ 'action.cancel' | translate }} 50 {{ 'action.cancel' | translate }}
56 </button> 51 </button>
  52 + <button mat-raised-button color="primary"
  53 + type="submit"
  54 + [disabled]="(isLoading$ | async) || importFormGroup.invalid || !importFormGroup.dirty">
  55 + {{ 'action.import' | translate }}
  56 + </button>
57 </div> 57 </div>
58 </form> 58 </form>
@@ -108,17 +108,17 @@ @@ -108,17 +108,17 @@
108 </fieldset> 108 </fieldset>
109 </div> 109 </div>
110 <div mat-dialog-actions fxLayoutAlign="end center"> 110 <div mat-dialog-actions fxLayoutAlign="end center">
111 - <button mat-raised-button color="primary"  
112 - *ngIf="!readonly"  
113 - type="submit"  
114 - [disabled]="(isLoading$ | async) || conditionFormGroup.invalid || !conditionFormGroup.dirty">  
115 - {{ 'action.save' | translate }}  
116 - </button>  
117 <button mat-button color="primary" 111 <button mat-button color="primary"
118 type="button" 112 type="button"
119 [disabled]="(isLoading$ | async)" 113 [disabled]="(isLoading$ | async)"
120 (click)="cancel()" cdkFocusInitial> 114 (click)="cancel()" cdkFocusInitial>
121 {{ (readonly ? 'action.close' : 'action.cancel') | translate }} 115 {{ (readonly ? 'action.close' : 'action.cancel') | translate }}
122 </button> 116 </button>
  117 + <button mat-raised-button color="primary"
  118 + *ngIf="!readonly"
  119 + type="submit"
  120 + [disabled]="(isLoading$ | async) || conditionFormGroup.invalid || !conditionFormGroup.dirty">
  121 + {{ 'action.save' | translate }}
  122 + </button>
123 </div> 123 </div>
124 </form> 124 </form>
@@ -37,17 +37,17 @@ @@ -37,17 +37,17 @@
37 </fieldset> 37 </fieldset>
38 </div> 38 </div>
39 <div mat-dialog-actions fxLayoutAlign="end center"> 39 <div mat-dialog-actions fxLayoutAlign="end center">
40 - <button mat-raised-button color="primary"  
41 - *ngIf="!readonly"  
42 - type="submit"  
43 - [disabled]="(isLoading$ | async) || alarmScheduleFormGroup.invalid || !alarmScheduleFormGroup.dirty">  
44 - {{ 'action.save' | translate }}  
45 - </button>  
46 <button mat-button color="primary" 40 <button mat-button color="primary"
47 type="button" 41 type="button"
48 [disabled]="(isLoading$ | async)" 42 [disabled]="(isLoading$ | async)"
49 (click)="cancel()" cdkFocusInitial> 43 (click)="cancel()" cdkFocusInitial>
50 {{ (readonly ? 'action.close' : 'action.cancel') | translate }} 44 {{ (readonly ? 'action.close' : 'action.cancel') | translate }}
51 </button> 45 </button>
  46 + <button mat-raised-button color="primary"
  47 + *ngIf="!readonly"
  48 + type="submit"
  49 + [disabled]="(isLoading$ | async) || alarmScheduleFormGroup.invalid || !alarmScheduleFormGroup.dirty">
  50 + {{ 'action.save' | translate }}
  51 + </button>
52 </div> 52 </div>
53 </form> 53 </form>
@@ -47,9 +47,9 @@ @@ -47,9 +47,9 @@
47 <mat-icon>remove_circle_outline</mat-icon> 47 <mat-icon>remove_circle_outline</mat-icon>
48 </button> 48 </button>
49 </div> 49 </div>
50 - <div *ngIf="!createAlarmRulesFormArray().controls.length"> 50 + <div *ngIf="!createAlarmRulesFormArray().controls.length && !disabled">
51 <span translate fxLayoutAlign="center center" style="margin: 16px 0" 51 <span translate fxLayoutAlign="center center" style="margin: 16px 0"
52 - class="tb-prompt">device-profile.no-create-alarm-rules</span> 52 + class="tb-prompt required">device-profile.add-create-alarm-rule-prompt</span>
53 </div> 53 </div>
54 <div fxLayout="row" *ngIf="!disabled"> 54 <div fxLayout="row" *ngIf="!disabled">
55 <button mat-stroked-button color="primary" 55 <button mat-stroked-button color="primary"
@@ -23,11 +23,11 @@ import { @@ -23,11 +23,11 @@ import {
23 FormControl, 23 FormControl,
24 FormGroup, 24 FormGroup,
25 NG_VALIDATORS, 25 NG_VALIDATORS,
26 - NG_VALUE_ACCESSOR, 26 + NG_VALUE_ACCESSOR, ValidationErrors,
27 Validator, 27 Validator,
28 Validators 28 Validators
29 } from '@angular/forms'; 29 } from '@angular/forms';
30 -import { AlarmRule } from '@shared/models/device.models'; 30 +import { AlarmRule, alarmRuleValidator } from '@shared/models/device.models';
31 import { MatDialog } from '@angular/material/dialog'; 31 import { MatDialog } from '@angular/material/dialog';
32 import { Subscription } from 'rxjs'; 32 import { Subscription } from 'rxjs';
33 import { AlarmSeverity, alarmSeverityTranslations } from '../../../../../shared/models/alarm.models'; 33 import { AlarmSeverity, alarmSeverityTranslations } from '../../../../../shared/models/alarm.models';
@@ -141,10 +141,23 @@ export class CreateAlarmRulesComponent implements ControlValueAccessor, OnInit, @@ -141,10 +141,23 @@ export class CreateAlarmRulesComponent implements ControlValueAccessor, OnInit,
141 }; 141 };
142 const createAlarmRulesArray = this.createAlarmRulesFormGroup.get('createAlarmRules') as FormArray; 142 const createAlarmRulesArray = this.createAlarmRulesFormGroup.get('createAlarmRules') as FormArray;
143 createAlarmRulesArray.push(this.fb.group({ 143 createAlarmRulesArray.push(this.fb.group({
144 - severity: [null, Validators.required],  
145 - alarmRule: [createAlarmRule, Validators.required] 144 + severity: [this.getFirstUnusedSeverity(), Validators.required],
  145 + alarmRule: [createAlarmRule, alarmRuleValidator]
146 })); 146 }));
147 this.createAlarmRulesFormGroup.updateValueAndValidity(); 147 this.createAlarmRulesFormGroup.updateValueAndValidity();
  148 + if (!this.createAlarmRulesFormGroup.valid) {
  149 + this.updateModel();
  150 + }
  151 + }
  152 +
  153 + private getFirstUnusedSeverity(): AlarmSeverity {
  154 + for (const severityKey of Object.keys(AlarmSeverity)) {
  155 + const severity = AlarmSeverity[severityKey];
  156 + if (this.usedSeverities.indexOf(severity) === -1) {
  157 + return severity;
  158 + }
  159 + }
  160 + return null;
148 } 161 }
149 162
150 public validate(c: FormControl) { 163 public validate(c: FormControl) {
@@ -25,7 +25,7 @@ import { @@ -25,7 +25,7 @@ import {
25 Validator, 25 Validator,
26 Validators 26 Validators
27 } from '@angular/forms'; 27 } from '@angular/forms';
28 -import { AlarmRule, DeviceProfileAlarm } from '@shared/models/device.models'; 28 +import { AlarmRule, DeviceProfileAlarm, deviceProfileAlarmValidator } from '@shared/models/device.models';
29 import { MatDialog } from '@angular/material/dialog'; 29 import { MatDialog } from '@angular/material/dialog';
30 import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes'; 30 import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes';
31 import { MatChipInputEvent } from '@angular/material/chips'; 31 import { MatChipInputEvent } from '@angular/material/chips';
@@ -92,7 +92,7 @@ export class DeviceProfileAlarmComponent implements ControlValueAccessor, OnInit @@ -92,7 +92,7 @@ export class DeviceProfileAlarmComponent implements ControlValueAccessor, OnInit
92 clearRule: [null], 92 clearRule: [null],
93 propagate: [null], 93 propagate: [null],
94 propagateRelationTypes: [null] 94 propagateRelationTypes: [null]
95 - }); 95 + }, { validators: deviceProfileAlarmValidator });
96 this.alarmFormGroup.valueChanges.subscribe(() => { 96 this.alarmFormGroup.valueChanges.subscribe(() => {
97 this.updateModel(); 97 this.updateModel();
98 }); 98 });
@@ -30,7 +30,7 @@ import { @@ -30,7 +30,7 @@ import {
30 import { Store } from '@ngrx/store'; 30 import { Store } from '@ngrx/store';
31 import { AppState } from '@app/core/core.state'; 31 import { AppState } from '@app/core/core.state';
32 import { coerceBooleanProperty } from '@angular/cdk/coercion'; 32 import { coerceBooleanProperty } from '@angular/cdk/coercion';
33 -import { DeviceProfileAlarm } from '@shared/models/device.models'; 33 +import { DeviceProfileAlarm, deviceProfileAlarmValidator } from '@shared/models/device.models';
34 import { guid } from '@core/utils'; 34 import { guid } from '@core/utils';
35 import { Subscription } from 'rxjs'; 35 import { Subscription } from 'rxjs';
36 import { MatDialog } from '@angular/material/dialog'; 36 import { MatDialog } from '@angular/material/dialog';
@@ -141,7 +141,7 @@ export class DeviceProfileAlarmsComponent implements ControlValueAccessor, OnIni @@ -141,7 +141,7 @@ export class DeviceProfileAlarmsComponent implements ControlValueAccessor, OnIni
141 id: guid(), 141 id: guid(),
142 alarmType: '', 142 alarmType: '',
143 createRules: { 143 createRules: {
144 - empty: { 144 + CRITICAL: {
145 condition: { 145 condition: {
146 condition: [] 146 condition: []
147 } 147 }
@@ -149,8 +149,11 @@ export class DeviceProfileAlarmsComponent implements ControlValueAccessor, OnIni @@ -149,8 +149,11 @@ export class DeviceProfileAlarmsComponent implements ControlValueAccessor, OnIni
149 } 149 }
150 }; 150 };
151 const alarmsArray = this.deviceProfileAlarmsFormGroup.get('alarms') as FormArray; 151 const alarmsArray = this.deviceProfileAlarmsFormGroup.get('alarms') as FormArray;
152 - alarmsArray.push(this.fb.control(alarm, [Validators.required])); 152 + alarmsArray.push(this.fb.control(alarm, [deviceProfileAlarmValidator]));
153 this.deviceProfileAlarmsFormGroup.updateValueAndValidity(); 153 this.deviceProfileAlarmsFormGroup.updateValueAndValidity();
  154 + if (!this.deviceProfileAlarmsFormGroup.valid) {
  155 + this.updateModel();
  156 + }
154 } 157 }
155 158
156 public validate(c: FormControl) { 159 public validate(c: FormControl) {
@@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
15 limitations under the License. 15 limitations under the License.
16 16
17 --> 17 -->
18 -<form (ngSubmit)="save()" style="min-width: 1000px;"> 18 +<form (ngSubmit)="save()" style="width: 1000px;">
19 <mat-toolbar color="primary"> 19 <mat-toolbar color="primary">
20 <h2>{{ (isAdd ? 'device-profile.add' : 'device-profile.edit' ) | translate }}</h2> 20 <h2>{{ (isAdd ? 'device-profile.add' : 'device-profile.edit' ) | translate }}</h2>
21 <span fxFlex></span> 21 <span fxFlex></span>
@@ -37,11 +37,6 @@ @@ -37,11 +37,6 @@
37 </tb-device-profile> 37 </tb-device-profile>
38 </div> 38 </div>
39 <div mat-dialog-actions fxLayoutAlign="end center"> 39 <div mat-dialog-actions fxLayoutAlign="end center">
40 - <button mat-raised-button color="primary"  
41 - type="submit"  
42 - [disabled]="(isLoading$ | async) || deviceProfileComponent.entityForm?.invalid || !deviceProfileComponent.entityForm?.dirty">  
43 - {{ (isAdd ? 'action.add' : 'action.save') | translate }}  
44 - </button>  
45 <button mat-button color="primary" 40 <button mat-button color="primary"
46 type="button" 41 type="button"
47 cdkFocusInitial 42 cdkFocusInitial
@@ -49,5 +44,10 @@ @@ -49,5 +44,10 @@
49 (click)="cancel()"> 44 (click)="cancel()">
50 {{ 'action.cancel' | translate }} 45 {{ 'action.cancel' | translate }}
51 </button> 46 </button>
  47 + <button mat-raised-button color="primary"
  48 + type="submit"
  49 + [disabled]="(isLoading$ | async) || deviceProfileComponent.entityForm?.invalid || !deviceProfileComponent.entityForm?.dirty">
  50 + {{ (isAdd ? 'action.add' : 'action.save') | translate }}
  51 + </button>
52 </div> 52 </div>
53 </form> 53 </form>
@@ -41,7 +41,7 @@ @@ -41,7 +41,7 @@
41 </div> 41 </div>
42 <div class="mat-padding" fxLayout="column"> 42 <div class="mat-padding" fxLayout="column">
43 <form [formGroup]="entityForm"> 43 <form [formGroup]="entityForm">
44 - <fieldset [disabled]="(isLoading$ | async) || !isEdit"> 44 + <fieldset [disabled]="(isLoading$ | async) || !isEdit" style="min-width: 0;">
45 <mat-form-field class="mat-block"> 45 <mat-form-field class="mat-block">
46 <mat-label translate>device-profile.name</mat-label> 46 <mat-label translate>device-profile.name</mat-label>
47 <input matInput formControlName="name" required/> 47 <input matInput formControlName="name" required/>
@@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
15 limitations under the License. 15 limitations under the License.
16 16
17 --> 17 -->
18 -<form (ngSubmit)="save()" style="min-width: 600px;"> 18 +<form (ngSubmit)="save()" style="width: 600px;">
19 <mat-toolbar color="primary"> 19 <mat-toolbar color="primary">
20 <h2>{{ (isAdd ? 'tenant-profile.add' : 'tenant-profile.edit' ) | translate }}</h2> 20 <h2>{{ (isAdd ? 'tenant-profile.add' : 'tenant-profile.edit' ) | translate }}</h2>
21 <span fxFlex></span> 21 <span fxFlex></span>
@@ -37,11 +37,6 @@ @@ -37,11 +37,6 @@
37 </tb-tenant-profile> 37 </tb-tenant-profile>
38 </div> 38 </div>
39 <div mat-dialog-actions fxLayoutAlign="end center"> 39 <div mat-dialog-actions fxLayoutAlign="end center">
40 - <button mat-raised-button color="primary"  
41 - type="submit"  
42 - [disabled]="(isLoading$ | async) || tenantProfileComponent.entityForm?.invalid || !tenantProfileComponent.entityForm?.dirty">  
43 - {{ (isAdd ? 'action.add' : 'action.save') | translate }}  
44 - </button>  
45 <button mat-button color="primary" 40 <button mat-button color="primary"
46 type="button" 41 type="button"
47 cdkFocusInitial 42 cdkFocusInitial
@@ -49,5 +44,10 @@ @@ -49,5 +44,10 @@
49 (click)="cancel()"> 44 (click)="cancel()">
50 {{ 'action.cancel' | translate }} 45 {{ 'action.cancel' | translate }}
51 </button> 46 </button>
  47 + <button mat-raised-button color="primary"
  48 + type="submit"
  49 + [disabled]="(isLoading$ | async) || tenantProfileComponent.entityForm?.invalid || !tenantProfileComponent.entityForm?.dirty">
  50 + {{ (isAdd ? 'action.add' : 'action.save') | translate }}
  51 + </button>
52 </div> 52 </div>
53 </form> 53 </form>
@@ -53,17 +53,16 @@ @@ -53,17 +53,16 @@
53 </div> 53 </div>
54 <div mat-dialog-actions fxLayout="row"> 54 <div mat-dialog-actions fxLayout="row">
55 <span fxFlex></span> 55 <span fxFlex></span>
56 - <button mat-button mat-raised-button color="primary"  
57 - type="submit"  
58 - [disabled]="(isLoading$ | async) || relationFormGroup.invalid || !(relationFormGroup.dirty || additionalInfo.dirty)">  
59 - {{ (isAdd ? 'action.add' : 'action.save') | translate }}  
60 - </button>  
61 <button mat-button color="primary" 56 <button mat-button color="primary"
62 - style="margin-right: 20px;"  
63 type="button" 57 type="button"
64 [disabled]="(isLoading$ | async)" 58 [disabled]="(isLoading$ | async)"
65 (click)="cancel()" cdkFocusInitial> 59 (click)="cancel()" cdkFocusInitial>
66 {{ 'action.cancel' | translate }} 60 {{ 'action.cancel' | translate }}
67 </button> 61 </button>
  62 + <button mat-button mat-raised-button color="primary"
  63 + type="submit"
  64 + [disabled]="(isLoading$ | async) || relationFormGroup.invalid || !(relationFormGroup.dirty || additionalInfo.dirty)">
  65 + {{ (isAdd ? 'action.add' : 'action.save') | translate }}
  66 + </button>
68 </div> 67 </div>
69 </form> 68 </form>
@@ -147,16 +147,16 @@ @@ -147,16 +147,16 @@
147 </fieldset> 147 </fieldset>
148 </div> 148 </div>
149 <div mat-dialog-actions fxLayoutAlign="end center"> 149 <div mat-dialog-actions fxLayoutAlign="end center">
150 - <button mat-raised-button color="primary"  
151 - type="submit"  
152 - [disabled]="(isLoading$ | async) || widgetActionFormGroup.invalid || actionTypeFormGroup.invalid || (!widgetActionFormGroup.dirty && !actionTypeFormGroup.dirty)">  
153 - {{ (isAdd ? 'action.add' : 'action.save') | translate }}  
154 - </button>  
155 <button mat-button color="primary" 150 <button mat-button color="primary"
156 type="button" 151 type="button"
157 [disabled]="(isLoading$ | async)" 152 [disabled]="(isLoading$ | async)"
158 (click)="cancel()" cdkFocusInitial> 153 (click)="cancel()" cdkFocusInitial>
159 {{ 'action.cancel' | translate }} 154 {{ 'action.cancel' | translate }}
160 </button> 155 </button>
  156 + <button mat-raised-button color="primary"
  157 + type="submit"
  158 + [disabled]="(isLoading$ | async) || widgetActionFormGroup.invalid || actionTypeFormGroup.invalid || (!widgetActionFormGroup.dirty && !actionTypeFormGroup.dirty)">
  159 + {{ (isAdd ? 'action.add' : 'action.save') | translate }}
  160 + </button>
161 </div> 161 </div>
162 </form> 162 </form>
@@ -38,16 +38,16 @@ @@ -38,16 +38,16 @@
38 </tb-data-key-config> 38 </tb-data-key-config>
39 </div> 39 </div>
40 <div mat-dialog-actions fxLayoutAlign="end center"> 40 <div mat-dialog-actions fxLayoutAlign="end center">
41 - <button mat-raised-button color="primary"  
42 - type="submit"  
43 - [disabled]="(isLoading$ | async) || dataKeyFormGroup.invalid || !dataKeyFormGroup.dirty">  
44 - {{ 'action.save' | translate }}  
45 - </button>  
46 <button mat-button color="primary" 41 <button mat-button color="primary"
47 type="button" 42 type="button"
48 [disabled]="(isLoading$ | async)" 43 [disabled]="(isLoading$ | async)"
49 (click)="cancel()" cdkFocusInitial> 44 (click)="cancel()" cdkFocusInitial>
50 {{ 'action.cancel' | translate }} 45 {{ 'action.cancel' | translate }}
51 </button> 46 </button>
  47 + <button mat-raised-button color="primary"
  48 + type="submit"
  49 + [disabled]="(isLoading$ | async) || dataKeyFormGroup.invalid || !dataKeyFormGroup.dirty">
  50 + {{ 'action.save' | translate }}
  51 + </button>
52 </div> 52 </div>
53 </form> 53 </form>
@@ -50,17 +50,16 @@ @@ -50,17 +50,16 @@
50 </mat-chip-list> 50 </mat-chip-list>
51 </mat-form-field> 51 </mat-form-field>
52 <div fxLayout="row" class="tb-panel-actions" fxLayoutAlign="end center"> 52 <div fxLayout="row" class="tb-panel-actions" fxLayoutAlign="end center">
  53 + <button type="button"
  54 + mat-button
  55 + (click)="cancel()">
  56 + {{ 'action.cancel' | translate }}
  57 + </button>
53 <button type="submit" 58 <button type="submit"
54 mat-raised-button 59 mat-raised-button
55 color="primary" 60 color="primary"
56 [disabled]="alarmFilterFormGroup.invalid || !alarmFilterFormGroup.dirty"> 61 [disabled]="alarmFilterFormGroup.invalid || !alarmFilterFormGroup.dirty">
57 {{ 'action.update' | translate }} 62 {{ 'action.update' | translate }}
58 </button> 63 </button>
59 - <button type="button"  
60 - mat-button  
61 - (click)="cancel()"  
62 - style="margin-right: 20px;">  
63 - {{ 'action.cancel' | translate }}  
64 - </button>  
65 </div> 64 </div>
66 </form> 65 </form>
@@ -28,7 +28,6 @@ @@ -28,7 +28,6 @@
28 <div class="tb-panel-actions" fxLayout="row" *ngIf="!settings.autoConfirm"> 28 <div class="tb-panel-actions" fxLayout="row" *ngIf="!settings.autoConfirm">
29 <span fxFlex></span> 29 <span fxFlex></span>
30 <button mat-button mat-raised-button color="primary" 30 <button mat-button mat-raised-button color="primary"
31 - style="margin-right: 20px;"  
32 type="button" (click)="apply()"> 31 type="button" (click)="apply()">
33 {{ 'action.ok' | translate }} 32 {{ 'action.ok' | translate }}
34 </button> 33 </button>
@@ -352,7 +352,8 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni @@ -352,7 +352,8 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni
352 } 352 }
353 dataKeys.push(dataKey); 353 dataKeys.push(dataKey);
354 354
355 - dataKey.title = this.utils.customTranslation(dataKey.label, dataKey.label); 355 + dataKey.label = this.utils.customTranslation(dataKey.label, dataKey.label);
  356 + dataKey.title = dataKey.label;
356 dataKey.def = 'def' + this.columns.length; 357 dataKey.def = 'def' + this.columns.length;
357 const keySettings: TableWidgetDataKeySettings = dataKey.settings; 358 const keySettings: TableWidgetDataKeySettings = dataKey.settings;
358 if (dataKey.type === DataKeyType.entityField && 359 if (dataKey.type === DataKeyType.entityField &&
@@ -374,7 +375,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni @@ -374,7 +375,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni
374 } 375 }
375 376
376 if (this.settings.defaultSortOrder && this.settings.defaultSortOrder.length) { 377 if (this.settings.defaultSortOrder && this.settings.defaultSortOrder.length) {
377 - this.defaultSortOrder = this.settings.defaultSortOrder; 378 + this.defaultSortOrder = this.utils.customTranslation(this.settings.defaultSortOrder, this.settings.defaultSortOrder);
378 } 379 }
379 380
380 this.pageLink.sortOrder = entityDataSortOrderFromString(this.defaultSortOrder, this.columns); 381 this.pageLink.sortOrder = entityDataSortOrderFromString(this.defaultSortOrder, this.columns);
@@ -40,17 +40,17 @@ @@ -40,17 +40,17 @@
40 </fieldset> 40 </fieldset>
41 </div> 41 </div>
42 <div mat-dialog-actions fxLayoutAlign="end center"> 42 <div mat-dialog-actions fxLayoutAlign="end center">
43 - <button mat-raised-button color="primary"  
44 - type="submit"  
45 - [disabled]="(isLoading$ | async) || addEntitiesToCustomerFormGroup.invalid  
46 - || !addEntitiesToCustomerFormGroup.dirty">  
47 - {{ 'action.assign' | translate }}  
48 - </button>  
49 <button mat-button color="primary" 43 <button mat-button color="primary"
50 type="button" 44 type="button"
51 [disabled]="(isLoading$ | async)" 45 [disabled]="(isLoading$ | async)"
52 (click)="cancel()" cdkFocusInitial> 46 (click)="cancel()" cdkFocusInitial>
53 {{ 'action.cancel' | translate }} 47 {{ 'action.cancel' | translate }}
54 </button> 48 </button>
  49 + <button mat-raised-button color="primary"
  50 + type="submit"
  51 + [disabled]="(isLoading$ | async) || addEntitiesToCustomerFormGroup.invalid
  52 + || !addEntitiesToCustomerFormGroup.dirty">
  53 + {{ 'action.assign' | translate }}
  54 + </button>
55 </div> 55 </div>
56 </form> 56 </form>
@@ -39,17 +39,17 @@ @@ -39,17 +39,17 @@
39 </fieldset> 39 </fieldset>
40 </div> 40 </div>
41 <div mat-dialog-actions fxLayout="row" fxLayoutAlign="end center"> 41 <div mat-dialog-actions fxLayout="row" fxLayoutAlign="end center">
42 - <button mat-raised-button color="primary"  
43 - type="submit"  
44 - [disabled]="(isLoading$ | async) || assignToCustomerFormGroup.invalid  
45 - || !assignToCustomerFormGroup.dirty">  
46 - {{ 'action.assign' | translate }}  
47 - </button>  
48 <button mat-button color="primary" 42 <button mat-button color="primary"
49 type="button" 43 type="button"
50 [disabled]="(isLoading$ | async)" 44 [disabled]="(isLoading$ | async)"
51 (click)="cancel()" cdkFocusInitial> 45 (click)="cancel()" cdkFocusInitial>
52 {{ 'action.cancel' | translate }} 46 {{ 'action.cancel' | translate }}
53 </button> 47 </button>
  48 + <button mat-raised-button color="primary"
  49 + type="submit"
  50 + [disabled]="(isLoading$ | async) || assignToCustomerFormGroup.invalid
  51 + || !assignToCustomerFormGroup.dirty">
  52 + {{ 'action.assign' | translate }}
  53 + </button>
54 </div> 54 </div>
55 </form> 55 </form>
@@ -14,8 +14,8 @@ @@ -14,8 +14,8 @@
14 /// limitations under the License. 14 /// limitations under the License.
15 /// 15 ///
16 16
17 -import { NgModule } from '@angular/core';  
18 -import { RouterModule, Routes } from '@angular/router'; 17 +import { Injectable, NgModule } from '@angular/core';
  18 +import { Resolve, RouterModule, Routes } from '@angular/router';
19 19
20 import { MailServerComponent } from '@modules/home/pages/admin/mail-server.component'; 20 import { MailServerComponent } from '@modules/home/pages/admin/mail-server.component';
21 import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard'; 21 import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard';
@@ -23,6 +23,25 @@ import { Authority } from '@shared/models/authority.enum'; @@ -23,6 +23,25 @@ import { Authority } from '@shared/models/authority.enum';
23 import { GeneralSettingsComponent } from '@modules/home/pages/admin/general-settings.component'; 23 import { GeneralSettingsComponent } from '@modules/home/pages/admin/general-settings.component';
24 import { SecuritySettingsComponent } from '@modules/home/pages/admin/security-settings.component'; 24 import { SecuritySettingsComponent } from '@modules/home/pages/admin/security-settings.component';
25 import { OAuth2SettingsComponent } from '@home/pages/admin/oauth2-settings.component'; 25 import { OAuth2SettingsComponent } from '@home/pages/admin/oauth2-settings.component';
  26 +import { User } from '@shared/models/user.model';
  27 +import { Store } from '@ngrx/store';
  28 +import { AppState } from '@core/core.state';
  29 +import { UserService } from '@core/http/user.service';
  30 +import { Observable } from 'rxjs';
  31 +import { getCurrentAuthUser } from '@core/auth/auth.selectors';
  32 +import { OAuth2Service } from '@core/http/oauth2.service';
  33 +import { UserProfileResolver } from '@home/pages/profile/profile-routing.module';
  34 +
  35 +@Injectable()
  36 +export class OAuth2LoginProcessingUrlResolver implements Resolve<string> {
  37 +
  38 + constructor(private oauth2Service: OAuth2Service) {
  39 + }
  40 +
  41 + resolve(): Observable<string> {
  42 + return this.oauth2Service.getLoginProcessingUrl();
  43 + }
  44 +}
26 45
27 const routes: Routes = [ 46 const routes: Routes = [
28 { 47 {
@@ -90,6 +109,9 @@ const routes: Routes = [ @@ -90,6 +109,9 @@ const routes: Routes = [
90 label: 'admin.oauth2.oauth2', 109 label: 'admin.oauth2.oauth2',
91 icon: 'security' 110 icon: 'security'
92 } 111 }
  112 + },
  113 + resolve: {
  114 + loginProcessingUrl: OAuth2LoginProcessingUrlResolver
93 } 115 }
94 } 116 }
95 ] 117 ]
@@ -98,6 +120,9 @@ const routes: Routes = [ @@ -98,6 +120,9 @@ const routes: Routes = [
98 120
99 @NgModule({ 121 @NgModule({
100 imports: [RouterModule.forChild(routes)], 122 imports: [RouterModule.forChild(routes)],
101 - exports: [RouterModule] 123 + exports: [RouterModule],
  124 + providers: [
  125 + OAuth2LoginProcessingUrlResolver
  126 + ]
102 }) 127 })
103 export class AdminRoutingModule { } 128 export class AdminRoutingModule { }
@@ -43,6 +43,7 @@ import { DialogService } from '@core/services/dialog.service'; @@ -43,6 +43,7 @@ import { DialogService } from '@core/services/dialog.service';
43 import { TranslateService } from '@ngx-translate/core'; 43 import { TranslateService } from '@ngx-translate/core';
44 import { isDefined, isDefinedAndNotNull } from '@core/utils'; 44 import { isDefined, isDefinedAndNotNull } from '@core/utils';
45 import { OAuth2Service } from '@core/http/oauth2.service'; 45 import { OAuth2Service } from '@core/http/oauth2.service';
  46 +import { ActivatedRoute } from '@angular/router';
46 47
47 @Component({ 48 @Component({
48 selector: 'tb-oauth2-settings', 49 selector: 'tb-oauth2-settings',
@@ -87,7 +88,10 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha @@ -87,7 +88,10 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
87 88
88 templateProvider = ['Custom']; 89 templateProvider = ['Custom'];
89 90
  91 + private loginProcessingUrl: string = this.route.snapshot.data.loginProcessingUrl;
  92 +
90 constructor(protected store: Store<AppState>, 93 constructor(protected store: Store<AppState>,
  94 + private route: ActivatedRoute,
91 private oauth2Service: OAuth2Service, 95 private oauth2Service: OAuth2Service,
92 private fb: FormBuilder, 96 private fb: FormBuilder,
93 private dialogService: DialogService, 97 private dialogService: DialogService,
@@ -130,7 +134,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha @@ -130,7 +134,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
130 return this.oauth2SettingsForm.get('domainsParams') as FormArray; 134 return this.oauth2SettingsForm.get('domainsParams') as FormArray;
131 } 135 }
132 136
133 - private formBasicGroup(mapperConfigBasic?: MapperConfigBasic): FormGroup { 137 + private formBasicGroup(type: MapperConfigType, mapperConfigBasic?: MapperConfigBasic): FormGroup {
134 let tenantNamePattern; 138 let tenantNamePattern;
135 if (mapperConfigBasic?.tenantNamePattern) { 139 if (mapperConfigBasic?.tenantNamePattern) {
136 tenantNamePattern = mapperConfigBasic.tenantNamePattern; 140 tenantNamePattern = mapperConfigBasic.tenantNamePattern;
@@ -138,16 +142,20 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha @@ -138,16 +142,20 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
138 tenantNamePattern = {value: null, disabled: true}; 142 tenantNamePattern = {value: null, disabled: true};
139 } 143 }
140 const basicGroup = this.fb.group({ 144 const basicGroup = this.fb.group({
141 - emailAttributeKey: [mapperConfigBasic?.emailAttributeKey ? mapperConfigBasic.emailAttributeKey : 'email', Validators.required],  
142 firstNameAttributeKey: [mapperConfigBasic?.firstNameAttributeKey ? mapperConfigBasic.firstNameAttributeKey : ''], 145 firstNameAttributeKey: [mapperConfigBasic?.firstNameAttributeKey ? mapperConfigBasic.firstNameAttributeKey : ''],
143 lastNameAttributeKey: [mapperConfigBasic?.lastNameAttributeKey ? mapperConfigBasic.lastNameAttributeKey : ''], 146 lastNameAttributeKey: [mapperConfigBasic?.lastNameAttributeKey ? mapperConfigBasic.lastNameAttributeKey : ''],
144 tenantNameStrategy: [mapperConfigBasic?.tenantNameStrategy ? mapperConfigBasic.tenantNameStrategy : TenantNameStrategy.DOMAIN], 147 tenantNameStrategy: [mapperConfigBasic?.tenantNameStrategy ? mapperConfigBasic.tenantNameStrategy : TenantNameStrategy.DOMAIN],
145 tenantNamePattern: [tenantNamePattern, Validators.required], 148 tenantNamePattern: [tenantNamePattern, Validators.required],
146 customerNamePattern: [mapperConfigBasic?.customerNamePattern ? mapperConfigBasic.customerNamePattern : null], 149 customerNamePattern: [mapperConfigBasic?.customerNamePattern ? mapperConfigBasic.customerNamePattern : null],
147 defaultDashboardName: [mapperConfigBasic?.defaultDashboardName ? mapperConfigBasic.defaultDashboardName : null], 150 defaultDashboardName: [mapperConfigBasic?.defaultDashboardName ? mapperConfigBasic.defaultDashboardName : null],
148 - alwaysFullScreen: [mapperConfigBasic?.alwaysFullScreen ? mapperConfigBasic.alwaysFullScreen : false] 151 + alwaysFullScreen: [isDefinedAndNotNull(mapperConfigBasic?.alwaysFullScreen) ? mapperConfigBasic.alwaysFullScreen : false]
149 }); 152 });
150 153
  154 + if (MapperConfigType.GITHUB !== type) {
  155 + basicGroup.addControl('emailAttributeKey',
  156 + this.fb.control( mapperConfigBasic?.emailAttributeKey ? mapperConfigBasic.emailAttributeKey : 'email', Validators.required));
  157 + }
  158 +
151 this.subscriptions.push(basicGroup.get('tenantNameStrategy').valueChanges.subscribe((domain) => { 159 this.subscriptions.push(basicGroup.get('tenantNameStrategy').valueChanges.subscribe((domain) => {
152 if (domain === 'CUSTOM') { 160 if (domain === 'CUSTOM') {
153 basicGroup.get('tenantNamePattern').enable(); 161 basicGroup.get('tenantNamePattern').enable();
@@ -279,9 +287,12 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha @@ -279,9 +287,12 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
279 clientRegistration?.userNameAttributeName ? clientRegistration.userNameAttributeName : 'email', Validators.required], 287 clientRegistration?.userNameAttributeName ? clientRegistration.userNameAttributeName : 'email', Validators.required],
280 mapperConfig: this.fb.group({ 288 mapperConfig: this.fb.group({
281 allowUserCreation: [ 289 allowUserCreation: [
282 - clientRegistration?.mapperConfig?.allowUserCreation ? clientRegistration.mapperConfig.allowUserCreation : true 290 + isDefinedAndNotNull(clientRegistration?.mapperConfig?.allowUserCreation) ?
  291 + clientRegistration.mapperConfig.allowUserCreation : true
  292 + ],
  293 + activateUser: [
  294 + isDefinedAndNotNull(clientRegistration?.mapperConfig?.activateUser) ? clientRegistration.mapperConfig.activateUser : false
283 ], 295 ],
284 - activateUser: [clientRegistration?.mapperConfig?.activateUser ? clientRegistration.mapperConfig.activateUser : false],  
285 type: [ 296 type: [
286 clientRegistration?.mapperConfig?.type ? clientRegistration.mapperConfig.type : MapperConfigType.BASIC, Validators.required 297 clientRegistration?.mapperConfig?.type ? clientRegistration.mapperConfig.type : MapperConfigType.BASIC, Validators.required
287 ] 298 ]
@@ -308,7 +319,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha @@ -308,7 +319,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
308 return clientRegistrationFormGroup; 319 return clientRegistrationFormGroup;
309 } 320 }
310 321
311 - private validateScope (control: AbstractControl): ValidationErrors | null { 322 + private validateScope(control: AbstractControl): ValidationErrors | null {
312 const scope: string[] = control.value; 323 const scope: string[] = control.value;
313 if (!scope || !scope.length) { 324 if (!scope || !scope.length) {
314 return { 325 return {
@@ -347,7 +358,11 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha @@ -347,7 +358,11 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
347 mapperConfig.addControl('custom', this.formCustomGroup(predefinedValue?.custom)); 358 mapperConfig.addControl('custom', this.formCustomGroup(predefinedValue?.custom));
348 } else { 359 } else {
349 mapperConfig.removeControl('custom'); 360 mapperConfig.removeControl('custom');
350 - mapperConfig.addControl('basic', this.formBasicGroup(predefinedValue?.basic)); 361 + if (mapperConfig.get('basic')) {
  362 + mapperConfig.setControl('basic', this.formBasicGroup(type, predefinedValue?.basic));
  363 + } else {
  364 + mapperConfig.addControl('basic', this.formBasicGroup(type, predefinedValue?.basic));
  365 + }
351 } 366 }
352 } 367 }
353 368
@@ -490,7 +505,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha @@ -490,7 +505,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
490 } else { 505 } else {
491 protocol = domainInfo.scheme === DomainSchema.MIXED ? DomainSchema.HTTPS.toLowerCase() : domainInfo.scheme.toLowerCase(); 506 protocol = domainInfo.scheme === DomainSchema.MIXED ? DomainSchema.HTTPS.toLowerCase() : domainInfo.scheme.toLowerCase();
492 } 507 }
493 - return `${protocol}://${domainInfo.name}/login/oauth2/code/`; 508 + return `${protocol}://${domainInfo.name}${this.loginProcessingUrl}`;
494 } 509 }
495 return ''; 510 return '';
496 } 511 }
@@ -41,11 +41,6 @@ @@ -41,11 +41,6 @@
41 </fieldset> 41 </fieldset>
42 </div> 42 </div>
43 <div mat-dialog-actions fxLayoutAlign="end center"> 43 <div mat-dialog-actions fxLayoutAlign="end center">
44 - <button mat-raised-button color="primary"  
45 - type="submit"  
46 - [disabled]="(isLoading$ | async) || widgetFormGroup.invalid">  
47 - {{ 'action.add' | translate }}  
48 - </button>  
49 <button mat-button color="primary" 44 <button mat-button color="primary"
50 type="button" 45 type="button"
51 [disabled]="(isLoading$ | async)" 46 [disabled]="(isLoading$ | async)"
@@ -53,5 +48,10 @@ @@ -53,5 +48,10 @@
53 cdkFocusInitial> 48 cdkFocusInitial>
54 {{ 'action.cancel' | translate }} 49 {{ 'action.cancel' | translate }}
55 </button> 50 </button>
  51 + <button mat-raised-button color="primary"
  52 + type="submit"
  53 + [disabled]="(isLoading$ | async) || widgetFormGroup.invalid">
  54 + {{ 'action.add' | translate }}
  55 + </button>
56 </div> 56 </div>
57 </form> 57 </form>
@@ -152,17 +152,17 @@ @@ -152,17 +152,17 @@
152 </fieldset> 152 </fieldset>
153 </div> 153 </div>
154 <div mat-dialog-actions fxLayoutAlign="end center"> 154 <div mat-dialog-actions fxLayoutAlign="end center">
155 - <button mat-raised-button color="primary"  
156 - type="submit"  
157 - [disabled]="(isLoading$ | async) || settingsFormGroup.invalid || gridSettingsFormGroup.invalid  
158 - || (!settingsFormGroup.dirty && !gridSettingsFormGroup.dirty)">  
159 - {{ 'action.save' | translate }}  
160 - </button>  
161 <button mat-button color="primary" 155 <button mat-button color="primary"
162 type="button" 156 type="button"
163 [disabled]="(isLoading$ | async)" 157 [disabled]="(isLoading$ | async)"
164 (click)="cancel()" cdkFocusInitial> 158 (click)="cancel()" cdkFocusInitial>
165 {{ 'action.cancel' | translate }} 159 {{ 'action.cancel' | translate }}
166 </button> 160 </button>
  161 + <button mat-raised-button color="primary"
  162 + type="submit"
  163 + [disabled]="(isLoading$ | async) || settingsFormGroup.invalid || gridSettingsFormGroup.invalid
  164 + || (!settingsFormGroup.dirty && !gridSettingsFormGroup.dirty)">
  165 + {{ 'action.save' | translate }}
  166 + </button>
167 </div> 167 </div>
168 </form> 168 </form>
@@ -54,11 +54,6 @@ @@ -54,11 +54,6 @@
54 </fieldset> 54 </fieldset>
55 </div> 55 </div>
56 <div mat-dialog-actions fxLayoutAlign="end center"> 56 <div mat-dialog-actions fxLayoutAlign="end center">
57 - <button mat-raised-button color="primary"  
58 - type="submit"  
59 - [disabled]="(isLoading$ | async) || layoutsFormGroup.invalid || !layoutsFormGroup.dirty">  
60 - {{ 'action.save' | translate }}  
61 - </button>  
62 <button mat-button 57 <button mat-button
63 color="primary" 58 color="primary"
64 type="button" 59 type="button"
@@ -66,5 +61,10 @@ @@ -66,5 +61,10 @@
66 (click)="cancel()" cdkFocusInitial> 61 (click)="cancel()" cdkFocusInitial>
67 {{ 'action.cancel' | translate }} 62 {{ 'action.cancel' | translate }}
68 </button> 63 </button>
  64 + <button mat-raised-button color="primary"
  65 + type="submit"
  66 + [disabled]="(isLoading$ | async) || layoutsFormGroup.invalid || !layoutsFormGroup.dirty">
  67 + {{ 'action.save' | translate }}
  68 + </button>
69 </div> 69 </div>
70 </form> 70 </form>
@@ -54,7 +54,6 @@ @@ -54,7 +54,6 @@
54 <div mat-dialog-actions fxLayout="row"> 54 <div mat-dialog-actions fxLayout="row">
55 <span fxFlex></span> 55 <span fxFlex></span>
56 <button mat-button color="primary" 56 <button mat-button color="primary"
57 - style="margin-right: 20px;"  
58 type="button" 57 type="button"
59 [disabled]="(isLoading$ | async)" 58 [disabled]="(isLoading$ | async)"
60 (click)="close()" cdkFocusInitial> 59 (click)="close()" cdkFocusInitial>
@@ -41,18 +41,17 @@ @@ -41,18 +41,17 @@
41 </div> 41 </div>
42 <div mat-dialog-actions fxLayout="row"> 42 <div mat-dialog-actions fxLayout="row">
43 <span fxFlex></span> 43 <span fxFlex></span>
44 - <button mat-button mat-raised-button color="primary"  
45 - type="submit"  
46 - [disabled]="(isLoading$ | async) || dashboardCustomersFormGroup.invalid  
47 - || !dashboardCustomersFormGroup.dirty">  
48 - {{ actionName | translate }}  
49 - </button>  
50 <button mat-button color="primary" 44 <button mat-button color="primary"
51 - style="margin-right: 20px;"  
52 type="button" 45 type="button"
53 [disabled]="(isLoading$ | async)" 46 [disabled]="(isLoading$ | async)"
54 (click)="cancel()" cdkFocusInitial> 47 (click)="cancel()" cdkFocusInitial>
55 {{ 'action.cancel' | translate }} 48 {{ 'action.cancel' | translate }}
56 </button> 49 </button>
  50 + <button mat-button mat-raised-button color="primary"
  51 + type="submit"
  52 + [disabled]="(isLoading$ | async) || dashboardCustomersFormGroup.invalid
  53 + || !dashboardCustomersFormGroup.dirty">
  54 + {{ actionName | translate }}
  55 + </button>
57 </div> 56 </div>
58 </form> 57 </form>
@@ -52,16 +52,16 @@ @@ -52,16 +52,16 @@
52 </fieldset> 52 </fieldset>
53 </div> 53 </div>
54 <div mat-dialog-actions fxLayoutAlign="end center"> 54 <div mat-dialog-actions fxLayoutAlign="end center">
55 - <button mat-raised-button color="primary"  
56 - type="submit"  
57 - [disabled]="(isLoading$ | async) || stateFormGroup.invalid || !stateFormGroup.dirty">  
58 - {{ (isAdd ? 'action.add' : 'action.save') | translate }}  
59 - </button>  
60 <button mat-button color="primary" 55 <button mat-button color="primary"
61 type="button" 56 type="button"
62 [disabled]="(isLoading$ | async)" 57 [disabled]="(isLoading$ | async)"
63 (click)="cancel()" cdkFocusInitial> 58 (click)="cancel()" cdkFocusInitial>
64 {{ 'action.cancel' | translate }} 59 {{ 'action.cancel' | translate }}
65 </button> 60 </button>
  61 + <button mat-raised-button color="primary"
  62 + type="submit"
  63 + [disabled]="(isLoading$ | async) || stateFormGroup.invalid || !stateFormGroup.dirty">
  64 + {{ (isAdd ? 'action.add' : 'action.save') | translate }}
  65 + </button>
66 </div> 66 </div>
67 </form> 67 </form>
@@ -136,16 +136,16 @@ @@ -136,16 +136,16 @@
136 </fieldset> 136 </fieldset>
137 </div> 137 </div>
138 <div mat-dialog-actions fxLayoutAlign="end center"> 138 <div mat-dialog-actions fxLayoutAlign="end center">
139 - <button mat-raised-button color="primary"  
140 - type="submit"  
141 - [disabled]="(isLoading$ | async) || statesFormGroup.invalid || !statesFormGroup.dirty">  
142 - {{ 'action.save' | translate }}  
143 - </button>  
144 <button mat-button color="primary" 139 <button mat-button color="primary"
145 type="button" 140 type="button"
146 [disabled]="(isLoading$ | async)" 141 [disabled]="(isLoading$ | async)"
147 (click)="cancel()" cdkFocusInitial> 142 (click)="cancel()" cdkFocusInitial>
148 {{ 'action.cancel' | translate }} 143 {{ 'action.cancel' | translate }}
149 </button> 144 </button>
  145 + <button mat-raised-button color="primary"
  146 + type="submit"
  147 + [disabled]="(isLoading$ | async) || statesFormGroup.invalid || !statesFormGroup.dirty">
  148 + {{ 'action.save' | translate }}
  149 + </button>
150 </div> 150 </div>
151 </form> 151 </form>
@@ -70,7 +70,7 @@ @@ -70,7 +70,7 @@
70 </div> 70 </div>
71 </div> 71 </div>
72 </mat-tab> 72 </mat-tab>
73 -<mat-tab *ngIf="entity" 73 +<mat-tab *ngIf="entity && !isEdit"
74 label="{{ 'audit-log.audit-logs' | translate }}" #auditLogsTab="matTab"> 74 label="{{ 'audit-log.audit-logs' | translate }}" #auditLogsTab="matTab">
75 <tb-audit-log-table detailsMode="true" [active]="auditLogsTab.isActive" [auditLogMode]="auditLogModes.ENTITY" [entityId]="entity.id"></tb-audit-log-table> 75 <tb-audit-log-table detailsMode="true" [active]="auditLogsTab.isActive" [auditLogMode]="auditLogModes.ENTITY" [entityId]="entity.id"></tb-audit-log-table>
76 </mat-tab> 76 </mat-tab>
@@ -36,17 +36,17 @@ @@ -36,17 +36,17 @@
36 </fieldset> 36 </fieldset>
37 </div> 37 </div>
38 <div mat-dialog-actions fxLayoutAlign="end center"> 38 <div mat-dialog-actions fxLayoutAlign="end center">
39 - <button *ngIf="!isReadOnly" mat-raised-button color="primary"  
40 - type="submit"  
41 - [disabled]="(isLoading$ | async) || deviceCredentialsFormGroup.invalid  
42 - || !deviceCredentialsFormGroup.dirty">  
43 - {{ 'action.save' | translate }}  
44 - </button>  
45 <button mat-button color="primary" 39 <button mat-button color="primary"
46 type="button" 40 type="button"
47 [disabled]="(isLoading$ | async)" 41 [disabled]="(isLoading$ | async)"
48 (click)="cancel()" cdkFocusInitial> 42 (click)="cancel()" cdkFocusInitial>
49 {{ (isReadOnly ? 'action.close' : 'action.cancel') | translate }} 43 {{ (isReadOnly ? 'action.close' : 'action.cancel') | translate }}
50 </button> 44 </button>
  45 + <button *ngIf="!isReadOnly" mat-raised-button color="primary"
  46 + type="submit"
  47 + [disabled]="(isLoading$ | async) || deviceCredentialsFormGroup.invalid
  48 + || !deviceCredentialsFormGroup.dirty">
  49 + {{ 'action.save' | translate }}
  50 + </button>
51 </div> 51 </div>
52 </form> 52 </form>
@@ -46,16 +46,16 @@ @@ -46,16 +46,16 @@
46 </mat-form-field> 46 </mat-form-field>
47 </div> 47 </div>
48 <div mat-dialog-actions fxLayout="row" fxLayoutAlign="end center"> 48 <div mat-dialog-actions fxLayout="row" fxLayoutAlign="end center">
49 - <button mat-raised-button color="primary"  
50 - type="submit"  
51 - [disabled]="(isLoading$ | async) || changePassword.invalid">  
52 - {{ 'profile.change-password' | translate }}  
53 - </button>  
54 <button mat-button color="primary" 49 <button mat-button color="primary"
55 type="button" 50 type="button"
56 [disabled]="(isLoading$ | async)" 51 [disabled]="(isLoading$ | async)"
57 [mat-dialog-close]="false" cdkFocusInitial> 52 [mat-dialog-close]="false" cdkFocusInitial>
58 {{ 'action.cancel' | translate }} 53 {{ 'action.cancel' | translate }}
59 </button> 54 </button>
  55 + <button mat-raised-button color="primary"
  56 + type="submit"
  57 + [disabled]="(isLoading$ | async) || changePassword.invalid">
  58 + {{ 'profile.change-password' | translate }}
  59 + </button>
60 </div> 60 </div>
61 </form> 61 </form>
@@ -40,16 +40,16 @@ @@ -40,16 +40,16 @@
40 </fieldset> 40 </fieldset>
41 </div> 41 </div>
42 <div mat-dialog-actions fxLayoutAlign="end center"> 42 <div mat-dialog-actions fxLayoutAlign="end center">
43 - <button mat-raised-button color="primary"  
44 - type="submit"  
45 - [disabled]="(isLoading$ | async) || tbRuleNode.ruleNodeFormGroup.invalid || !tbRuleNode.ruleNodeFormGroup.dirty">  
46 - {{ 'action.add' | translate }}  
47 - </button>  
48 <button mat-button color="primary" 43 <button mat-button color="primary"
49 type="button" 44 type="button"
50 [disabled]="(isLoading$ | async)" 45 [disabled]="(isLoading$ | async)"
51 (click)="cancel()" cdkFocusInitial> 46 (click)="cancel()" cdkFocusInitial>
52 {{ 'action.cancel' | translate }} 47 {{ 'action.cancel' | translate }}
53 </button> 48 </button>
  49 + <button mat-raised-button color="primary"
  50 + type="submit"
  51 + [disabled]="(isLoading$ | async) || tbRuleNode.ruleNodeFormGroup.invalid || !tbRuleNode.ruleNodeFormGroup.dirty">
  52 + {{ 'action.add' | translate }}
  53 + </button>
54 </div> 54 </div>
55 </form> 55 </form>
@@ -39,16 +39,16 @@ @@ -39,16 +39,16 @@
39 </fieldset> 39 </fieldset>
40 </div> 40 </div>
41 <div mat-dialog-actions fxLayoutAlign="end center"> 41 <div mat-dialog-actions fxLayoutAlign="end center">
42 - <button mat-raised-button color="primary"  
43 - type="submit"  
44 - [disabled]="(isLoading$ | async) || ruleNodeLinkFormGroup.invalid || !ruleNodeLinkFormGroup.dirty">  
45 - {{ 'action.add' | translate }}  
46 - </button>  
47 <button mat-button color="primary" 42 <button mat-button color="primary"
48 type="button" 43 type="button"
49 [disabled]="(isLoading$ | async)" 44 [disabled]="(isLoading$ | async)"
50 (click)="cancel()" cdkFocusInitial> 45 (click)="cancel()" cdkFocusInitial>
51 {{ 'action.cancel' | translate }} 46 {{ 'action.cancel' | translate }}
52 </button> 47 </button>
  48 + <button mat-raised-button color="primary"
  49 + type="submit"
  50 + [disabled]="(isLoading$ | async) || ruleNodeLinkFormGroup.invalid || !ruleNodeLinkFormGroup.dirty">
  51 + {{ 'action.add' | translate }}
  52 + </button>
53 </div> 53 </div>
54 </form> 54 </form>
@@ -41,11 +41,6 @@ @@ -41,11 +41,6 @@
41 </mat-form-field> 41 </mat-form-field>
42 </div> 42 </div>
43 <div mat-dialog-actions fxLayoutAlign="end center"> 43 <div mat-dialog-actions fxLayoutAlign="end center">
44 - <button mat-raised-button color="primary"  
45 - type="submit"  
46 - [disabled]="(isLoading$ | async) || detailsForm.invalid || !detailsForm.dirty">  
47 - {{ 'action.add' | translate }}  
48 - </button>  
49 <button mat-button color="primary" 44 <button mat-button color="primary"
50 type="button" 45 type="button"
51 cdkFocusInitial 46 cdkFocusInitial
@@ -53,5 +48,10 @@ @@ -53,5 +48,10 @@
53 (click)="cancel()"> 48 (click)="cancel()">
54 {{ 'action.cancel' | translate }} 49 {{ 'action.cancel' | translate }}
55 </button> 50 </button>
  51 + <button mat-raised-button color="primary"
  52 + type="submit"
  53 + [disabled]="(isLoading$ | async) || detailsForm.invalid || !detailsForm.dirty">
  54 + {{ 'action.add' | translate }}
  55 + </button>
56 </div> 56 </div>
57 </form> 57 </form>
@@ -46,17 +46,17 @@ @@ -46,17 +46,17 @@
46 </fieldset> 46 </fieldset>
47 </div> 47 </div>
48 <div mat-dialog-actions fxLayoutAlign="end center"> 48 <div mat-dialog-actions fxLayoutAlign="end center">
49 - <button mat-raised-button color="primary"  
50 - type="submit"  
51 - [disabled]="(isLoading$ | async) || saveWidgetTypeAsFormGroup.invalid  
52 - || !saveWidgetTypeAsFormGroup.dirty">  
53 - {{ 'action.saveAs' | translate }}  
54 - </button>  
55 <button mat-button color="primary" 49 <button mat-button color="primary"
56 type="button" 50 type="button"
57 [disabled]="(isLoading$ | async)" 51 [disabled]="(isLoading$ | async)"
58 (click)="cancel()" cdkFocusInitial> 52 (click)="cancel()" cdkFocusInitial>
59 {{ 'action.cancel' | translate }} 53 {{ 'action.cancel' | translate }}
60 </button> 54 </button>
  55 + <button mat-raised-button color="primary"
  56 + type="submit"
  57 + [disabled]="(isLoading$ | async) || saveWidgetTypeAsFormGroup.invalid
  58 + || !saveWidgetTypeAsFormGroup.dirty">
  59 + {{ 'action.saveAs' | translate }}
  60 + </button>
61 </div> 61 </div>
62 </form> 62 </form>
@@ -34,7 +34,6 @@ @@ -34,7 +34,6 @@
34 </button> 34 </button>
35 <button mat-button 35 <button mat-button
36 type="submit" 36 type="submit"
37 - style="margin-right: 20px;"  
38 [disabled]="(isLoading$ | async) || colorPickerFormGroup.invalid || !colorPickerFormGroup.dirty"> 37 [disabled]="(isLoading$ | async) || colorPickerFormGroup.invalid || !colorPickerFormGroup.dirty">
39 {{ 'action.select' | translate }} 38 {{ 'action.select' | translate }}
40 </button> 39 </button>
@@ -41,17 +41,16 @@ @@ -41,17 +41,16 @@
41 </div> 41 </div>
42 <div mat-dialog-actions fxLayout="row" fxLayoutAlign="end center"> 42 <div mat-dialog-actions fxLayout="row" fxLayoutAlign="end center">
43 <span fxFlex></span> 43 <span fxFlex></span>
44 - <button mat-button mat-raised-button color="primary"  
45 - type="submit"  
46 - [disabled]="(isLoading$ | async) || jsonFormGroup.invalid || !jsonFormGroup.dirty">  
47 - {{ 'action.save' | translate }}  
48 - </button>  
49 <button mat-button color="primary" 44 <button mat-button color="primary"
50 - style="margin-right: 20px;"  
51 type="button" 45 type="button"
52 [disabled]="(isLoading$ | async)" 46 [disabled]="(isLoading$ | async)"
53 (click)="cancel()" cdkFocusInitial> 47 (click)="cancel()" cdkFocusInitial>
54 {{ 'action.cancel' | translate }} 48 {{ 'action.cancel' | translate }}
55 </button> 49 </button>
  50 + <button mat-button mat-raised-button color="primary"
  51 + type="submit"
  52 + [disabled]="(isLoading$ | async) || jsonFormGroup.invalid || !jsonFormGroup.dirty">
  53 + {{ 'action.save' | translate }}
  54 + </button>
56 </div> 55 </div>
57 </form> 56 </form>
@@ -109,18 +109,17 @@ @@ -109,18 +109,17 @@
109 {{ 'rulenode.test' | translate }} 109 {{ 'rulenode.test' | translate }}
110 </button> 110 </button>
111 <span fxFlex></span> 111 <span fxFlex></span>
112 - <button mat-button mat-raised-button color="primary"  
113 - type="submit"  
114 - [disabled]="(isLoading$ | async) || nodeScriptTestFormGroup.get('script').invalid || !nodeScriptTestFormGroup.get('script').dirty">  
115 - {{ 'action.save' | translate }}  
116 - </button>  
117 <button mat-button color="primary" 112 <button mat-button color="primary"
118 - style="margin-right: 20px;"  
119 type="button" 113 type="button"
120 cdkFocusInitial 114 cdkFocusInitial
121 [disabled]="(isLoading$ | async)" 115 [disabled]="(isLoading$ | async)"
122 (click)="cancel()"> 116 (click)="cancel()">
123 {{ 'action.cancel' | translate }} 117 {{ 'action.cancel' | translate }}
124 </button> 118 </button>
  119 + <button mat-button mat-raised-button color="primary"
  120 + type="submit"
  121 + [disabled]="(isLoading$ | async) || nodeScriptTestFormGroup.get('script').invalid || !nodeScriptTestFormGroup.get('script').dirty">
  122 + {{ 'action.save' | translate }}
  123 + </button>
125 </div> 124 </div>
126 </form> 125 </form>
@@ -140,19 +140,18 @@ @@ -140,19 +140,18 @@
140 </tb-timeinterval> 140 </tb-timeinterval>
141 </div> 141 </div>
142 <div fxLayout="row" class="tb-panel-actions" fxLayoutAlign="end center"> 142 <div fxLayout="row" class="tb-panel-actions" fxLayoutAlign="end center">
  143 + <button type="button"
  144 + mat-button
  145 + [disabled]="(isLoading$ | async)"
  146 + (click)="cancel()">
  147 + {{ 'action.cancel' | translate }}
  148 + </button>
143 <button type="submit" 149 <button type="submit"
144 mat-raised-button 150 mat-raised-button
145 color="primary" 151 color="primary"
146 [disabled]="(isLoading$ | async) || timewindowForm.invalid || !timewindowForm.dirty"> 152 [disabled]="(isLoading$ | async) || timewindowForm.invalid || !timewindowForm.dirty">
147 {{ 'action.update' | translate }} 153 {{ 'action.update' | translate }}
148 </button> 154 </button>
149 - <button type="button"  
150 - mat-button  
151 - [disabled]="(isLoading$ | async)"  
152 - (click)="cancel()"  
153 - style="margin-right: 20px;">  
154 - {{ 'action.cancel' | translate }}  
155 - </button>  
156 </div> 155 </div>
157 </div> 156 </div>
158 </fieldset> 157 </fieldset>
@@ -26,7 +26,7 @@ import { EntityInfoData } from '@shared/models/entity.models'; @@ -26,7 +26,7 @@ import { EntityInfoData } from '@shared/models/entity.models';
26 import { KeyFilter } from '@shared/models/query/query.models'; 26 import { KeyFilter } from '@shared/models/query/query.models';
27 import { TimeUnit } from '@shared/models/time/time.models'; 27 import { TimeUnit } from '@shared/models/time/time.models';
28 import * as _moment from 'moment-timezone'; 28 import * as _moment from 'moment-timezone';
29 -import { AbstractControl, FormGroup } from '@angular/forms'; 29 +import { AbstractControl, FormGroup, ValidationErrors } from '@angular/forms';
30 30
31 export enum DeviceProfileType { 31 export enum DeviceProfileType {
32 DEFAULT = 'DEFAULT' 32 DEFAULT = 'DEFAULT'
@@ -87,7 +87,7 @@ export const deviceProvisionTypeTranslationMap = new Map<DeviceProvisionType, st @@ -87,7 +87,7 @@ export const deviceProvisionTypeTranslationMap = new Map<DeviceProvisionType, st
87 [DeviceProvisionType.ALLOW_CREATE_NEW_DEVICES, 'device-profile.provision-strategy-created-new'], 87 [DeviceProvisionType.ALLOW_CREATE_NEW_DEVICES, 'device-profile.provision-strategy-created-new'],
88 [DeviceProvisionType.CHECK_PRE_PROVISIONED_DEVICES, 'device-profile.provision-strategy-check-pre-provisioned'] 88 [DeviceProvisionType.CHECK_PRE_PROVISIONED_DEVICES, 'device-profile.provision-strategy-check-pre-provisioned']
89 ] 89 ]
90 -) 90 +);
91 91
92 export const deviceTransportTypeHintMap = new Map<DeviceTransportType, string>( 92 export const deviceTransportTypeHintMap = new Map<DeviceTransportType, string>(
93 [ 93 [
@@ -303,6 +303,18 @@ export interface AlarmRule { @@ -303,6 +303,18 @@ export interface AlarmRule {
303 schedule?: AlarmSchedule; 303 schedule?: AlarmSchedule;
304 } 304 }
305 305
  306 +export function alarmRuleValidator(control: AbstractControl): ValidationErrors | null {
  307 + const alarmRule: AlarmRule = control.value;
  308 + return alarmRuleValid(alarmRule) ? null : {alarmRule: true};
  309 +}
  310 +
  311 +function alarmRuleValid(alarmRule: AlarmRule): boolean {
  312 + if (!alarmRule || !alarmRule.condition || !alarmRule.condition.condition || !alarmRule.condition.condition.length) {
  313 + return false;
  314 + }
  315 + return true;
  316 +}
  317 +
306 export interface DeviceProfileAlarm { 318 export interface DeviceProfileAlarm {
307 id: string; 319 id: string;
308 alarmType: string; 320 alarmType: string;
@@ -312,6 +324,34 @@ export interface DeviceProfileAlarm { @@ -312,6 +324,34 @@ export interface DeviceProfileAlarm {
312 propagateRelationTypes?: Array<string>; 324 propagateRelationTypes?: Array<string>;
313 } 325 }
314 326
  327 +export function deviceProfileAlarmValidator(control: AbstractControl): ValidationErrors | null {
  328 + const deviceProfileAlarm: DeviceProfileAlarm = control.value;
  329 + if (deviceProfileAlarm && deviceProfileAlarm.id && deviceProfileAlarm.alarmType &&
  330 + deviceProfileAlarm.createRules) {
  331 + const severities = Object.keys(deviceProfileAlarm.createRules);
  332 + if (severities.length) {
  333 + let alarmRulesValid = true;
  334 + for (const severity of severities) {
  335 + const alarmRule = deviceProfileAlarm.createRules[severity];
  336 + if (!alarmRuleValid(alarmRule)) {
  337 + alarmRulesValid = false;
  338 + break;
  339 + }
  340 + }
  341 + if (alarmRulesValid) {
  342 + if (deviceProfileAlarm.clearRule && !alarmRuleValid(deviceProfileAlarm.clearRule)) {
  343 + alarmRulesValid = false;
  344 + }
  345 + }
  346 + if (alarmRulesValid) {
  347 + return null;
  348 + }
  349 + }
  350 + }
  351 + return {deviceProfileAlarm: true};
  352 +}
  353 +
  354 +
315 export interface DeviceProfileData { 355 export interface DeviceProfileData {
316 configuration: DeviceProfileConfiguration; 356 configuration: DeviceProfileConfiguration;
317 transportConfiguration: DeviceProfileTransportConfiguration; 357 transportConfiguration: DeviceProfileTransportConfiguration;
@@ -908,6 +908,7 @@ @@ -908,6 +908,7 @@
908 "create-alarm-pattern": "Create <b>{{alarmType}}</b> alarm", 908 "create-alarm-pattern": "Create <b>{{alarmType}}</b> alarm",
909 "create-alarm-rules": "Create alarm rules", 909 "create-alarm-rules": "Create alarm rules",
910 "no-create-alarm-rules": "No create conditions configured", 910 "no-create-alarm-rules": "No create conditions configured",
  911 + "add-create-alarm-rule-prompt": "Please add create alarm rule",
911 "clear-alarm-rule": "Clear alarm rule", 912 "clear-alarm-rule": "Clear alarm rule",
912 "no-clear-alarm-rule": "No clear condition configured", 913 "no-clear-alarm-rule": "No clear condition configured",
913 "add-create-alarm-rule": "Add create condition", 914 "add-create-alarm-rule": "Add create condition",
@@ -331,6 +331,9 @@ pre.tb-highlight { @@ -331,6 +331,9 @@ pre.tb-highlight {
331 font-weight: 400; 331 font-weight: 400;
332 line-height: 18px; 332 line-height: 18px;
333 color: rgba(0, 0, 0, .38); 333 color: rgba(0, 0, 0, .38);
  334 + &.required {
  335 + color: rgb(221, 44, 0);
  336 + }
334 } 337 }
335 338
336 .tb-fullscreen { 339 .tb-fullscreen {