Oauth2 - set provider name to created user additionalInfo. UI: Improve device profile alarm rules.
This commit is contained in:
parent
b0126a9d47
commit
52e6e76ac6
@ -31,6 +31,8 @@ import org.thingsboard.server.common.data.id.CustomerId;
|
||||
import org.thingsboard.server.common.data.id.DashboardId;
|
||||
import org.thingsboard.server.common.data.id.IdBased;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo;
|
||||
import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig;
|
||||
import org.thingsboard.server.common.data.page.PageData;
|
||||
import org.thingsboard.server.common.data.page.PageLink;
|
||||
import org.thingsboard.server.common.data.security.Authority;
|
||||
@ -76,12 +78,15 @@ public abstract class AbstractOAuth2ClientMapper {
|
||||
|
||||
private final Lock userCreationLock = new ReentrantLock();
|
||||
|
||||
protected SecurityUser getOrCreateSecurityUserFromOAuth2User(OAuth2User oauth2User, boolean allowUserCreation, boolean activateUser) {
|
||||
protected SecurityUser getOrCreateSecurityUserFromOAuth2User(OAuth2User oauth2User, OAuth2ClientRegistrationInfo clientRegistration) {
|
||||
|
||||
OAuth2MapperConfig config = clientRegistration.getMapperConfig();
|
||||
|
||||
UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.USER_NAME, oauth2User.getEmail());
|
||||
|
||||
User user = userService.findUserByEmail(TenantId.SYS_TENANT_ID, oauth2User.getEmail());
|
||||
|
||||
if (user == null && !allowUserCreation) {
|
||||
if (user == null && !config.isAllowUserCreation()) {
|
||||
throw new UsernameNotFoundException("User not found: " + oauth2User.getEmail());
|
||||
}
|
||||
|
||||
@ -106,21 +111,28 @@ public abstract class AbstractOAuth2ClientMapper {
|
||||
user.setFirstName(oauth2User.getFirstName());
|
||||
user.setLastName(oauth2User.getLastName());
|
||||
|
||||
ObjectNode additionalInfo = objectMapper.createObjectNode();
|
||||
|
||||
if (!StringUtils.isEmpty(oauth2User.getDefaultDashboardName())) {
|
||||
Optional<DashboardId> dashboardIdOpt =
|
||||
user.getAuthority() == Authority.TENANT_ADMIN ?
|
||||
getDashboardId(tenantId, oauth2User.getDefaultDashboardName())
|
||||
: getDashboardId(tenantId, customerId, oauth2User.getDefaultDashboardName());
|
||||
if (dashboardIdOpt.isPresent()) {
|
||||
ObjectNode additionalInfo = objectMapper.createObjectNode();
|
||||
additionalInfo.put("defaultDashboardFullscreen", oauth2User.isAlwaysFullScreen());
|
||||
additionalInfo.put("defaultDashboardId", dashboardIdOpt.get().getId().toString());
|
||||
user.setAdditionalInfo(additionalInfo);
|
||||
}
|
||||
}
|
||||
|
||||
if (clientRegistration.getAdditionalInfo() != null &&
|
||||
clientRegistration.getAdditionalInfo().has("providerName")) {
|
||||
additionalInfo.put("authProviderName", clientRegistration.getAdditionalInfo().get("providerName").asText());
|
||||
}
|
||||
|
||||
user.setAdditionalInfo(additionalInfo);
|
||||
|
||||
user = userService.saveUser(user);
|
||||
if (activateUser) {
|
||||
if (config.isActivateUser()) {
|
||||
UserCredentials userCredentials = userService.findUserCredentialsByUserId(user.getTenantId(), user.getId());
|
||||
userService.activateUserCredentials(user.getTenantId(), userCredentials.getActivateToken(), passwordEncoder.encode(""));
|
||||
}
|
||||
|
||||
@ -18,6 +18,7 @@ package org.thingsboard.server.service.security.auth.oauth2;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo;
|
||||
import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig;
|
||||
import org.thingsboard.server.dao.oauth2.OAuth2User;
|
||||
import org.thingsboard.server.service.security.model.SecurityUser;
|
||||
@ -29,11 +30,12 @@ import java.util.Map;
|
||||
public class BasicOAuth2ClientMapper extends AbstractOAuth2ClientMapper implements OAuth2ClientMapper {
|
||||
|
||||
@Override
|
||||
public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2MapperConfig config) {
|
||||
public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2ClientRegistrationInfo clientRegistration) {
|
||||
OAuth2MapperConfig config = clientRegistration.getMapperConfig();
|
||||
Map<String, Object> attributes = token.getPrincipal().getAttributes();
|
||||
String email = BasicMapperUtils.getStringAttributeByKey(attributes, config.getBasic().getEmailAttributeKey());
|
||||
OAuth2User oauth2User = BasicMapperUtils.getOAuth2User(email, attributes, config);
|
||||
|
||||
return getOrCreateSecurityUserFromOAuth2User(oauth2User, config.isAllowUserCreation(), config.isActivateUser());
|
||||
return getOrCreateSecurityUserFromOAuth2User(oauth2User, clientRegistration);
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,6 +23,7 @@ import org.springframework.security.oauth2.client.authentication.OAuth2Authentic
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo;
|
||||
import org.thingsboard.server.common.data.oauth2.OAuth2CustomMapperConfig;
|
||||
import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig;
|
||||
import org.thingsboard.server.dao.oauth2.OAuth2User;
|
||||
@ -38,9 +39,10 @@ public class CustomOAuth2ClientMapper extends AbstractOAuth2ClientMapper impleme
|
||||
private RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();
|
||||
|
||||
@Override
|
||||
public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2MapperConfig config) {
|
||||
public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2ClientRegistrationInfo clientRegistration) {
|
||||
OAuth2MapperConfig config = clientRegistration.getMapperConfig();
|
||||
OAuth2User oauth2User = getOAuth2User(token, providerAccessToken, config.getCustom());
|
||||
return getOrCreateSecurityUserFromOAuth2User(oauth2User, config.isAllowUserCreation(), config.isActivateUser());
|
||||
return getOrCreateSecurityUserFromOAuth2User(oauth2User, clientRegistration);
|
||||
}
|
||||
|
||||
private synchronized OAuth2User getOAuth2User(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2CustomMapperConfig custom) {
|
||||
|
||||
@ -23,6 +23,7 @@ import org.springframework.boot.web.client.RestTemplateBuilder;
|
||||
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo;
|
||||
import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig;
|
||||
import org.thingsboard.server.dao.oauth2.OAuth2Configuration;
|
||||
import org.thingsboard.server.dao.oauth2.OAuth2User;
|
||||
@ -45,12 +46,13 @@ public class GithubOAuth2ClientMapper extends AbstractOAuth2ClientMapper impleme
|
||||
private OAuth2Configuration oAuth2Configuration;
|
||||
|
||||
@Override
|
||||
public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2MapperConfig config) {
|
||||
public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2ClientRegistrationInfo clientRegistration) {
|
||||
OAuth2MapperConfig config = clientRegistration.getMapperConfig();
|
||||
Map<String, String> githubMapperConfig = oAuth2Configuration.getGithubMapper();
|
||||
String email = getEmail(githubMapperConfig.get(EMAIL_URL_KEY), providerAccessToken);
|
||||
Map<String, Object> attributes = token.getPrincipal().getAttributes();
|
||||
OAuth2User oAuth2User = BasicMapperUtils.getOAuth2User(email, attributes, config);
|
||||
return getOrCreateSecurityUserFromOAuth2User(oAuth2User, config.isAllowUserCreation(), config.isActivateUser());
|
||||
return getOrCreateSecurityUserFromOAuth2User(oAuth2User, clientRegistration);
|
||||
}
|
||||
|
||||
private synchronized String getEmail(String emailUrl, String oauth2Token) {
|
||||
|
||||
@ -16,9 +16,9 @@
|
||||
package org.thingsboard.server.service.security.auth.oauth2;
|
||||
|
||||
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
|
||||
import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig;
|
||||
import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo;
|
||||
import org.thingsboard.server.service.security.model.SecurityUser;
|
||||
|
||||
public interface OAuth2ClientMapper {
|
||||
SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2MapperConfig config);
|
||||
SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2ClientRegistrationInfo clientRegistration);
|
||||
}
|
||||
|
||||
@ -74,7 +74,7 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS
|
||||
token.getPrincipal().getName());
|
||||
OAuth2ClientMapper mapper = oauth2ClientMapperProvider.getOAuth2ClientMapperByType(clientRegistration.getMapperConfig().getType());
|
||||
SecurityUser securityUser = mapper.getOrCreateUserByClientPrincipal(token, oAuth2AuthorizedClient.getAccessToken().getTokenValue(),
|
||||
clientRegistration.getMapperConfig());
|
||||
clientRegistration);
|
||||
|
||||
JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser);
|
||||
JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser);
|
||||
@ -85,4 +85,4 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS
|
||||
URLEncoder.encode(e.getMessage(), StandardCharsets.UTF_8.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -33,11 +33,6 @@
|
||||
<tb-anchor #entityDetailsForm></tb-anchor>
|
||||
</div>
|
||||
<div mat-dialog-actions fxLayoutAlign="end center">
|
||||
<button mat-raised-button color="primary"
|
||||
type="submit"
|
||||
[disabled]="(isLoading$ | async) || detailsForm?.invalid || !detailsForm?.dirty">
|
||||
{{ 'action.add' | translate }}
|
||||
</button>
|
||||
<button mat-button color="primary"
|
||||
type="button"
|
||||
cdkFocusInitial
|
||||
@ -45,5 +40,10 @@
|
||||
(click)="cancel()">
|
||||
{{ 'action.cancel' | translate }}
|
||||
</button>
|
||||
<button mat-raised-button color="primary"
|
||||
type="submit"
|
||||
[disabled]="(isLoading$ | async) || detailsForm?.invalid || !detailsForm?.dirty">
|
||||
{{ 'action.add' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@ -15,5 +15,5 @@
|
||||
limitations under the License.
|
||||
|
||||
-->
|
||||
<div class="tb-filter-text" [ngClass]="{disabled: disabled, required: requiredClass}"
|
||||
<div class="tb-filter-text" [ngClass]="{disabled: disabled, required: requiredClass, nowrap: nowrap}"
|
||||
[innerHTML]="filterText"></div>
|
||||
|
||||
@ -21,6 +21,11 @@
|
||||
}
|
||||
&.required {
|
||||
color: #f44336;
|
||||
padding: 0 4px;
|
||||
}
|
||||
&.nowrap {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,6 +54,9 @@ export class FilterTextComponent implements ControlValueAccessor, OnInit {
|
||||
@Input()
|
||||
addFilterPrompt = this.translate.instant('filter.add-filter-prompt');
|
||||
|
||||
@Input()
|
||||
nowrap = false;
|
||||
|
||||
requiredClass = false;
|
||||
|
||||
private filterText: string;
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
limitations under the License.
|
||||
|
||||
-->
|
||||
<div style="min-width: 1000px;">
|
||||
<div>
|
||||
<mat-toolbar color="primary">
|
||||
<h2 translate>device-profile.add</h2>
|
||||
<span fxFlex></span>
|
||||
@ -106,28 +106,25 @@
|
||||
</mat-step>
|
||||
</mat-horizontal-stepper>
|
||||
</div>
|
||||
<div mat-dialog-actions fxLayout="column" fxLayoutAlign="start wrap" fxLayoutGap="8px" style="height: 100px;">
|
||||
<div fxFlex fxLayout="row" fxLayoutAlign="end">
|
||||
<button mat-raised-button
|
||||
*ngIf="showNext"
|
||||
[disabled]="(isLoading$ | async)"
|
||||
(click)="nextStep()">{{ 'action.next-with-label' | translate:{label: (getFormLabel(this.selectedIndex+1) | translate)} }}</button>
|
||||
</div>
|
||||
<div fxFlex fxLayout="row">
|
||||
<button mat-button
|
||||
color="primary"
|
||||
[disabled]="(isLoading$ | async)"
|
||||
(click)="cancel()">{{ 'action.cancel' | translate }}</button>
|
||||
<span fxFlex></span>
|
||||
<div fxLayout="row wrap" fxLayoutGap="8px">
|
||||
<button mat-raised-button *ngIf="selectedIndex > 0"
|
||||
[disabled]="(isLoading$ | async)"
|
||||
(click)="previousStep()">{{ 'action.back' | translate }}</button>
|
||||
<button mat-raised-button
|
||||
[disabled]="(isLoading$ | async)"
|
||||
color="primary"
|
||||
(click)="add()">{{ 'action.add' | translate }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div mat-dialog-actions fxLayout="row">
|
||||
<button mat-stroked-button *ngIf="selectedIndex > 0"
|
||||
[disabled]="(isLoading$ | async)"
|
||||
(click)="previousStep()">{{ 'action.back' | translate }}</button>
|
||||
<span fxFlex></span>
|
||||
<button mat-stroked-button
|
||||
color="primary"
|
||||
*ngIf="showNext"
|
||||
[disabled]="(isLoading$ | async)"
|
||||
(click)="nextStep()">{{ 'action.next-with-label' | translate:{label: (getFormLabel(this.selectedIndex+1) | translate)} }}</button>
|
||||
</div>
|
||||
<mat-divider></mat-divider>
|
||||
<div mat-dialog-actions fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="end">
|
||||
<button mat-button
|
||||
[disabled]="(isLoading$ | async)"
|
||||
(click)="cancel()">{{ 'action.cancel' | translate }}</button>
|
||||
<button mat-raised-button
|
||||
[disabled]="(isLoading$ | async)"
|
||||
color="primary"
|
||||
(click)="add()">{{ 'action.add' | translate }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -28,7 +28,7 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
padding: 24px 24px 8px !important;
|
||||
padding: 0 !important;
|
||||
|
||||
.mat-stepper-horizontal {
|
||||
display: flex;
|
||||
@ -45,7 +45,7 @@
|
||||
}
|
||||
}
|
||||
.mat-horizontal-content-container {
|
||||
height: 350px;
|
||||
height: 530px;
|
||||
max-height: 100%;
|
||||
width: 100%;;
|
||||
overflow-y: auto;
|
||||
|
||||
@ -15,25 +15,22 @@
|
||||
limitations under the License.
|
||||
|
||||
-->
|
||||
<div fxLayout="column" fxFlex [formGroup]="alarmRuleConditionFormGroup">
|
||||
<div fxLayout="row" fxLayoutAlign="start center" style="min-height: 40px;">
|
||||
<label class="tb-title" translate>device-profile.condition</label>
|
||||
<span fxFlex></span>
|
||||
<a mat-button color="primary"
|
||||
type="button"
|
||||
(click)="openFilterDialog($event)"
|
||||
matTooltip="{{ (disabled ? 'action.view' : 'action.edit') | translate }}"
|
||||
matTooltipPosition="above">
|
||||
{{ (disabled ? 'action.view' : (conditionSet() ? 'action.edit' : 'action.add')) | translate }}
|
||||
</a>
|
||||
</div>
|
||||
<div fxLayout="row" fxLayoutAlign="start center" [formGroup]="alarmRuleConditionFormGroup" style="min-width: 0;">
|
||||
<div class="tb-alarm-rule-condition" fxFlex fxLayout="column" fxLayoutAlign="center" (click)="openFilterDialog($event)">
|
||||
<tb-filter-text formControlName="condition"
|
||||
[nowrap]="true"
|
||||
required
|
||||
addFilterPrompt="{{'device-profile.enter-alarm-rule-condition-prompt' | translate}}">
|
||||
</tb-filter-text>
|
||||
<span class="tb-alarm-rule-condition-spec" [ngClass]="{disabled: this.disabled}" [innerHTML]="specText">
|
||||
<span *ngIf="specText" class="tb-alarm-rule-condition-spec" [ngClass]="{disabled: this.disabled}" [innerHTML]="specText">
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<button mat-icon-button
|
||||
[color]="conditionSet() ? 'primary' : 'warn'"
|
||||
type="button"
|
||||
(click)="openFilterDialog($event)"
|
||||
matTooltip="{{ (disabled ? 'action.view' : 'action.edit') | translate }}"
|
||||
matTooltipPosition="above">
|
||||
<mat-icon>{{ disabled ? 'visibility' : (conditionSet() ? 'edit' : 'add') }}</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@ -22,13 +22,10 @@
|
||||
}
|
||||
.tb-alarm-rule-condition {
|
||||
cursor: pointer;
|
||||
min-width: 0;
|
||||
.tb-alarm-rule-condition-spec {
|
||||
margin-top: 1em;
|
||||
line-height: 1.8em;
|
||||
opacity: 0.7;
|
||||
padding: 4px;
|
||||
&.disabled {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,36 +16,23 @@
|
||||
|
||||
-->
|
||||
<div fxLayout="column" [formGroup]="alarmRuleFormGroup">
|
||||
<tb-alarm-rule-condition fxFlex class="row"
|
||||
formControlName="condition">
|
||||
<tb-alarm-rule-condition formControlName="condition">
|
||||
</tb-alarm-rule-condition>
|
||||
<mat-divider class="row"></mat-divider>
|
||||
<tb-alarm-schedule-info fxFlex class="row"
|
||||
formControlName="schedule">
|
||||
<tb-alarm-schedule-info formControlName="schedule">
|
||||
</tb-alarm-schedule-info>
|
||||
<mat-divider class="row"></mat-divider>
|
||||
<div fxLayout="column" fxFlex class="tb-alarm-rule-details row">
|
||||
<div fxLayout="row" fxLayoutAlign="start center" style="min-height: 40px;">
|
||||
<label class="tb-title" translate>device-profile.alarm-rule-details</label>
|
||||
<span fxFlex></span>
|
||||
<a mat-button color="primary"
|
||||
*ngIf="!disabled"
|
||||
type="button"
|
||||
(click)="openEditDetailsDialog($event)"
|
||||
matTooltip="{{ 'action.edit' | translate }}"
|
||||
matTooltipPosition="above">
|
||||
{{ 'action.edit' | translate }}
|
||||
</a>
|
||||
</div>
|
||||
<div fxLayout="row" fxLayoutAlign="start start">
|
||||
<div class="tb-alarm-rule-details-content" [ngClass]="{disabled: this.disabled, collapsed: !this.expandAlarmDetails}"
|
||||
(click)="!disabled ? openEditDetailsDialog($event) : {}"
|
||||
fxFlex [innerHTML]="alarmRuleFormGroup.get('alarmDetails').value"></div>
|
||||
<a mat-button color="primary"
|
||||
type="button"
|
||||
(click)="expandAlarmDetails = !expandAlarmDetails">
|
||||
{{ (expandAlarmDetails ? 'action.hide' : 'action.read-more') | translate }}
|
||||
</a>
|
||||
</div>
|
||||
<div *ngIf="!disabled || alarmRuleFormGroup.get('alarmDetails').value" fxLayout="row" fxLayoutAlign="start center">
|
||||
<span class="tb-alarm-rule-details title" (click)="openEditDetailsDialog($event)">
|
||||
{{ alarmRuleFormGroup.get('alarmDetails').value ? ('device-profile.alarm-rule-details' | translate) + ': ' : ('device-profile.add-alarm-rule-details' | translate) }}
|
||||
</span>
|
||||
<span *ngIf="alarmRuleFormGroup.get('alarmDetails').value" class="tb-alarm-rule-details"
|
||||
(click)="openEditDetailsDialog($event)"
|
||||
[innerHTML]="alarmRuleFormGroup.get('alarmDetails').value"></span>
|
||||
<button mat-icon-button color="primary"
|
||||
type="button"
|
||||
(click)="openEditDetailsDialog($event)"
|
||||
matTooltip="{{ (disabled ? 'action.view' : 'action.edit') | translate }}"
|
||||
matTooltipPosition="above">
|
||||
<mat-icon>{{ disabled ? 'visibility' : (alarmRuleFormGroup.get('alarmDetails').value ? 'edit' : 'add') }}</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -14,31 +14,19 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
:host {
|
||||
min-width: 0;
|
||||
.row {
|
||||
margin-top: 1em;
|
||||
}
|
||||
.tb-alarm-rule-details {
|
||||
a.mat-button {
|
||||
&:hover, &:focus {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
.tb-alarm-rule-details-content {
|
||||
min-height: 33px;
|
||||
overflow: hidden;
|
||||
white-space: pre;
|
||||
line-height: 1.8em;
|
||||
padding: 4px;
|
||||
cursor: pointer;
|
||||
&.collapsed {
|
||||
max-height: 33px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
&.disabled {
|
||||
opacity: 0.7;
|
||||
cursor: auto;
|
||||
}
|
||||
padding: 4px;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
&.title {
|
||||
opacity: 0.7;
|
||||
overflow: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -118,7 +118,8 @@ export class AlarmRuleComponent implements ControlValueAccessor, OnInit, Validat
|
||||
disableClose: true,
|
||||
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
|
||||
data: {
|
||||
alarmDetails: this.alarmRuleFormGroup.get('alarmDetails').value
|
||||
alarmDetails: this.alarmRuleFormGroup.get('alarmDetails').value,
|
||||
readonly: this.disabled
|
||||
}
|
||||
}).afterClosed().subscribe((alarmDetails) => {
|
||||
if (isDefinedAndNotNull(alarmDetails)) {
|
||||
|
||||
@ -15,19 +15,16 @@
|
||||
limitations under the License.
|
||||
|
||||
-->
|
||||
<div fxLayout="column" fxFlex>
|
||||
<div fxLayout="row" fxLayoutAlign="start center" style="min-height: 40px;">
|
||||
<label class="tb-title" translate>device-profile.schedule</label>
|
||||
<span fxFlex></span>
|
||||
<a mat-button color="primary"
|
||||
type="button"
|
||||
(click)="openScheduleDialog($event)"
|
||||
matTooltip="{{ (disabled ? 'action.view' : 'action.edit') | translate }}"
|
||||
matTooltipPosition="above">
|
||||
{{ (disabled ? 'action.view' : 'action.edit' ) | translate }}
|
||||
</a>
|
||||
</div>
|
||||
<sapn class="tb-alarm-rule-schedule" [ngClass]="{disabled: this.disabled}" (click)="openScheduleDialog($event)"
|
||||
<div fxLayout="row" fxLayoutAlign="start center" style="min-width: 0;">
|
||||
<span class="tb-alarm-rule-schedule title" (click)="openScheduleDialog($event)">{{('device-profile.schedule' | translate) + ': '}}</span>
|
||||
<span class="tb-alarm-rule-schedule" (click)="openScheduleDialog($event)"
|
||||
[innerHTML]="scheduleText">
|
||||
</sapn>
|
||||
</span>
|
||||
<button mat-icon-button color="primary"
|
||||
type="button"
|
||||
(click)="openScheduleDialog($event)"
|
||||
matTooltip="{{ (disabled ? 'action.view' : 'action.edit') | translate }}"
|
||||
matTooltipPosition="above">
|
||||
<mat-icon>{{ disabled ? 'visibility' : 'edit' }}</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@ -21,14 +21,14 @@
|
||||
}
|
||||
}
|
||||
.tb-alarm-rule-schedule {
|
||||
line-height: 1.8em;
|
||||
padding: 4px;
|
||||
cursor: pointer;
|
||||
&.disabled {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
&.title {
|
||||
opacity: 0.7;
|
||||
}
|
||||
.nowrap {
|
||||
white-space: nowrap;
|
||||
overflow: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -101,7 +101,7 @@ export class AlarmScheduleInfoComponent implements ControlValueAccessor, OnInit
|
||||
for (const item of schedule.items) {
|
||||
if (item.enabled) {
|
||||
if (this.scheduleText.length) {
|
||||
this.scheduleText += '<br/>';
|
||||
this.scheduleText += ', ';
|
||||
}
|
||||
this.scheduleText += this.translate.instant(dayOfWeekTranslations[item.dayOfWeek - 1]);
|
||||
this.scheduleText += ' <b>' + getAlarmScheduleRangeText(utcTimestampToTimeOfDay(item.startsOn),
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
<div *ngFor="let createAlarmRuleControl of createAlarmRulesFormArray().controls; let $index = index;
|
||||
last as isLast;" fxLayout="row" fxLayoutAlign="start center"
|
||||
fxLayoutGap="8px" style="padding-bottom: 8px;" [formGroup]="createAlarmRuleControl">
|
||||
<div class="create-alarm-rule" fxFlex fxLayout="column" fxLayoutGap="8px" fxLayoutAlign="start">
|
||||
<div class="create-alarm-rule" fxFlex fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="start">
|
||||
<mat-form-field class="severity mat-block" floatLabel="always" hideRequiredMarker>
|
||||
<mat-label translate>alarm.severity</mat-label>
|
||||
<mat-select formControlName="severity"
|
||||
@ -34,7 +34,7 @@
|
||||
{{ 'device-profile.alarm-severity-required' | translate }}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-divider></mat-divider>
|
||||
<mat-divider vertical></mat-divider>
|
||||
<tb-alarm-rule formControlName="alarmRule" required fxFlex>
|
||||
</tb-alarm-rule>
|
||||
</div>
|
||||
|
||||
@ -98,7 +98,7 @@
|
||||
<mat-icon>remove_circle_outline</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div *ngIf="!alarmFormGroup.get('clearRule').value">
|
||||
<div *ngIf="disabled && !alarmFormGroup.get('clearRule').value">
|
||||
<span translate fxLayoutAlign="center center" style="margin: 16px 0"
|
||||
class="tb-prompt">device-profile.no-clear-alarm-rule</span>
|
||||
</div>
|
||||
|
||||
@ -38,16 +38,16 @@
|
||||
</fieldset>
|
||||
</div>
|
||||
<div mat-dialog-actions fxLayoutAlign="end center">
|
||||
<button mat-raised-button color="primary"
|
||||
type="submit"
|
||||
[disabled]="(isLoading$ | async) || editDetailsFormGroup.invalid || !editDetailsFormGroup.dirty">
|
||||
{{ 'action.save' | translate }}
|
||||
</button>
|
||||
<button mat-button color="primary"
|
||||
type="button"
|
||||
[disabled]="(isLoading$ | async)"
|
||||
(click)="cancel()" cdkFocusInitial>
|
||||
{{ 'action.cancel' | translate }}
|
||||
</button>
|
||||
<button *ngIf="!data.readonly" mat-raised-button color="primary"
|
||||
type="submit"
|
||||
[disabled]="(isLoading$ | async) || editDetailsFormGroup.invalid || !editDetailsFormGroup.dirty">
|
||||
{{ 'action.save' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@ -27,6 +27,7 @@ import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
export interface EditAlarmDetailsDialogData {
|
||||
alarmDetails: string;
|
||||
readonly: boolean;
|
||||
}
|
||||
|
||||
@Component({
|
||||
@ -57,6 +58,9 @@ export class EditAlarmDetailsDialogComponent extends DialogComponent<EditAlarmDe
|
||||
this.editDetailsFormGroup = this.fb.group({
|
||||
alarmDetails: [this.alarmDetails]
|
||||
});
|
||||
if (this.data.readonly) {
|
||||
this.editDetailsFormGroup.disable();
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
|
||||
@ -151,28 +151,25 @@
|
||||
</mat-step>
|
||||
</mat-horizontal-stepper>
|
||||
</div>
|
||||
<div mat-dialog-actions fxLayout="column" fxLayoutAlign="start wrap" fxLayoutGap="8px" style="height: 100px;">
|
||||
<div fxFlex fxLayout="row" fxLayoutAlign="end">
|
||||
<button mat-raised-button
|
||||
*ngIf="showNext"
|
||||
[disabled]="(isLoading$ | async)"
|
||||
(click)="nextStep()">{{ 'action.next-with-label' | translate:{label: (getFormLabel(this.selectedIndex+1) | translate)} }}</button>
|
||||
</div>
|
||||
<div fxFlex fxLayout="row">
|
||||
<button mat-button
|
||||
color="primary"
|
||||
[disabled]="(isLoading$ | async)"
|
||||
(click)="cancel()">{{ 'action.cancel' | translate }}</button>
|
||||
<span fxFlex></span>
|
||||
<div fxLayout="row wrap" fxLayoutGap="8px">
|
||||
<button mat-raised-button *ngIf="selectedIndex > 0"
|
||||
[disabled]="(isLoading$ | async)"
|
||||
(click)="previousStep()">{{ 'action.back' | translate }}</button>
|
||||
<button mat-raised-button
|
||||
[disabled]="(isLoading$ | async)"
|
||||
color="primary"
|
||||
(click)="add()">{{ 'action.add' | translate }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div mat-dialog-actions fxLayout="row">
|
||||
<button mat-stroked-button *ngIf="selectedIndex > 0"
|
||||
[disabled]="(isLoading$ | async)"
|
||||
(click)="previousStep()">{{ 'action.back' | translate }}</button>
|
||||
<span fxFlex></span>
|
||||
<button mat-stroked-button
|
||||
color="primary"
|
||||
*ngIf="showNext"
|
||||
[disabled]="(isLoading$ | async)"
|
||||
(click)="nextStep()">{{ 'action.next-with-label' | translate:{label: (getFormLabel(this.selectedIndex+1) | translate)} }}</button>
|
||||
</div>
|
||||
<mat-divider></mat-divider>
|
||||
<div mat-dialog-actions fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="end">
|
||||
<button mat-button
|
||||
[disabled]="(isLoading$ | async)"
|
||||
(click)="cancel()">{{ 'action.cancel' | translate }}</button>
|
||||
<button mat-raised-button
|
||||
[disabled]="(isLoading$ | async)"
|
||||
color="primary"
|
||||
(click)="add()">{{ 'action.add' | translate }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -29,6 +29,7 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
padding: 0 !important;
|
||||
|
||||
.mat-stepper-horizontal {
|
||||
display: flex;
|
||||
@ -45,7 +46,7 @@
|
||||
}
|
||||
}
|
||||
.mat-horizontal-content-container {
|
||||
height: 450px;
|
||||
height: 530px;
|
||||
max-height: 100%;
|
||||
width: 100%;;
|
||||
overflow-y: auto;
|
||||
|
||||
@ -923,6 +923,7 @@
|
||||
"condition-duration-time-unit-required": "Time unit is required.",
|
||||
"advanced-settings": "Advanced settings",
|
||||
"alarm-rule-details": "Details",
|
||||
"add-alarm-rule-details": "Add details",
|
||||
"propagate-alarm": "Propagate alarm",
|
||||
"alarm-rule-relation-types-list": "Relation types to propagate",
|
||||
"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 @@
|
||||
"condition-type": "Condition type",
|
||||
"condition-type-simple": "Simple",
|
||||
"condition-type-duration": "Duration",
|
||||
"condition-during": "During <b>{{during}}</b>",
|
||||
"condition-during": "During {{during}}",
|
||||
"condition-type-repeating": "Repeating",
|
||||
"condition-type-required": "Condition type is required.",
|
||||
"condition-repeating-value": "Count of events",
|
||||
"condition-repeating-value-range": "Count of events should be in a range from 1 to 2147483647.",
|
||||
"condition-repeating-value-pattern": "Count of events should be integers.",
|
||||
"condition-repeating-value-required": "Count of events is required.",
|
||||
"condition-repeat-times": "Repeats <b>{ count, plural, 1 {1 time} other {# times} }</b>",
|
||||
"condition-repeat-times": "Repeats { count, plural, 1 {1 time} other {# times} }",
|
||||
"schedule-type": "Scheduler type",
|
||||
"schedule-type-required": "Scheduler type is required.",
|
||||
"schedule": "Schedule",
|
||||
|
||||
@ -869,10 +869,7 @@ mat-label {
|
||||
}
|
||||
.mat-dialog-actions {
|
||||
margin-bottom: 0;
|
||||
padding: 8px 8px 8px 16px;
|
||||
button:last-of-type{
|
||||
margin-right: 20px;
|
||||
}
|
||||
padding: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user