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