Commit 52e6e76ac6c46cbead43f17b6c53b7ebc967dba6
1 parent
b0126a9d
Oauth2 - set provider name to created user additionalInfo. UI: Improve device profile alarm rules.
Showing
28 changed files
with
165 additions
and
175 deletions
... | ... | @@ -31,6 +31,8 @@ import org.thingsboard.server.common.data.id.CustomerId; |
31 | 31 | import org.thingsboard.server.common.data.id.DashboardId; |
32 | 32 | import org.thingsboard.server.common.data.id.IdBased; |
33 | 33 | import org.thingsboard.server.common.data.id.TenantId; |
34 | +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo; | |
35 | +import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; | |
34 | 36 | import org.thingsboard.server.common.data.page.PageData; |
35 | 37 | import org.thingsboard.server.common.data.page.PageLink; |
36 | 38 | import org.thingsboard.server.common.data.security.Authority; |
... | ... | @@ -76,12 +78,15 @@ public abstract class AbstractOAuth2ClientMapper { |
76 | 78 | |
77 | 79 | private final Lock userCreationLock = new ReentrantLock(); |
78 | 80 | |
79 | - protected SecurityUser getOrCreateSecurityUserFromOAuth2User(OAuth2User oauth2User, boolean allowUserCreation, boolean activateUser) { | |
81 | + protected SecurityUser getOrCreateSecurityUserFromOAuth2User(OAuth2User oauth2User, OAuth2ClientRegistrationInfo clientRegistration) { | |
82 | + | |
83 | + OAuth2MapperConfig config = clientRegistration.getMapperConfig(); | |
84 | + | |
80 | 85 | UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.USER_NAME, oauth2User.getEmail()); |
81 | 86 | |
82 | 87 | User user = userService.findUserByEmail(TenantId.SYS_TENANT_ID, oauth2User.getEmail()); |
83 | 88 | |
84 | - if (user == null && !allowUserCreation) { | |
89 | + if (user == null && !config.isAllowUserCreation()) { | |
85 | 90 | throw new UsernameNotFoundException("User not found: " + oauth2User.getEmail()); |
86 | 91 | } |
87 | 92 | |
... | ... | @@ -106,21 +111,28 @@ public abstract class AbstractOAuth2ClientMapper { |
106 | 111 | user.setFirstName(oauth2User.getFirstName()); |
107 | 112 | user.setLastName(oauth2User.getLastName()); |
108 | 113 | |
114 | + ObjectNode additionalInfo = objectMapper.createObjectNode(); | |
115 | + | |
109 | 116 | if (!StringUtils.isEmpty(oauth2User.getDefaultDashboardName())) { |
110 | 117 | Optional<DashboardId> dashboardIdOpt = |
111 | 118 | user.getAuthority() == Authority.TENANT_ADMIN ? |
112 | 119 | getDashboardId(tenantId, oauth2User.getDefaultDashboardName()) |
113 | 120 | : getDashboardId(tenantId, customerId, oauth2User.getDefaultDashboardName()); |
114 | 121 | if (dashboardIdOpt.isPresent()) { |
115 | - ObjectNode additionalInfo = objectMapper.createObjectNode(); | |
116 | 122 | additionalInfo.put("defaultDashboardFullscreen", oauth2User.isAlwaysFullScreen()); |
117 | 123 | additionalInfo.put("defaultDashboardId", dashboardIdOpt.get().getId().toString()); |
118 | - user.setAdditionalInfo(additionalInfo); | |
119 | 124 | } |
120 | 125 | } |
121 | 126 | |
127 | + if (clientRegistration.getAdditionalInfo() != null && | |
128 | + clientRegistration.getAdditionalInfo().has("providerName")) { | |
129 | + additionalInfo.put("authProviderName", clientRegistration.getAdditionalInfo().get("providerName").asText()); | |
130 | + } | |
131 | + | |
132 | + user.setAdditionalInfo(additionalInfo); | |
133 | + | |
122 | 134 | user = userService.saveUser(user); |
123 | - if (activateUser) { | |
135 | + if (config.isActivateUser()) { | |
124 | 136 | UserCredentials userCredentials = userService.findUserCredentialsByUserId(user.getTenantId(), user.getId()); |
125 | 137 | userService.activateUserCredentials(user.getTenantId(), userCredentials.getActivateToken(), passwordEncoder.encode("")); |
126 | 138 | } | ... | ... |
... | ... | @@ -18,6 +18,7 @@ package org.thingsboard.server.service.security.auth.oauth2; |
18 | 18 | import lombok.extern.slf4j.Slf4j; |
19 | 19 | import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; |
20 | 20 | import org.springframework.stereotype.Service; |
21 | +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo; | |
21 | 22 | import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; |
22 | 23 | import org.thingsboard.server.dao.oauth2.OAuth2User; |
23 | 24 | import org.thingsboard.server.service.security.model.SecurityUser; |
... | ... | @@ -29,11 +30,12 @@ import java.util.Map; |
29 | 30 | public class BasicOAuth2ClientMapper extends AbstractOAuth2ClientMapper implements OAuth2ClientMapper { |
30 | 31 | |
31 | 32 | @Override |
32 | - public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2MapperConfig config) { | |
33 | + public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2ClientRegistrationInfo clientRegistration) { | |
34 | + OAuth2MapperConfig config = clientRegistration.getMapperConfig(); | |
33 | 35 | Map<String, Object> attributes = token.getPrincipal().getAttributes(); |
34 | 36 | String email = BasicMapperUtils.getStringAttributeByKey(attributes, config.getBasic().getEmailAttributeKey()); |
35 | 37 | OAuth2User oauth2User = BasicMapperUtils.getOAuth2User(email, attributes, config); |
36 | 38 | |
37 | - return getOrCreateSecurityUserFromOAuth2User(oauth2User, config.isAllowUserCreation(), config.isActivateUser()); | |
39 | + return getOrCreateSecurityUserFromOAuth2User(oauth2User, clientRegistration); | |
38 | 40 | } |
39 | 41 | } | ... | ... |
... | ... | @@ -23,6 +23,7 @@ import org.springframework.security.oauth2.client.authentication.OAuth2Authentic |
23 | 23 | import org.springframework.stereotype.Service; |
24 | 24 | import org.springframework.util.StringUtils; |
25 | 25 | import org.springframework.web.client.RestTemplate; |
26 | +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo; | |
26 | 27 | import org.thingsboard.server.common.data.oauth2.OAuth2CustomMapperConfig; |
27 | 28 | import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; |
28 | 29 | import org.thingsboard.server.dao.oauth2.OAuth2User; |
... | ... | @@ -38,9 +39,10 @@ public class CustomOAuth2ClientMapper extends AbstractOAuth2ClientMapper impleme |
38 | 39 | private RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder(); |
39 | 40 | |
40 | 41 | @Override |
41 | - public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2MapperConfig config) { | |
42 | + public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2ClientRegistrationInfo clientRegistration) { | |
43 | + OAuth2MapperConfig config = clientRegistration.getMapperConfig(); | |
42 | 44 | OAuth2User oauth2User = getOAuth2User(token, providerAccessToken, config.getCustom()); |
43 | - return getOrCreateSecurityUserFromOAuth2User(oauth2User, config.isAllowUserCreation(), config.isActivateUser()); | |
45 | + return getOrCreateSecurityUserFromOAuth2User(oauth2User, clientRegistration); | |
44 | 46 | } |
45 | 47 | |
46 | 48 | private synchronized OAuth2User getOAuth2User(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2CustomMapperConfig custom) { | ... | ... |
... | ... | @@ -23,6 +23,7 @@ import org.springframework.boot.web.client.RestTemplateBuilder; |
23 | 23 | import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; |
24 | 24 | import org.springframework.stereotype.Service; |
25 | 25 | import org.springframework.web.client.RestTemplate; |
26 | +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo; | |
26 | 27 | import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; |
27 | 28 | import org.thingsboard.server.dao.oauth2.OAuth2Configuration; |
28 | 29 | import org.thingsboard.server.dao.oauth2.OAuth2User; |
... | ... | @@ -45,12 +46,13 @@ public class GithubOAuth2ClientMapper extends AbstractOAuth2ClientMapper impleme |
45 | 46 | private OAuth2Configuration oAuth2Configuration; |
46 | 47 | |
47 | 48 | @Override |
48 | - public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2MapperConfig config) { | |
49 | + public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2ClientRegistrationInfo clientRegistration) { | |
50 | + OAuth2MapperConfig config = clientRegistration.getMapperConfig(); | |
49 | 51 | Map<String, String> githubMapperConfig = oAuth2Configuration.getGithubMapper(); |
50 | 52 | String email = getEmail(githubMapperConfig.get(EMAIL_URL_KEY), providerAccessToken); |
51 | 53 | Map<String, Object> attributes = token.getPrincipal().getAttributes(); |
52 | 54 | OAuth2User oAuth2User = BasicMapperUtils.getOAuth2User(email, attributes, config); |
53 | - return getOrCreateSecurityUserFromOAuth2User(oAuth2User, config.isAllowUserCreation(), config.isActivateUser()); | |
55 | + return getOrCreateSecurityUserFromOAuth2User(oAuth2User, clientRegistration); | |
54 | 56 | } |
55 | 57 | |
56 | 58 | private synchronized String getEmail(String emailUrl, String oauth2Token) { | ... | ... |
... | ... | @@ -16,9 +16,9 @@ |
16 | 16 | package org.thingsboard.server.service.security.auth.oauth2; |
17 | 17 | |
18 | 18 | import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; |
19 | -import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; | |
19 | +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo; | |
20 | 20 | import org.thingsboard.server.service.security.model.SecurityUser; |
21 | 21 | |
22 | 22 | public interface OAuth2ClientMapper { |
23 | - SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2MapperConfig config); | |
23 | + SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2ClientRegistrationInfo clientRegistration); | |
24 | 24 | } | ... | ... |
... | ... | @@ -74,7 +74,7 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS |
74 | 74 | token.getPrincipal().getName()); |
75 | 75 | OAuth2ClientMapper mapper = oauth2ClientMapperProvider.getOAuth2ClientMapperByType(clientRegistration.getMapperConfig().getType()); |
76 | 76 | SecurityUser securityUser = mapper.getOrCreateUserByClientPrincipal(token, oAuth2AuthorizedClient.getAccessToken().getTokenValue(), |
77 | - clientRegistration.getMapperConfig()); | |
77 | + clientRegistration); | |
78 | 78 | |
79 | 79 | JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser); |
80 | 80 | JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser); |
... | ... | @@ -85,4 +85,4 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS |
85 | 85 | URLEncoder.encode(e.getMessage(), StandardCharsets.UTF_8.toString())); |
86 | 86 | } |
87 | 87 | } |
88 | -} | |
\ No newline at end of file | ||
88 | +} | ... | ... |
... | ... | @@ -33,11 +33,6 @@ |
33 | 33 | <tb-anchor #entityDetailsForm></tb-anchor> |
34 | 34 | </div> |
35 | 35 | <div mat-dialog-actions fxLayoutAlign="end center"> |
36 | - <button mat-raised-button color="primary" | |
37 | - type="submit" | |
38 | - [disabled]="(isLoading$ | async) || detailsForm?.invalid || !detailsForm?.dirty"> | |
39 | - {{ 'action.add' | translate }} | |
40 | - </button> | |
41 | 36 | <button mat-button color="primary" |
42 | 37 | type="button" |
43 | 38 | cdkFocusInitial |
... | ... | @@ -45,5 +40,10 @@ |
45 | 40 | (click)="cancel()"> |
46 | 41 | {{ 'action.cancel' | translate }} |
47 | 42 | </button> |
43 | + <button mat-raised-button color="primary" | |
44 | + type="submit" | |
45 | + [disabled]="(isLoading$ | async) || detailsForm?.invalid || !detailsForm?.dirty"> | |
46 | + {{ 'action.add' | translate }} | |
47 | + </button> | |
48 | 48 | </div> |
49 | 49 | </form> | ... | ... |
... | ... | @@ -15,5 +15,5 @@ |
15 | 15 | limitations under the License. |
16 | 16 | |
17 | 17 | --> |
18 | -<div class="tb-filter-text" [ngClass]="{disabled: disabled, required: requiredClass}" | |
18 | +<div class="tb-filter-text" [ngClass]="{disabled: disabled, required: requiredClass, nowrap: nowrap}" | |
19 | 19 | [innerHTML]="filterText"></div> | ... | ... |
... | ... | @@ -54,6 +54,9 @@ export class FilterTextComponent implements ControlValueAccessor, OnInit { |
54 | 54 | @Input() |
55 | 55 | addFilterPrompt = this.translate.instant('filter.add-filter-prompt'); |
56 | 56 | |
57 | + @Input() | |
58 | + nowrap = false; | |
59 | + | |
57 | 60 | requiredClass = false; |
58 | 61 | |
59 | 62 | private filterText: string; | ... | ... |
... | ... | @@ -15,7 +15,7 @@ |
15 | 15 | limitations under the License. |
16 | 16 | |
17 | 17 | --> |
18 | -<div style="min-width: 1000px;"> | |
18 | +<div> | |
19 | 19 | <mat-toolbar color="primary"> |
20 | 20 | <h2 translate>device-profile.add</h2> |
21 | 21 | <span fxFlex></span> |
... | ... | @@ -106,28 +106,25 @@ |
106 | 106 | </mat-step> |
107 | 107 | </mat-horizontal-stepper> |
108 | 108 | </div> |
109 | - <div mat-dialog-actions fxLayout="column" fxLayoutAlign="start wrap" fxLayoutGap="8px" style="height: 100px;"> | |
110 | - <div fxFlex fxLayout="row" fxLayoutAlign="end"> | |
111 | - <button mat-raised-button | |
112 | - *ngIf="showNext" | |
113 | - [disabled]="(isLoading$ | async)" | |
114 | - (click)="nextStep()">{{ 'action.next-with-label' | translate:{label: (getFormLabel(this.selectedIndex+1) | translate)} }}</button> | |
115 | - </div> | |
116 | - <div fxFlex fxLayout="row"> | |
117 | - <button mat-button | |
118 | - color="primary" | |
119 | - [disabled]="(isLoading$ | async)" | |
120 | - (click)="cancel()">{{ 'action.cancel' | translate }}</button> | |
121 | - <span fxFlex></span> | |
122 | - <div fxLayout="row wrap" fxLayoutGap="8px"> | |
123 | - <button mat-raised-button *ngIf="selectedIndex > 0" | |
124 | - [disabled]="(isLoading$ | async)" | |
125 | - (click)="previousStep()">{{ 'action.back' | translate }}</button> | |
126 | - <button mat-raised-button | |
127 | - [disabled]="(isLoading$ | async)" | |
128 | - color="primary" | |
129 | - (click)="add()">{{ 'action.add' | translate }}</button> | |
130 | - </div> | |
131 | - </div> | |
109 | + <div mat-dialog-actions fxLayout="row"> | |
110 | + <button mat-stroked-button *ngIf="selectedIndex > 0" | |
111 | + [disabled]="(isLoading$ | async)" | |
112 | + (click)="previousStep()">{{ 'action.back' | translate }}</button> | |
113 | + <span fxFlex></span> | |
114 | + <button mat-stroked-button | |
115 | + color="primary" | |
116 | + *ngIf="showNext" | |
117 | + [disabled]="(isLoading$ | async)" | |
118 | + (click)="nextStep()">{{ 'action.next-with-label' | translate:{label: (getFormLabel(this.selectedIndex+1) | translate)} }}</button> | |
119 | + </div> | |
120 | + <mat-divider></mat-divider> | |
121 | + <div mat-dialog-actions fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="end"> | |
122 | + <button mat-button | |
123 | + [disabled]="(isLoading$ | async)" | |
124 | + (click)="cancel()">{{ 'action.cancel' | translate }}</button> | |
125 | + <button mat-raised-button | |
126 | + [disabled]="(isLoading$ | async)" | |
127 | + color="primary" | |
128 | + (click)="add()">{{ 'action.add' | translate }}</button> | |
132 | 129 | </div> |
133 | 130 | </div> | ... | ... |
... | ... | @@ -28,7 +28,7 @@ |
28 | 28 | display: flex; |
29 | 29 | flex-direction: column; |
30 | 30 | height: 100%; |
31 | - padding: 24px 24px 8px !important; | |
31 | + padding: 0 !important; | |
32 | 32 | |
33 | 33 | .mat-stepper-horizontal { |
34 | 34 | display: flex; |
... | ... | @@ -45,7 +45,7 @@ |
45 | 45 | } |
46 | 46 | } |
47 | 47 | .mat-horizontal-content-container { |
48 | - height: 350px; | |
48 | + height: 530px; | |
49 | 49 | max-height: 100%; |
50 | 50 | width: 100%;; |
51 | 51 | overflow-y: auto; | ... | ... |
... | ... | @@ -15,25 +15,22 @@ |
15 | 15 | limitations under the License. |
16 | 16 | |
17 | 17 | --> |
18 | -<div fxLayout="column" fxFlex [formGroup]="alarmRuleConditionFormGroup"> | |
19 | - <div fxLayout="row" fxLayoutAlign="start center" style="min-height: 40px;"> | |
20 | - <label class="tb-title" translate>device-profile.condition</label> | |
21 | - <span fxFlex></span> | |
22 | - <a mat-button color="primary" | |
23 | - type="button" | |
24 | - (click)="openFilterDialog($event)" | |
25 | - matTooltip="{{ (disabled ? 'action.view' : 'action.edit') | translate }}" | |
26 | - matTooltipPosition="above"> | |
27 | - {{ (disabled ? 'action.view' : (conditionSet() ? 'action.edit' : 'action.add')) | translate }} | |
28 | - </a> | |
29 | - </div> | |
18 | +<div fxLayout="row" fxLayoutAlign="start center" [formGroup]="alarmRuleConditionFormGroup" style="min-width: 0;"> | |
30 | 19 | <div class="tb-alarm-rule-condition" fxFlex fxLayout="column" fxLayoutAlign="center" (click)="openFilterDialog($event)"> |
31 | 20 | <tb-filter-text formControlName="condition" |
21 | + [nowrap]="true" | |
32 | 22 | required |
33 | 23 | addFilterPrompt="{{'device-profile.enter-alarm-rule-condition-prompt' | translate}}"> |
34 | 24 | </tb-filter-text> |
35 | - <span class="tb-alarm-rule-condition-spec" [ngClass]="{disabled: this.disabled}" [innerHTML]="specText"> | |
25 | + <span *ngIf="specText" class="tb-alarm-rule-condition-spec" [ngClass]="{disabled: this.disabled}" [innerHTML]="specText"> | |
36 | 26 | </span> |
37 | 27 | </div> |
38 | - | |
28 | + <button mat-icon-button | |
29 | + [color]="conditionSet() ? 'primary' : 'warn'" | |
30 | + type="button" | |
31 | + (click)="openFilterDialog($event)" | |
32 | + matTooltip="{{ (disabled ? 'action.view' : 'action.edit') | translate }}" | |
33 | + matTooltipPosition="above"> | |
34 | + <mat-icon>{{ disabled ? 'visibility' : (conditionSet() ? 'edit' : 'add') }}</mat-icon> | |
35 | + </button> | |
39 | 36 | </div> | ... | ... |
... | ... | @@ -22,13 +22,10 @@ |
22 | 22 | } |
23 | 23 | .tb-alarm-rule-condition { |
24 | 24 | cursor: pointer; |
25 | + min-width: 0; | |
25 | 26 | .tb-alarm-rule-condition-spec { |
26 | - margin-top: 1em; | |
27 | - line-height: 1.8em; | |
27 | + opacity: 0.7; | |
28 | 28 | padding: 4px; |
29 | - &.disabled { | |
30 | - opacity: 0.7; | |
31 | - } | |
32 | 29 | } |
33 | 30 | } |
34 | 31 | } | ... | ... |
... | ... | @@ -16,36 +16,23 @@ |
16 | 16 | |
17 | 17 | --> |
18 | 18 | <div fxLayout="column" [formGroup]="alarmRuleFormGroup"> |
19 | - <tb-alarm-rule-condition fxFlex class="row" | |
20 | - formControlName="condition"> | |
19 | + <tb-alarm-rule-condition formControlName="condition"> | |
21 | 20 | </tb-alarm-rule-condition> |
22 | - <mat-divider class="row"></mat-divider> | |
23 | - <tb-alarm-schedule-info fxFlex class="row" | |
24 | - formControlName="schedule"> | |
21 | + <tb-alarm-schedule-info formControlName="schedule"> | |
25 | 22 | </tb-alarm-schedule-info> |
26 | - <mat-divider class="row"></mat-divider> | |
27 | - <div fxLayout="column" fxFlex class="tb-alarm-rule-details row"> | |
28 | - <div fxLayout="row" fxLayoutAlign="start center" style="min-height: 40px;"> | |
29 | - <label class="tb-title" translate>device-profile.alarm-rule-details</label> | |
30 | - <span fxFlex></span> | |
31 | - <a mat-button color="primary" | |
32 | - *ngIf="!disabled" | |
33 | - type="button" | |
34 | - (click)="openEditDetailsDialog($event)" | |
35 | - matTooltip="{{ 'action.edit' | translate }}" | |
36 | - matTooltipPosition="above"> | |
37 | - {{ 'action.edit' | translate }} | |
38 | - </a> | |
39 | - </div> | |
40 | - <div fxLayout="row" fxLayoutAlign="start start"> | |
41 | - <div class="tb-alarm-rule-details-content" [ngClass]="{disabled: this.disabled, collapsed: !this.expandAlarmDetails}" | |
42 | - (click)="!disabled ? openEditDetailsDialog($event) : {}" | |
43 | - fxFlex [innerHTML]="alarmRuleFormGroup.get('alarmDetails').value"></div> | |
44 | - <a mat-button color="primary" | |
45 | - type="button" | |
46 | - (click)="expandAlarmDetails = !expandAlarmDetails"> | |
47 | - {{ (expandAlarmDetails ? 'action.hide' : 'action.read-more') | translate }} | |
48 | - </a> | |
49 | - </div> | |
23 | + <div *ngIf="!disabled || alarmRuleFormGroup.get('alarmDetails').value" fxLayout="row" fxLayoutAlign="start center"> | |
24 | + <span class="tb-alarm-rule-details title" (click)="openEditDetailsDialog($event)"> | |
25 | + {{ alarmRuleFormGroup.get('alarmDetails').value ? ('device-profile.alarm-rule-details' | translate) + ': ' : ('device-profile.add-alarm-rule-details' | translate) }} | |
26 | + </span> | |
27 | + <span *ngIf="alarmRuleFormGroup.get('alarmDetails').value" class="tb-alarm-rule-details" | |
28 | + (click)="openEditDetailsDialog($event)" | |
29 | + [innerHTML]="alarmRuleFormGroup.get('alarmDetails').value"></span> | |
30 | + <button mat-icon-button color="primary" | |
31 | + type="button" | |
32 | + (click)="openEditDetailsDialog($event)" | |
33 | + matTooltip="{{ (disabled ? 'action.view' : 'action.edit') | translate }}" | |
34 | + matTooltipPosition="above"> | |
35 | + <mat-icon>{{ disabled ? 'visibility' : (alarmRuleFormGroup.get('alarmDetails').value ? 'edit' : 'add') }}</mat-icon> | |
36 | + </button> | |
50 | 37 | </div> |
51 | 38 | </div> | ... | ... |
... | ... | @@ -14,31 +14,19 @@ |
14 | 14 | * limitations under the License. |
15 | 15 | */ |
16 | 16 | :host { |
17 | + min-width: 0; | |
17 | 18 | .row { |
18 | 19 | margin-top: 1em; |
19 | 20 | } |
20 | 21 | .tb-alarm-rule-details { |
21 | - a.mat-button { | |
22 | - &:hover, &:focus { | |
23 | - border-bottom: none; | |
24 | - } | |
25 | - } | |
26 | - .tb-alarm-rule-details-content { | |
27 | - min-height: 33px; | |
28 | - overflow: hidden; | |
29 | - white-space: pre; | |
30 | - line-height: 1.8em; | |
31 | - padding: 4px; | |
32 | - cursor: pointer; | |
33 | - &.collapsed { | |
34 | - max-height: 33px; | |
35 | - white-space: nowrap; | |
36 | - text-overflow: ellipsis; | |
37 | - } | |
38 | - &.disabled { | |
39 | - opacity: 0.7; | |
40 | - cursor: auto; | |
41 | - } | |
22 | + padding: 4px; | |
23 | + cursor: pointer; | |
24 | + overflow: hidden; | |
25 | + white-space: nowrap; | |
26 | + text-overflow: ellipsis; | |
27 | + &.title { | |
28 | + opacity: 0.7; | |
29 | + overflow: visible; | |
42 | 30 | } |
43 | 31 | } |
44 | 32 | } | ... | ... |
... | ... | @@ -118,7 +118,8 @@ export class AlarmRuleComponent implements ControlValueAccessor, OnInit, Validat |
118 | 118 | disableClose: true, |
119 | 119 | panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], |
120 | 120 | data: { |
121 | - alarmDetails: this.alarmRuleFormGroup.get('alarmDetails').value | |
121 | + alarmDetails: this.alarmRuleFormGroup.get('alarmDetails').value, | |
122 | + readonly: this.disabled | |
122 | 123 | } |
123 | 124 | }).afterClosed().subscribe((alarmDetails) => { |
124 | 125 | if (isDefinedAndNotNull(alarmDetails)) { | ... | ... |
... | ... | @@ -15,19 +15,16 @@ |
15 | 15 | limitations under the License. |
16 | 16 | |
17 | 17 | --> |
18 | -<div fxLayout="column" fxFlex> | |
19 | - <div fxLayout="row" fxLayoutAlign="start center" style="min-height: 40px;"> | |
20 | - <label class="tb-title" translate>device-profile.schedule</label> | |
21 | - <span fxFlex></span> | |
22 | - <a mat-button color="primary" | |
23 | - type="button" | |
24 | - (click)="openScheduleDialog($event)" | |
25 | - matTooltip="{{ (disabled ? 'action.view' : 'action.edit') | translate }}" | |
26 | - matTooltipPosition="above"> | |
27 | - {{ (disabled ? 'action.view' : 'action.edit' ) | translate }} | |
28 | - </a> | |
29 | - </div> | |
30 | - <sapn class="tb-alarm-rule-schedule" [ngClass]="{disabled: this.disabled}" (click)="openScheduleDialog($event)" | |
18 | +<div fxLayout="row" fxLayoutAlign="start center" style="min-width: 0;"> | |
19 | + <span class="tb-alarm-rule-schedule title" (click)="openScheduleDialog($event)">{{('device-profile.schedule' | translate) + ': '}}</span> | |
20 | + <span class="tb-alarm-rule-schedule" (click)="openScheduleDialog($event)" | |
31 | 21 | [innerHTML]="scheduleText"> |
32 | - </sapn> | |
22 | + </span> | |
23 | + <button mat-icon-button color="primary" | |
24 | + type="button" | |
25 | + (click)="openScheduleDialog($event)" | |
26 | + matTooltip="{{ (disabled ? 'action.view' : 'action.edit') | translate }}" | |
27 | + matTooltipPosition="above"> | |
28 | + <mat-icon>{{ disabled ? 'visibility' : 'edit' }}</mat-icon> | |
29 | + </button> | |
33 | 30 | </div> | ... | ... |
... | ... | @@ -21,14 +21,14 @@ |
21 | 21 | } |
22 | 22 | } |
23 | 23 | .tb-alarm-rule-schedule { |
24 | - line-height: 1.8em; | |
25 | 24 | padding: 4px; |
26 | 25 | cursor: pointer; |
27 | - &.disabled { | |
26 | + overflow: hidden; | |
27 | + white-space: nowrap; | |
28 | + text-overflow: ellipsis; | |
29 | + &.title { | |
28 | 30 | opacity: 0.7; |
29 | - } | |
30 | - .nowrap { | |
31 | - white-space: nowrap; | |
31 | + overflow: visible; | |
32 | 32 | } |
33 | 33 | } |
34 | 34 | } | ... | ... |
... | ... | @@ -101,7 +101,7 @@ export class AlarmScheduleInfoComponent implements ControlValueAccessor, OnInit |
101 | 101 | for (const item of schedule.items) { |
102 | 102 | if (item.enabled) { |
103 | 103 | if (this.scheduleText.length) { |
104 | - this.scheduleText += '<br/>'; | |
104 | + this.scheduleText += ', '; | |
105 | 105 | } |
106 | 106 | this.scheduleText += this.translate.instant(dayOfWeekTranslations[item.dayOfWeek - 1]); |
107 | 107 | this.scheduleText += ' <b>' + getAlarmScheduleRangeText(utcTimestampToTimeOfDay(item.startsOn), | ... | ... |
... | ... | @@ -19,7 +19,7 @@ |
19 | 19 | <div *ngFor="let createAlarmRuleControl of createAlarmRulesFormArray().controls; let $index = index; |
20 | 20 | last as isLast;" fxLayout="row" fxLayoutAlign="start center" |
21 | 21 | fxLayoutGap="8px" style="padding-bottom: 8px;" [formGroup]="createAlarmRuleControl"> |
22 | - <div class="create-alarm-rule" fxFlex fxLayout="column" fxLayoutGap="8px" fxLayoutAlign="start"> | |
22 | + <div class="create-alarm-rule" fxFlex fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="start"> | |
23 | 23 | <mat-form-field class="severity mat-block" floatLabel="always" hideRequiredMarker> |
24 | 24 | <mat-label translate>alarm.severity</mat-label> |
25 | 25 | <mat-select formControlName="severity" |
... | ... | @@ -34,7 +34,7 @@ |
34 | 34 | {{ 'device-profile.alarm-severity-required' | translate }} |
35 | 35 | </mat-error> |
36 | 36 | </mat-form-field> |
37 | - <mat-divider></mat-divider> | |
37 | + <mat-divider vertical></mat-divider> | |
38 | 38 | <tb-alarm-rule formControlName="alarmRule" required fxFlex> |
39 | 39 | </tb-alarm-rule> |
40 | 40 | </div> | ... | ... |
... | ... | @@ -98,7 +98,7 @@ |
98 | 98 | <mat-icon>remove_circle_outline</mat-icon> |
99 | 99 | </button> |
100 | 100 | </div> |
101 | - <div *ngIf="!alarmFormGroup.get('clearRule').value"> | |
101 | + <div *ngIf="disabled && !alarmFormGroup.get('clearRule').value"> | |
102 | 102 | <span translate fxLayoutAlign="center center" style="margin: 16px 0" |
103 | 103 | class="tb-prompt">device-profile.no-clear-alarm-rule</span> |
104 | 104 | </div> | ... | ... |
... | ... | @@ -38,16 +38,16 @@ |
38 | 38 | </fieldset> |
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) || editDetailsFormGroup.invalid || !editDetailsFormGroup.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 *ngIf="!data.readonly" mat-raised-button color="primary" | |
48 | + type="submit" | |
49 | + [disabled]="(isLoading$ | async) || editDetailsFormGroup.invalid || !editDetailsFormGroup.dirty"> | |
50 | + {{ 'action.save' | translate }} | |
51 | + </button> | |
52 | 52 | </div> |
53 | 53 | </form> | ... | ... |
... | ... | @@ -27,6 +27,7 @@ import { TranslateService } from '@ngx-translate/core'; |
27 | 27 | |
28 | 28 | export interface EditAlarmDetailsDialogData { |
29 | 29 | alarmDetails: string; |
30 | + readonly: boolean; | |
30 | 31 | } |
31 | 32 | |
32 | 33 | @Component({ |
... | ... | @@ -57,6 +58,9 @@ export class EditAlarmDetailsDialogComponent extends DialogComponent<EditAlarmDe |
57 | 58 | this.editDetailsFormGroup = this.fb.group({ |
58 | 59 | alarmDetails: [this.alarmDetails] |
59 | 60 | }); |
61 | + if (this.data.readonly) { | |
62 | + this.editDetailsFormGroup.disable(); | |
63 | + } | |
60 | 64 | } |
61 | 65 | |
62 | 66 | ngOnInit(): void { | ... | ... |
... | ... | @@ -151,28 +151,25 @@ |
151 | 151 | </mat-step> |
152 | 152 | </mat-horizontal-stepper> |
153 | 153 | </div> |
154 | - <div mat-dialog-actions fxLayout="column" fxLayoutAlign="start wrap" fxLayoutGap="8px" style="height: 100px;"> | |
155 | - <div fxFlex fxLayout="row" fxLayoutAlign="end"> | |
156 | - <button mat-raised-button | |
157 | - *ngIf="showNext" | |
158 | - [disabled]="(isLoading$ | async)" | |
159 | - (click)="nextStep()">{{ 'action.next-with-label' | translate:{label: (getFormLabel(this.selectedIndex+1) | translate)} }}</button> | |
160 | - </div> | |
161 | - <div fxFlex fxLayout="row"> | |
162 | - <button mat-button | |
163 | - color="primary" | |
164 | - [disabled]="(isLoading$ | async)" | |
165 | - (click)="cancel()">{{ 'action.cancel' | translate }}</button> | |
166 | - <span fxFlex></span> | |
167 | - <div fxLayout="row wrap" fxLayoutGap="8px"> | |
168 | - <button mat-raised-button *ngIf="selectedIndex > 0" | |
169 | - [disabled]="(isLoading$ | async)" | |
170 | - (click)="previousStep()">{{ 'action.back' | translate }}</button> | |
171 | - <button mat-raised-button | |
172 | - [disabled]="(isLoading$ | async)" | |
173 | - color="primary" | |
174 | - (click)="add()">{{ 'action.add' | translate }}</button> | |
175 | - </div> | |
176 | - </div> | |
154 | + <div mat-dialog-actions fxLayout="row"> | |
155 | + <button mat-stroked-button *ngIf="selectedIndex > 0" | |
156 | + [disabled]="(isLoading$ | async)" | |
157 | + (click)="previousStep()">{{ 'action.back' | translate }}</button> | |
158 | + <span fxFlex></span> | |
159 | + <button mat-stroked-button | |
160 | + color="primary" | |
161 | + *ngIf="showNext" | |
162 | + [disabled]="(isLoading$ | async)" | |
163 | + (click)="nextStep()">{{ 'action.next-with-label' | translate:{label: (getFormLabel(this.selectedIndex+1) | translate)} }}</button> | |
164 | + </div> | |
165 | + <mat-divider></mat-divider> | |
166 | + <div mat-dialog-actions fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="end"> | |
167 | + <button mat-button | |
168 | + [disabled]="(isLoading$ | async)" | |
169 | + (click)="cancel()">{{ 'action.cancel' | translate }}</button> | |
170 | + <button mat-raised-button | |
171 | + [disabled]="(isLoading$ | async)" | |
172 | + color="primary" | |
173 | + (click)="add()">{{ 'action.add' | translate }}</button> | |
177 | 174 | </div> |
178 | 175 | </div> | ... | ... |
... | ... | @@ -29,6 +29,7 @@ |
29 | 29 | display: flex; |
30 | 30 | flex-direction: column; |
31 | 31 | height: 100%; |
32 | + padding: 0 !important; | |
32 | 33 | |
33 | 34 | .mat-stepper-horizontal { |
34 | 35 | display: flex; |
... | ... | @@ -45,7 +46,7 @@ |
45 | 46 | } |
46 | 47 | } |
47 | 48 | .mat-horizontal-content-container { |
48 | - height: 450px; | |
49 | + height: 530px; | |
49 | 50 | max-height: 100%; |
50 | 51 | width: 100%;; |
51 | 52 | overflow-y: auto; | ... | ... |
... | ... | @@ -923,6 +923,7 @@ |
923 | 923 | "condition-duration-time-unit-required": "Time unit is required.", |
924 | 924 | "advanced-settings": "Advanced settings", |
925 | 925 | "alarm-rule-details": "Details", |
926 | + "add-alarm-rule-details": "Add details", | |
926 | 927 | "propagate-alarm": "Propagate alarm", |
927 | 928 | "alarm-rule-relation-types-list": "Relation types to propagate", |
928 | 929 | "alarm-rule-relation-types-list-hint": "If Propagate relation types are not selected, alarms will be propagated without filtering by relation type.", |
... | ... | @@ -944,14 +945,14 @@ |
944 | 945 | "condition-type": "Condition type", |
945 | 946 | "condition-type-simple": "Simple", |
946 | 947 | "condition-type-duration": "Duration", |
947 | - "condition-during": "During <b>{{during}}</b>", | |
948 | + "condition-during": "During {{during}}", | |
948 | 949 | "condition-type-repeating": "Repeating", |
949 | 950 | "condition-type-required": "Condition type is required.", |
950 | 951 | "condition-repeating-value": "Count of events", |
951 | 952 | "condition-repeating-value-range": "Count of events should be in a range from 1 to 2147483647.", |
952 | 953 | "condition-repeating-value-pattern": "Count of events should be integers.", |
953 | 954 | "condition-repeating-value-required": "Count of events is required.", |
954 | - "condition-repeat-times": "Repeats <b>{ count, plural, 1 {1 time} other {# times} }</b>", | |
955 | + "condition-repeat-times": "Repeats { count, plural, 1 {1 time} other {# times} }", | |
955 | 956 | "schedule-type": "Scheduler type", |
956 | 957 | "schedule-type-required": "Scheduler type is required.", |
957 | 958 | "schedule": "Schedule", | ... | ... |