Oauth2 - set provider name to created user additionalInfo. UI: Improve device profile alarm rules.

This commit is contained in:
Igor Kulikov 2020-10-13 19:48:46 +03:00
parent b0126a9d47
commit 52e6e76ac6
28 changed files with 165 additions and 175 deletions

View File

@ -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(""));
}

View File

@ -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);
}
}

View File

@ -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) {

View File

@ -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) {

View File

@ -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);
}

View File

@ -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);

View File

@ -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>

View File

@ -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>

View File

@ -21,6 +21,11 @@
}
&.required {
color: #f44336;
padding: 0 4px;
}
&.nowrap {
white-space: nowrap;
overflow: hidden;
}
}
}

View File

@ -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;

View File

@ -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
<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>
<div fxFlex fxLayout="row">
<mat-divider></mat-divider>
<div mat-dialog-actions fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="end">
<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>
</div>

View File

@ -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;

View File

@ -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"
<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 *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">
{{ (disabled ? 'action.view' : (conditionSet() ? 'action.edit' : 'action.add')) | translate }}
</a>
</div>
<div class="tb-alarm-rule-condition" fxFlex fxLayout="column" fxLayoutAlign="center" (click)="openFilterDialog($event)">
<tb-filter-text formControlName="condition"
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>
</div>
<mat-icon>{{ disabled ? 'visibility' : (conditionSet() ? 'edit' : 'add') }}</mat-icon>
</button>
</div>

View File

@ -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;
padding: 4px;
&.disabled {
opacity: 0.7;
}
padding: 4px;
}
}
}

View File

@ -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"
<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="{{ 'action.edit' | translate }}"
matTooltip="{{ (disabled ? 'action.view' : '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>
<mat-icon>{{ disabled ? 'visibility' : (alarmRuleFormGroup.get('alarmDetails').value ? 'edit' : 'add') }}</mat-icon>
</button>
</div>
</div>

View File

@ -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;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
&.disabled {
&.title {
opacity: 0.7;
cursor: auto;
}
overflow: visible;
}
}
}

View File

@ -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)) {

View File

@ -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"
<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">
</span>
<button mat-icon-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)"
[innerHTML]="scheduleText">
</sapn>
<mat-icon>{{ disabled ? 'visibility' : 'edit' }}</mat-icon>
</button>
</div>

View File

@ -21,14 +21,14 @@
}
}
.tb-alarm-rule-schedule {
line-height: 1.8em;
padding: 4px;
cursor: pointer;
&.disabled {
opacity: 0.7;
}
.nowrap {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
&.title {
opacity: 0.7;
overflow: visible;
}
}
}

View File

@ -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),

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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 {

View File

@ -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
<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>
<div fxFlex fxLayout="row">
<mat-divider></mat-divider>
<div mat-dialog-actions fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="end">
<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>
</div>

View File

@ -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;

View File

@ -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",

View File

@ -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;
}
}
}