Fix device profile provision configuration conflict
This commit is contained in:
commit
82f2915865
@ -582,12 +582,9 @@ export class WidgetSubscription implements IWidgetSubscription {
|
||||
const data = this.alarms.data[0];
|
||||
entityId = data.originator;
|
||||
entityName = data.originatorName;
|
||||
entityLabel = data.originatorLabel;
|
||||
if (data.latest && data.latest[EntityKeyType.ENTITY_FIELD]) {
|
||||
const entityFields = data.latest[EntityKeyType.ENTITY_FIELD];
|
||||
const labelValue = entityFields.label;
|
||||
if (labelValue) {
|
||||
entityLabel = labelValue.value;
|
||||
}
|
||||
const additionalInfoValue = entityFields.additionalInfo;
|
||||
if (additionalInfoValue) {
|
||||
const additionalInfo = additionalInfoValue.value;
|
||||
|
||||
@ -29,7 +29,11 @@ import {
|
||||
isDefined,
|
||||
isDefinedAndNotNull,
|
||||
isString,
|
||||
isUndefined
|
||||
isUndefined,
|
||||
objToBase64,
|
||||
objToBase64URI,
|
||||
base64toString,
|
||||
base64toObj
|
||||
} from '@core/utils';
|
||||
import { WindowMessage } from '@shared/models/window-message.model';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
@ -87,7 +91,7 @@ const commonMaterialIcons: Array<string> = ['more_horiz', 'more_vert', 'open_in_
|
||||
'arrow_forward', 'arrow_upwards', 'close', 'refresh', 'menu', 'show_chart', 'multiline_chart', 'pie_chart', 'insert_chart', 'people',
|
||||
'person', 'domain', 'devices_other', 'now_widgets', 'dashboards', 'map', 'pin_drop', 'my_location', 'extension', 'search',
|
||||
'settings', 'notifications', 'notifications_active', 'info', 'info_outline', 'warning', 'list', 'file_download', 'import_export',
|
||||
'share', 'add', 'edit', 'done'];
|
||||
'share', 'add', 'edit', 'done', 'delete'];
|
||||
|
||||
// @dynamic
|
||||
@Injectable({
|
||||
@ -518,4 +522,21 @@ export class UtilsService {
|
||||
refCount()
|
||||
);
|
||||
}
|
||||
|
||||
public objToBase64(obj: any): string {
|
||||
return objToBase64(obj);
|
||||
}
|
||||
|
||||
public base64toString(b64Encoded: string): string {
|
||||
return base64toString(b64Encoded);
|
||||
}
|
||||
|
||||
public objToBase64URI(obj: any): string {
|
||||
return objToBase64URI(obj);
|
||||
}
|
||||
|
||||
public base64toObj(b64Encoded: string): any {
|
||||
return base64toObj(b64Encoded);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -42,7 +42,7 @@
|
||||
<div class="predicate-list">
|
||||
<div fxLayout="row" fxLayoutAlign="start" style="height: 71px;"
|
||||
formArrayName="predicates"
|
||||
*ngFor="let predicateControl of predicatesFormArray().controls; let $index = index">
|
||||
*ngFor="let predicateControl of predicatesFormArray.controls; let $index = index">
|
||||
<div fxFlex="8" fxLayout="row" fxLayoutAlign="center" class="filters-operation">
|
||||
<span *ngIf="$index > 0">{{ complexOperationTranslations.get(operation) | translate }}</span>
|
||||
</div>
|
||||
@ -68,7 +68,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span [fxShow]="!predicatesFormArray().length"
|
||||
<span [fxShow]="!predicatesFormArray.length"
|
||||
fxLayoutAlign="center center" [ngClass]="{'disabled': disabled}"
|
||||
class="no-data-found" translate>filter.no-filters</span>
|
||||
</div>
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
/// limitations under the License.
|
||||
///
|
||||
|
||||
import { Component, forwardRef, Inject, Input, OnInit } from '@angular/core';
|
||||
import { Component, forwardRef, Inject, Input, OnDestroy, OnInit } from '@angular/core';
|
||||
import {
|
||||
AbstractControl,
|
||||
ControlValueAccessor,
|
||||
@ -27,7 +27,7 @@ import {
|
||||
Validator,
|
||||
Validators
|
||||
} from '@angular/forms';
|
||||
import { Observable, of, Subscription } from 'rxjs';
|
||||
import { Observable, of, Subject } from 'rxjs';
|
||||
import {
|
||||
ComplexFilterPredicateInfo,
|
||||
ComplexOperation,
|
||||
@ -37,7 +37,7 @@ import {
|
||||
KeyFilterPredicateInfo
|
||||
} from '@shared/models/query/query.models';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { map, takeUntil } from 'rxjs/operators';
|
||||
import { ComponentType } from '@angular/cdk/portal';
|
||||
import { COMPLEX_FILTER_PREDICATE_DIALOG_COMPONENT_TOKEN } from '@home/components/tokens';
|
||||
import { ComplexFilterPredicateDialogData } from '@home/components/filter/filter-component.models';
|
||||
@ -59,7 +59,7 @@ import { ComplexFilterPredicateDialogData } from '@home/components/filter/filter
|
||||
}
|
||||
]
|
||||
})
|
||||
export class FilterPredicateListComponent implements ControlValueAccessor, Validator, OnInit {
|
||||
export class FilterPredicateListComponent implements ControlValueAccessor, Validator, OnInit, OnDestroy {
|
||||
|
||||
@Input() disabled: boolean;
|
||||
|
||||
@ -81,22 +81,29 @@ export class FilterPredicateListComponent implements ControlValueAccessor, Valid
|
||||
|
||||
complexOperationTranslations = complexOperationTranslationMap;
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
private propagateChange = null;
|
||||
|
||||
private valueChangeSubscription: Subscription = null;
|
||||
|
||||
constructor(private fb: UntypedFormBuilder,
|
||||
@Inject(COMPLEX_FILTER_PREDICATE_DIALOG_COMPONENT_TOKEN) private complexFilterPredicateDialogComponent: ComponentType<any>,
|
||||
private dialog: MatDialog) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.filterListFormGroup = this.fb.group({});
|
||||
this.filterListFormGroup.addControl('predicates',
|
||||
this.fb.array([]));
|
||||
this.filterListFormGroup = this.fb.group({
|
||||
predicates: this.fb.array([])
|
||||
});
|
||||
this.filterListFormGroup.valueChanges.pipe(
|
||||
takeUntil(this.destroy$)
|
||||
).subscribe(() => this.updateModel());
|
||||
}
|
||||
|
||||
predicatesFormArray(): UntypedFormArray {
|
||||
ngOnDestroy() {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
get predicatesFormArray(): UntypedFormArray {
|
||||
return this.filterListFormGroup.get('predicates') as UntypedFormArray;
|
||||
}
|
||||
|
||||
@ -123,28 +130,26 @@ export class FilterPredicateListComponent implements ControlValueAccessor, Valid
|
||||
}
|
||||
|
||||
writeValue(predicates: Array<KeyFilterPredicateInfo>): void {
|
||||
if (this.valueChangeSubscription) {
|
||||
this.valueChangeSubscription.unsubscribe();
|
||||
}
|
||||
const predicateControls: Array<AbstractControl> = [];
|
||||
if (predicates) {
|
||||
for (const predicate of predicates) {
|
||||
predicateControls.push(this.fb.control(predicate, [Validators.required]));
|
||||
}
|
||||
}
|
||||
this.filterListFormGroup.setControl('predicates', this.fb.array(predicateControls));
|
||||
this.valueChangeSubscription = this.filterListFormGroup.valueChanges.subscribe(() => {
|
||||
this.updateModel();
|
||||
});
|
||||
if (this.disabled) {
|
||||
this.filterListFormGroup.disable({emitEvent: false});
|
||||
if (predicates.length === this.predicatesFormArray.length) {
|
||||
this.predicatesFormArray.patchValue(predicates, {emitEvent: false});
|
||||
} else {
|
||||
this.filterListFormGroup.enable({emitEvent: false});
|
||||
const predicateControls: Array<AbstractControl> = [];
|
||||
if (predicates) {
|
||||
for (const predicate of predicates) {
|
||||
predicateControls.push(this.fb.control(predicate, [Validators.required]));
|
||||
}
|
||||
}
|
||||
this.filterListFormGroup.setControl('predicates', this.fb.array(predicateControls), {emitEvent: false});
|
||||
if (this.disabled) {
|
||||
this.filterListFormGroup.disable({emitEvent: false});
|
||||
} else {
|
||||
this.filterListFormGroup.enable({emitEvent: false});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public removePredicate(index: number) {
|
||||
(this.filterListFormGroup.get('predicates') as UntypedFormArray).removeAt(index);
|
||||
this.predicatesFormArray.removeAt(index);
|
||||
}
|
||||
|
||||
public addPredicate(complex: boolean) {
|
||||
|
||||
@ -64,6 +64,15 @@
|
||||
matTooltipPosition="above">
|
||||
<mat-icon>edit</mat-icon>
|
||||
</button>
|
||||
<button [disabled]="isLoading$ | async"
|
||||
mat-icon-button color="primary"
|
||||
style="min-width: 48px;"
|
||||
type="button"
|
||||
(click)="duplicateFilter($index)"
|
||||
matTooltip="{{ 'filter.duplicate-filter' | translate }}"
|
||||
matTooltipPosition="above">
|
||||
<mat-icon>content_copy</mat-icon>
|
||||
</button>
|
||||
<button [disabled]="isLoading$ | async"
|
||||
mat-icon-button color="primary"
|
||||
style="min-width: 40px;"
|
||||
|
||||
@ -64,6 +64,8 @@ export class FiltersDialogComponent extends DialogComponent<FiltersDialogCompone
|
||||
|
||||
filterToWidgetsMap: {[filterId: string]: Array<string>} = {};
|
||||
|
||||
filterNames: Set<string> = new Set<string>();
|
||||
|
||||
filtersFormGroup: UntypedFormGroup;
|
||||
|
||||
submitted = false;
|
||||
@ -112,6 +114,7 @@ export class FiltersDialogComponent extends DialogComponent<FiltersDialogCompone
|
||||
if (isUndefined(filter.editable)) {
|
||||
filter.editable = true;
|
||||
}
|
||||
this.filterNames.add(filter.filter);
|
||||
filterControls.push(this.createFilterFormControl(filterId, filter));
|
||||
}
|
||||
|
||||
@ -158,10 +161,37 @@ export class FiltersDialogComponent extends DialogComponent<FiltersDialogCompone
|
||||
message, this.translate.instant('action.close'), true);
|
||||
} else {
|
||||
(this.filtersFormGroup.get('filters') as UntypedFormArray).removeAt(index);
|
||||
this.filterNames.delete(filter.filter);
|
||||
this.filtersFormGroup.markAsDirty();
|
||||
}
|
||||
}
|
||||
|
||||
private getNextDuplicatedName(filterName: string): string {
|
||||
const suffix = ` - ${this.translate.instant('action.copy')} `;
|
||||
let counter = 0;
|
||||
while (++counter < Number.MAX_SAFE_INTEGER) {
|
||||
const newName = `${filterName}${suffix}${counter}`;
|
||||
if (!this.filterNames.has(newName)) {
|
||||
return newName;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
duplicateFilter(index: number) {
|
||||
const originalFilter = (this.filtersFormGroup.get('filters').value as any[])[index];
|
||||
const newFilterName = this.getNextDuplicatedName(originalFilter.filter);
|
||||
if (newFilterName) {
|
||||
const duplicatedFilter = deepClone(originalFilter);
|
||||
duplicatedFilter.id = this.utils.guid();
|
||||
duplicatedFilter.filter = newFilterName;
|
||||
(this.filtersFormGroup.get('filters') as UntypedFormArray).
|
||||
insert(index + 1, this.createFilterFormControl(duplicatedFilter.id, duplicatedFilter));
|
||||
this.filterNames.add(duplicatedFilter.filter);
|
||||
}
|
||||
}
|
||||
|
||||
public addFilter() {
|
||||
this.openFilterDialog(-1);
|
||||
}
|
||||
@ -176,6 +206,7 @@ export class FiltersDialogComponent extends DialogComponent<FiltersDialogCompone
|
||||
const filtersArray = this.filtersFormGroup.get('filters').value as any[];
|
||||
if (!isAdd) {
|
||||
filter = filtersArray[index];
|
||||
this.filterNames.delete(filter.filter);
|
||||
}
|
||||
this.dialog.open<FilterDialogComponent, FilterDialogData,
|
||||
Filter>(FilterDialogComponent, {
|
||||
@ -197,6 +228,7 @@ export class FiltersDialogComponent extends DialogComponent<FiltersDialogCompone
|
||||
filterFormControl.get('editable').patchValue(result.editable);
|
||||
filterFormControl.get('keyFilters').patchValue(result.keyFilters);
|
||||
}
|
||||
this.filterNames.add(result.filter);
|
||||
this.filtersFormGroup.markAsDirty();
|
||||
}
|
||||
});
|
||||
|
||||
@ -36,7 +36,7 @@
|
||||
<div class="key-filter-list">
|
||||
<div fxLayout="row" fxLayoutAlign="start" style="max-height: 71px;"
|
||||
formArrayName="keyFilters"
|
||||
*ngFor="let keyFilterControl of keyFiltersFormArray().controls; let $index = index">
|
||||
*ngFor="let keyFilterControl of keyFiltersFormArray.controls; let $index = index">
|
||||
<div fxFlex="8" class="filters-operation">
|
||||
<span *ngIf="$index > 0" translate>filter.operation.and</span>
|
||||
</div>
|
||||
@ -63,7 +63,7 @@
|
||||
<mat-divider></mat-divider>
|
||||
</div>
|
||||
</div>
|
||||
<span [fxShow]="!keyFiltersFormArray().length"
|
||||
<span [fxShow]="!keyFiltersFormArray.length"
|
||||
fxLayoutAlign="center center" [ngClass]="{'disabled': disabled}"
|
||||
class="no-data-found" translate>filter.no-key-filters</span>
|
||||
</div>
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
/// limitations under the License.
|
||||
///
|
||||
|
||||
import { Component, forwardRef, Input, OnInit } from '@angular/core';
|
||||
import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core';
|
||||
import {
|
||||
AbstractControl,
|
||||
ControlValueAccessor,
|
||||
@ -28,7 +28,7 @@ import {
|
||||
Validator,
|
||||
Validators
|
||||
} from '@angular/forms';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
import {
|
||||
EntityKeyType,
|
||||
entityKeyTypeTranslationMap,
|
||||
@ -39,6 +39,7 @@ import { MatDialog } from '@angular/material/dialog';
|
||||
import { deepClone } from '@core/utils';
|
||||
import { KeyFilterDialogComponent, KeyFilterDialogData } from '@home/components/filter/key-filter-dialog.component';
|
||||
import { EntityId } from '@shared/models/id/entity-id';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'tb-key-filter-list',
|
||||
@ -57,7 +58,7 @@ import { EntityId } from '@shared/models/id/entity-id';
|
||||
}
|
||||
]
|
||||
})
|
||||
export class KeyFilterListComponent implements ControlValueAccessor, Validator, OnInit {
|
||||
export class KeyFilterListComponent implements ControlValueAccessor, Validator, OnInit, OnDestroy {
|
||||
|
||||
@Input() disabled: boolean;
|
||||
|
||||
@ -75,22 +76,30 @@ export class KeyFilterListComponent implements ControlValueAccessor, Validator,
|
||||
|
||||
keyFiltersControl: UntypedFormControl;
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
private propagateChange = null;
|
||||
|
||||
private valueChangeSubscription: Subscription = null;
|
||||
|
||||
constructor(private fb: UntypedFormBuilder,
|
||||
private dialog: MatDialog) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.keyFilterListFormGroup = this.fb.group({});
|
||||
this.keyFilterListFormGroup.addControl('keyFilters',
|
||||
this.fb.array([]));
|
||||
this.keyFilterListFormGroup = this.fb.group({
|
||||
keyFilters: this.fb.array([])
|
||||
});
|
||||
this.keyFiltersControl = this.fb.control(null);
|
||||
|
||||
this.keyFilterListFormGroup.valueChanges.pipe(
|
||||
takeUntil(this.destroy$)
|
||||
).subscribe(() => this.updateModel());
|
||||
}
|
||||
|
||||
keyFiltersFormArray(): UntypedFormArray {
|
||||
ngOnDestroy() {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
get keyFiltersFormArray(): UntypedFormArray {
|
||||
return this.keyFilterListFormGroup.get('keyFilters') as UntypedFormArray;
|
||||
}
|
||||
|
||||
@ -119,23 +128,21 @@ export class KeyFilterListComponent implements ControlValueAccessor, Validator,
|
||||
}
|
||||
|
||||
writeValue(keyFilters: Array<KeyFilterInfo>): void {
|
||||
if (this.valueChangeSubscription) {
|
||||
this.valueChangeSubscription.unsubscribe();
|
||||
}
|
||||
const keyFilterControls: Array<AbstractControl> = [];
|
||||
if (keyFilters) {
|
||||
for (const keyFilter of keyFilters) {
|
||||
keyFilterControls.push(this.fb.control(keyFilter, [Validators.required]));
|
||||
}
|
||||
}
|
||||
this.keyFilterListFormGroup.setControl('keyFilters', this.fb.array(keyFilterControls));
|
||||
this.valueChangeSubscription = this.keyFilterListFormGroup.valueChanges.subscribe(() => {
|
||||
this.updateModel();
|
||||
});
|
||||
if (this.disabled) {
|
||||
this.keyFilterListFormGroup.disable({emitEvent: false});
|
||||
if (keyFilters.length === this.keyFiltersFormArray.length) {
|
||||
this.keyFiltersFormArray.patchValue(keyFilters, {emitEvent: false});
|
||||
} else {
|
||||
this.keyFilterListFormGroup.enable({emitEvent: false});
|
||||
const keyFilterControls: Array<AbstractControl> = [];
|
||||
if (keyFilters) {
|
||||
for (const keyFilter of keyFilters) {
|
||||
keyFilterControls.push(this.fb.control(keyFilter, [Validators.required]));
|
||||
}
|
||||
}
|
||||
this.keyFilterListFormGroup.setControl('keyFilters', this.fb.array(keyFilterControls), {emitEvent: false});
|
||||
if (this.disabled) {
|
||||
this.keyFilterListFormGroup.disable({emitEvent: false});
|
||||
} else {
|
||||
this.keyFilterListFormGroup.enable({emitEvent: false});
|
||||
}
|
||||
}
|
||||
const keyFiltersArray = keyFilterInfosToKeyFilters(keyFilters);
|
||||
this.keyFiltersControl.patchValue(keyFiltersArray, {emitEvent: false});
|
||||
|
||||
@ -15,124 +15,128 @@
|
||||
limitations under the License.
|
||||
|
||||
-->
|
||||
<div>
|
||||
<mat-toolbar color="primary">
|
||||
<h2 translate>device-profile.add</h2>
|
||||
<span fxFlex></span>
|
||||
<div [tb-help]="'deviceProfiles'"></div>
|
||||
<button mat-icon-button
|
||||
(click)="cancel()"
|
||||
type="button">
|
||||
<mat-icon class="material-icons">close</mat-icon>
|
||||
</button>
|
||||
</mat-toolbar>
|
||||
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
|
||||
</mat-progress-bar>
|
||||
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
|
||||
<div mat-dialog-content>
|
||||
<mat-horizontal-stepper [linear]="true" #addDeviceProfileStepper (selectionChange)="changeStep($event)">
|
||||
<mat-step [stepControl]="deviceProfileDetailsFormGroup">
|
||||
<form [formGroup]="deviceProfileDetailsFormGroup" style="padding-bottom: 16px;">
|
||||
<ng-template matStepLabel>{{ 'device-profile.device-profile-details' | translate }}</ng-template>
|
||||
<fieldset [disabled]="isLoading$ | async">
|
||||
<mat-form-field class="mat-block">
|
||||
<mat-label translate>device-profile.name</mat-label>
|
||||
<input matInput formControlName="name" required/>
|
||||
<mat-error *ngIf="deviceProfileDetailsFormGroup.get('name').hasError('required')">
|
||||
{{ 'device-profile.name-required' | translate }}
|
||||
</mat-error>
|
||||
<mat-error *ngIf="deviceProfileDetailsFormGroup.get('name').hasError('maxlength')">
|
||||
{{ 'device-profile.name-max-length' | translate }}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
<tb-rule-chain-autocomplete
|
||||
labelText="device-profile.default-rule-chain"
|
||||
formControlName="defaultRuleChainId">
|
||||
</tb-rule-chain-autocomplete>
|
||||
<tb-dashboard-autocomplete
|
||||
label="{{'device-profile.mobile-dashboard' | translate}}"
|
||||
formControlName="defaultDashboardId">
|
||||
<span tb-hint>{{'device-profile.mobile-dashboard-hint' | translate}}</span>
|
||||
</tb-dashboard-autocomplete>
|
||||
<tb-queue-autocomplete
|
||||
[queueType]="serviceType"
|
||||
formControlName="defaultQueueName">
|
||||
</tb-queue-autocomplete>
|
||||
<tb-rule-chain-autocomplete
|
||||
labelText="device-profile.default-edge-rule-chain"
|
||||
formControlName="defaultEdgeRuleChainId"
|
||||
[ruleChainType]="edgeRuleChainType">
|
||||
<span tb-hint>{{'device-profile.default-edge-rule-chain-hint' | translate}}</span>
|
||||
</tb-rule-chain-autocomplete>
|
||||
<mat-form-field fxHide class="mat-block">
|
||||
<mat-label translate>device-profile.type</mat-label>
|
||||
<mat-select formControlName="type" required>
|
||||
<mat-option *ngFor="let type of deviceProfileTypes" [value]="type">
|
||||
{{deviceProfileTypeTranslations.get(type) | translate}}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
<mat-error *ngIf="deviceProfileDetailsFormGroup.get('type').hasError('required')">
|
||||
{{ 'device-profile.type-required' | translate }}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
<tb-image-input fxFlex
|
||||
label="{{'device-profile.image' | translate}}"
|
||||
maxSizeByte="524288"
|
||||
formControlName="image">
|
||||
</tb-image-input>
|
||||
<mat-form-field class="mat-block">
|
||||
<mat-label translate>device-profile.description</mat-label>
|
||||
<textarea matInput formControlName="description" rows="2"></textarea>
|
||||
</mat-form-field>
|
||||
</fieldset>
|
||||
</form>
|
||||
</mat-step>
|
||||
<mat-step [stepControl]="transportConfigFormGroup" [optional]="true">
|
||||
<form [formGroup]="transportConfigFormGroup" style="padding-bottom: 16px;">
|
||||
<ng-template matStepLabel>{{ 'device-profile.transport-configuration' | translate }}</ng-template>
|
||||
<mat-toolbar color="primary">
|
||||
<h2 translate>device-profile.add</h2>
|
||||
<span fxFlex></span>
|
||||
<div [tb-help]="'deviceProfiles'"></div>
|
||||
<button mat-icon-button
|
||||
(click)="cancel()"
|
||||
type="button">
|
||||
<mat-icon class="material-icons">close</mat-icon>
|
||||
</button>
|
||||
</mat-toolbar>
|
||||
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
|
||||
</mat-progress-bar>
|
||||
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
|
||||
<div mat-dialog-content>
|
||||
<mat-horizontal-stepper [linear]="true"
|
||||
#addDeviceProfileStepper
|
||||
[labelPosition]="(stepperLabelPosition | async)"
|
||||
[orientation]="(stepperOrientation | async)"
|
||||
(selectionChange)="changeStep($event)">
|
||||
<mat-step [stepControl]="deviceProfileDetailsFormGroup">
|
||||
<form [formGroup]="deviceProfileDetailsFormGroup">
|
||||
<ng-template matStepLabel>{{ 'device-profile.device-profile-details' | translate }}</ng-template>
|
||||
<fieldset [disabled]="isLoading$ | async">
|
||||
<mat-form-field class="mat-block">
|
||||
<mat-label translate>device-profile.transport-type</mat-label>
|
||||
<mat-select formControlName="transportType" required>
|
||||
<mat-option *ngFor="let type of deviceTransportTypes" [value]="type">
|
||||
{{deviceTransportTypeTranslations.get(type) | translate}}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
<mat-hint *ngIf="transportConfigFormGroup.get('transportType').value">
|
||||
{{deviceTransportTypeHints.get(transportConfigFormGroup.get('transportType').value) | translate}}
|
||||
</mat-hint>
|
||||
<mat-error *ngIf="transportConfigFormGroup.get('transportType').hasError('required')">
|
||||
{{ 'device-profile.transport-type-required' | translate }}
|
||||
<mat-label translate>device-profile.name</mat-label>
|
||||
<input matInput formControlName="name" required/>
|
||||
<mat-error *ngIf="deviceProfileDetailsFormGroup.get('name').hasError('required')">
|
||||
{{ 'device-profile.name-required' | translate }}
|
||||
</mat-error>
|
||||
<mat-error *ngIf="deviceProfileDetailsFormGroup.get('name').hasError('maxlength')">
|
||||
{{ 'device-profile.name-max-length' | translate }}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
<tb-device-profile-transport-configuration
|
||||
formControlName="transportConfiguration"
|
||||
isAdd="true"
|
||||
required>
|
||||
</tb-device-profile-transport-configuration>
|
||||
</form>
|
||||
</mat-step>
|
||||
<mat-step [stepControl]="alarmRulesFormGroup" [optional]="true">
|
||||
<form [formGroup]="alarmRulesFormGroup" style="padding-bottom: 16px;">
|
||||
<ng-template matStepLabel>{{'device-profile.alarm-rules-with-count' | translate:
|
||||
{count: alarmRulesFormGroup.get('alarms').value ?
|
||||
alarmRulesFormGroup.get('alarms').value.length : 0} }}</ng-template>
|
||||
<tb-device-profile-alarms
|
||||
formControlName="alarms"
|
||||
[deviceProfileId]="null">
|
||||
</tb-device-profile-alarms>
|
||||
</form>
|
||||
</mat-step>
|
||||
<mat-step [stepControl]="provisionConfigFormGroup" [optional]="true">
|
||||
<form [formGroup]="provisionConfigFormGroup" style="padding-bottom: 16px;">
|
||||
<ng-template matStepLabel>{{ 'device-profile.device-provisioning' | translate }}</ng-template>
|
||||
<tb-device-profile-provision-configuration
|
||||
formControlName="provisionConfiguration">
|
||||
</tb-device-profile-provision-configuration>
|
||||
</form>
|
||||
</mat-step>
|
||||
</mat-horizontal-stepper>
|
||||
</div>
|
||||
<div mat-dialog-actions fxLayout="row">
|
||||
<tb-rule-chain-autocomplete
|
||||
labelText="device-profile.default-rule-chain"
|
||||
formControlName="defaultRuleChainId">
|
||||
</tb-rule-chain-autocomplete>
|
||||
<tb-dashboard-autocomplete
|
||||
label="{{'device-profile.mobile-dashboard' | translate}}"
|
||||
formControlName="defaultDashboardId">
|
||||
<span tb-hint>{{'device-profile.mobile-dashboard-hint' | translate}}</span>
|
||||
</tb-dashboard-autocomplete>
|
||||
<tb-queue-autocomplete
|
||||
[queueType]="serviceType"
|
||||
formControlName="defaultQueueName">
|
||||
</tb-queue-autocomplete>
|
||||
<tb-rule-chain-autocomplete
|
||||
labelText="device-profile.default-edge-rule-chain"
|
||||
formControlName="defaultEdgeRuleChainId"
|
||||
[ruleChainType]="edgeRuleChainType">
|
||||
<span tb-hint>{{'device-profile.default-edge-rule-chain-hint' | translate}}</span>
|
||||
</tb-rule-chain-autocomplete>
|
||||
<mat-form-field fxHide class="mat-block">
|
||||
<mat-label translate>device-profile.type</mat-label>
|
||||
<mat-select formControlName="type" required>
|
||||
<mat-option *ngFor="let type of deviceProfileTypes" [value]="type">
|
||||
{{deviceProfileTypeTranslations.get(type) | translate}}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
<mat-error *ngIf="deviceProfileDetailsFormGroup.get('type').hasError('required')">
|
||||
{{ 'device-profile.type-required' | translate }}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
<tb-image-input fxFlex
|
||||
label="{{'device-profile.image' | translate}}"
|
||||
maxSizeByte="524288"
|
||||
formControlName="image">
|
||||
</tb-image-input>
|
||||
<mat-form-field class="mat-block">
|
||||
<mat-label translate>device-profile.description</mat-label>
|
||||
<textarea matInput formControlName="description" rows="2"></textarea>
|
||||
</mat-form-field>
|
||||
</fieldset>
|
||||
</form>
|
||||
</mat-step>
|
||||
<mat-step [stepControl]="transportConfigFormGroup" [optional]="true">
|
||||
<form [formGroup]="transportConfigFormGroup" style="padding-bottom: 16px;">
|
||||
<ng-template matStepLabel>{{ 'device-profile.transport-configuration' | translate }}</ng-template>
|
||||
<mat-form-field class="mat-block">
|
||||
<mat-label translate>device-profile.transport-type</mat-label>
|
||||
<mat-select formControlName="transportType" required>
|
||||
<mat-option *ngFor="let type of deviceTransportTypes" [value]="type">
|
||||
{{deviceTransportTypeTranslations.get(type) | translate}}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
<mat-hint *ngIf="transportConfigFormGroup.get('transportType').value">
|
||||
{{deviceTransportTypeHints.get(transportConfigFormGroup.get('transportType').value) | translate}}
|
||||
</mat-hint>
|
||||
<mat-error *ngIf="transportConfigFormGroup.get('transportType').hasError('required')">
|
||||
{{ 'device-profile.transport-type-required' | translate }}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
<tb-device-profile-transport-configuration
|
||||
formControlName="transportConfiguration"
|
||||
isAdd="true"
|
||||
required>
|
||||
</tb-device-profile-transport-configuration>
|
||||
</form>
|
||||
</mat-step>
|
||||
<mat-step [stepControl]="alarmRulesFormGroup" [optional]="true">
|
||||
<form [formGroup]="alarmRulesFormGroup" style="padding-bottom: 16px;">
|
||||
<ng-template matStepLabel>{{'device-profile.alarm-rules-with-count' | translate:
|
||||
{count: alarmRulesFormGroup.get('alarms').value ?
|
||||
alarmRulesFormGroup.get('alarms').value.length : 0} }}</ng-template>
|
||||
<tb-device-profile-alarms
|
||||
formControlName="alarms"
|
||||
[deviceProfileId]="null">
|
||||
</tb-device-profile-alarms>
|
||||
</form>
|
||||
</mat-step>
|
||||
<mat-step [stepControl]="provisionConfigFormGroup" [optional]="true">
|
||||
<form [formGroup]="provisionConfigFormGroup" style="padding-bottom: 16px;">
|
||||
<ng-template matStepLabel>{{ 'device-profile.device-provisioning' | translate }}</ng-template>
|
||||
<tb-device-profile-provision-configuration
|
||||
formControlName="provisionConfiguration">
|
||||
</tb-device-profile-provision-configuration>
|
||||
</form>
|
||||
</mat-step>
|
||||
</mat-horizontal-stepper>
|
||||
</div>
|
||||
<div mat-dialog-actions style="padding: 0">
|
||||
<div class="dialog-actions-row" fxFlex fxLayout="row" fxLayoutAlign="end center">
|
||||
<button mat-stroked-button *ngIf="selectedIndex > 0"
|
||||
[disabled]="(isLoading$ | async)"
|
||||
(click)="previousStep()">{{ 'action.back' | translate }}</button>
|
||||
@ -143,8 +147,8 @@
|
||||
[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">
|
||||
<mat-divider style="width: 100%"></mat-divider>
|
||||
<div class="dialog-actions-row" fxFlex fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="end center">
|
||||
<button mat-button
|
||||
[disabled]="(isLoading$ | async)"
|
||||
(click)="cancel()">{{ 'action.cancel' | translate }}</button>
|
||||
|
||||
@ -15,6 +15,15 @@
|
||||
*/
|
||||
@import "../../../../../scss/constants";
|
||||
|
||||
:host {
|
||||
height: 100%;
|
||||
display: grid;
|
||||
|
||||
.dialog-actions-row {
|
||||
padding: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
:host-context(.tb-fullscreen-dialog .mat-mdc-dialog-container) {
|
||||
@media #{$mat-lt-sm} {
|
||||
.mat-mdc-dialog-content {
|
||||
@ -32,31 +41,21 @@
|
||||
|
||||
.mat-stepper-horizontal {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
@media #{$mat-lt-sm} {
|
||||
.mat-step-label {
|
||||
white-space: normal;
|
||||
overflow: visible;
|
||||
.mat-step-text-label {
|
||||
overflow: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.mat-horizontal-stepper-wrapper {
|
||||
flex: 1 1 100%;
|
||||
}
|
||||
|
||||
.mat-horizontal-content-container {
|
||||
height: 530px;
|
||||
height: 680px;
|
||||
max-height: 100%;
|
||||
width: 100%;;
|
||||
overflow-y: auto;
|
||||
scrollbar-gutter: stable;
|
||||
@media #{$mat-gt-sm} {
|
||||
min-width: 800px;
|
||||
}
|
||||
}
|
||||
.mat-horizontal-stepper-content[aria-expanded=true] {
|
||||
height: 100%;
|
||||
form {
|
||||
height: 100%;
|
||||
min-width: 500px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,13 +44,17 @@ import {
|
||||
} from '@shared/models/device.models';
|
||||
import { DeviceProfileService } from '@core/http/device-profile.service';
|
||||
import { EntityType } from '@shared/models/entity-type.models';
|
||||
import { MatStepper } from '@angular/material/stepper';
|
||||
import { MatStepper, StepperOrientation } from '@angular/material/stepper';
|
||||
import { RuleChainId } from '@shared/models/id/rule-chain-id';
|
||||
import { StepperSelectionEvent } from '@angular/cdk/stepper';
|
||||
import { deepTrim } from '@core/utils';
|
||||
import { ServiceType } from '@shared/models/queue.models';
|
||||
import { DashboardId } from '@shared/models/id/dashboard-id';
|
||||
import { RuleChainType } from '@shared/models/rule-chain.models';
|
||||
import { Observable } from 'rxjs';
|
||||
import { BreakpointObserver } from '@angular/cdk/layout';
|
||||
import { MediaBreakpoints } from '@shared/models/constants';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
export interface AddDeviceProfileDialogData {
|
||||
deviceProfileName: string;
|
||||
@ -67,7 +71,8 @@ export class AddDeviceProfileDialogComponent extends
|
||||
DialogComponent<AddDeviceProfileDialogComponent, DeviceProfile> implements AfterViewInit {
|
||||
|
||||
@ViewChild('addDeviceProfileStepper', {static: true}) addDeviceProfileStepper: MatStepper;
|
||||
|
||||
stepperOrientation: Observable<StepperOrientation>;
|
||||
stepperLabelPosition: Observable<'bottom' | 'end'>;
|
||||
selectedIndex = 0;
|
||||
|
||||
showNext = true;
|
||||
@ -102,10 +107,17 @@ export class AddDeviceProfileDialogComponent extends
|
||||
public dialogRef: MatDialogRef<AddDeviceProfileDialogComponent, DeviceProfile>,
|
||||
private componentFactoryResolver: ComponentFactoryResolver,
|
||||
private injector: Injector,
|
||||
private breakpointObserver: BreakpointObserver,
|
||||
@SkipSelf() private errorStateMatcher: ErrorStateMatcher,
|
||||
private deviceProfileService: DeviceProfileService,
|
||||
private fb: UntypedFormBuilder) {
|
||||
super(store, router, dialogRef);
|
||||
this.stepperOrientation = this.breakpointObserver.observe(MediaBreakpoints['gt-sm'])
|
||||
.pipe(map(({matches}) => matches ? 'horizontal' : 'vertical'));
|
||||
|
||||
this.stepperLabelPosition = this.breakpointObserver.observe(MediaBreakpoints['gt-md'])
|
||||
.pipe(map(({matches}) => matches ? 'end' : 'bottom'));
|
||||
|
||||
this.deviceProfileDetailsFormGroup = this.fb.group(
|
||||
{
|
||||
name: [data.deviceProfileName, [Validators.required, Validators.maxLength(255)]],
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
/// limitations under the License.
|
||||
///
|
||||
|
||||
import { Component, forwardRef, Input, OnInit } from '@angular/core';
|
||||
import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core';
|
||||
import {
|
||||
AbstractControl,
|
||||
ControlValueAccessor,
|
||||
@ -23,15 +23,16 @@ import {
|
||||
UntypedFormControl,
|
||||
UntypedFormGroup,
|
||||
NG_VALIDATORS,
|
||||
NG_VALUE_ACCESSOR, ValidationErrors,
|
||||
NG_VALUE_ACCESSOR,
|
||||
Validator,
|
||||
Validators
|
||||
} from '@angular/forms';
|
||||
import { DeviceProfileAlarmRule, alarmRuleValidator } from '@shared/models/device.models';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { Subject } from 'rxjs';
|
||||
import { AlarmSeverity, alarmSeverityTranslations } from '@shared/models/alarm.models';
|
||||
import { EntityId } from '@shared/models/id/entity-id';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'tb-create-alarm-rules',
|
||||
@ -50,7 +51,7 @@ import { EntityId } from '@shared/models/id/entity-id';
|
||||
}
|
||||
]
|
||||
})
|
||||
export class CreateAlarmRulesComponent implements ControlValueAccessor, OnInit, Validator {
|
||||
export class CreateAlarmRulesComponent implements ControlValueAccessor, OnInit, Validator, OnDestroy {
|
||||
|
||||
alarmSeverities = Object.keys(AlarmSeverity);
|
||||
alarmSeverityEnum = AlarmSeverity;
|
||||
@ -67,8 +68,7 @@ export class CreateAlarmRulesComponent implements ControlValueAccessor, OnInit,
|
||||
|
||||
private usedSeverities: AlarmSeverity[] = [];
|
||||
|
||||
private valueChangeSubscription: Subscription = null;
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
private propagateChange = (v: any) => { };
|
||||
|
||||
constructor(private dialog: MatDialog,
|
||||
@ -86,6 +86,14 @@ export class CreateAlarmRulesComponent implements ControlValueAccessor, OnInit,
|
||||
this.createAlarmRulesFormGroup = this.fb.group({
|
||||
createAlarmRules: this.fb.array([])
|
||||
});
|
||||
this.createAlarmRulesFormGroup.valueChanges.pipe(
|
||||
takeUntil(this.destroy$)
|
||||
).subscribe(() => this.updateModel());
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
createAlarmRulesFormArray(): UntypedFormArray {
|
||||
@ -102,9 +110,6 @@ export class CreateAlarmRulesComponent implements ControlValueAccessor, OnInit,
|
||||
}
|
||||
|
||||
writeValue(createAlarmRules: {[severity: string]: DeviceProfileAlarmRule}): void {
|
||||
if (this.valueChangeSubscription) {
|
||||
this.valueChangeSubscription.unsubscribe();
|
||||
}
|
||||
const createAlarmRulesControls: Array<AbstractControl> = [];
|
||||
if (createAlarmRules) {
|
||||
Object.keys(createAlarmRules).forEach((severity) => {
|
||||
@ -118,15 +123,12 @@ export class CreateAlarmRulesComponent implements ControlValueAccessor, OnInit,
|
||||
}));
|
||||
});
|
||||
}
|
||||
this.createAlarmRulesFormGroup.setControl('createAlarmRules', this.fb.array(createAlarmRulesControls));
|
||||
this.createAlarmRulesFormGroup.setControl('createAlarmRules', this.fb.array(createAlarmRulesControls), {emitEvent: false});
|
||||
if (this.disabled) {
|
||||
this.createAlarmRulesFormGroup.disable({emitEvent: false});
|
||||
} else {
|
||||
this.createAlarmRulesFormGroup.enable({emitEvent: false});
|
||||
}
|
||||
this.valueChangeSubscription = this.createAlarmRulesFormGroup.valueChanges.subscribe(() => {
|
||||
this.updateModel();
|
||||
});
|
||||
this.updateUsedSeverities();
|
||||
if (!this.disabled && !this.createAlarmRulesFormGroup.valid) {
|
||||
this.updateModel();
|
||||
|
||||
@ -59,10 +59,10 @@
|
||||
</mat-panel-title>
|
||||
</mat-expansion-panel-header>
|
||||
<ng-template matExpansionPanelContent>
|
||||
<mat-checkbox formControlName="propagate" style="display: block; padding-bottom: 16px;">
|
||||
<mat-checkbox formControlName="propagate" style="display: block;">
|
||||
{{ 'device-profile.propagate-alarm' | translate }}
|
||||
</mat-checkbox>
|
||||
<section *ngIf="alarmFormGroup.get('propagate').value === true" style="padding-bottom: 1em;">
|
||||
<section *ngIf="alarmFormGroup.get('propagate').value === true">
|
||||
<mat-form-field floatLabel="always" class="mat-block">
|
||||
<mat-label translate>device-profile.alarm-rule-relation-types-list</mat-label>
|
||||
<mat-chip-grid #relationTypesChipList [disabled]="disabled">
|
||||
@ -82,10 +82,10 @@
|
||||
<mat-hint innerHTML="{{ 'device-profile.alarm-rule-relation-types-list-hint' | translate }}"></mat-hint>
|
||||
</mat-form-field>
|
||||
</section>
|
||||
<mat-checkbox formControlName="propagateToOwner" style="display: block; padding-bottom: 16px;">
|
||||
<mat-checkbox formControlName="propagateToOwner" style="display: block;">
|
||||
{{ 'device-profile.propagate-alarm-to-owner' | translate }}
|
||||
</mat-checkbox>
|
||||
<mat-checkbox formControlName="propagateToTenant" style="display: block; padding-bottom: 16px;">
|
||||
<mat-checkbox formControlName="propagateToTenant" style="display: block;">
|
||||
{{ 'device-profile.propagate-alarm-to-tenant' | translate }}
|
||||
</mat-checkbox>
|
||||
</ng-template>
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
-->
|
||||
<div fxLayout="column">
|
||||
<div class="tb-device-profile-alarms">
|
||||
<div *ngFor="let alarmControl of alarmsFormArray().controls; trackBy: trackByAlarm;
|
||||
<div *ngFor="let alarmControl of alarmsFormArray.controls; trackBy: trackByAlarm;
|
||||
let $index = index; last as isLast;"
|
||||
fxLayout="column" [ngStyle]="!isLast ? {paddingBottom: '8px'} : {}">
|
||||
<tb-device-profile-alarm [formControl]="alarmControl"
|
||||
@ -27,7 +27,7 @@
|
||||
</tb-device-profile-alarm>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="!alarmsFormArray().controls.length">
|
||||
<div *ngIf="!alarmsFormArray.controls.length">
|
||||
<span translate fxLayoutAlign="center center"
|
||||
class="tb-prompt">device-profile.no-alarm-rules</span>
|
||||
</div>
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
/// limitations under the License.
|
||||
///
|
||||
|
||||
import { Component, forwardRef, Input, OnInit } from '@angular/core';
|
||||
import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core';
|
||||
import {
|
||||
AbstractControl,
|
||||
ControlValueAccessor,
|
||||
@ -32,9 +32,9 @@ import { AppState } from '@app/core/core.state';
|
||||
import { coerceBooleanProperty } from '@angular/cdk/coercion';
|
||||
import { DeviceProfileAlarm, deviceProfileAlarmValidator } from '@shared/models/device.models';
|
||||
import { guid } from '@core/utils';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { Subject } from 'rxjs';
|
||||
import { EntityId } from '@shared/models/id/entity-id';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'tb-device-profile-alarms',
|
||||
@ -53,7 +53,7 @@ import { EntityId } from '@shared/models/id/entity-id';
|
||||
}
|
||||
]
|
||||
})
|
||||
export class DeviceProfileAlarmsComponent implements ControlValueAccessor, OnInit, Validator {
|
||||
export class DeviceProfileAlarmsComponent implements ControlValueAccessor, OnInit, Validator, OnDestroy {
|
||||
|
||||
deviceProfileAlarmsFormGroup: UntypedFormGroup;
|
||||
|
||||
@ -72,13 +72,11 @@ export class DeviceProfileAlarmsComponent implements ControlValueAccessor, OnIni
|
||||
@Input()
|
||||
deviceProfileId: EntityId;
|
||||
|
||||
private valueChangeSubscription: Subscription = null;
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
private propagateChange = (v: any) => { };
|
||||
|
||||
constructor(private store: Store<AppState>,
|
||||
private fb: UntypedFormBuilder,
|
||||
private dialog: MatDialog) {
|
||||
private fb: UntypedFormBuilder) {
|
||||
}
|
||||
|
||||
registerOnChange(fn: any): void {
|
||||
@ -92,9 +90,17 @@ export class DeviceProfileAlarmsComponent implements ControlValueAccessor, OnIni
|
||||
this.deviceProfileAlarmsFormGroup = this.fb.group({
|
||||
alarms: this.fb.array([])
|
||||
});
|
||||
this.deviceProfileAlarmsFormGroup.valueChanges.pipe(
|
||||
takeUntil(this.destroy$)
|
||||
).subscribe(() => this.updateModel());
|
||||
}
|
||||
|
||||
alarmsFormArray(): UntypedFormArray {
|
||||
ngOnDestroy() {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
get alarmsFormArray(): UntypedFormArray {
|
||||
return this.deviceProfileAlarmsFormGroup.get('alarms') as UntypedFormArray;
|
||||
}
|
||||
|
||||
@ -108,24 +114,22 @@ export class DeviceProfileAlarmsComponent implements ControlValueAccessor, OnIni
|
||||
}
|
||||
|
||||
writeValue(alarms: Array<DeviceProfileAlarm> | null): void {
|
||||
if (this.valueChangeSubscription) {
|
||||
this.valueChangeSubscription.unsubscribe();
|
||||
}
|
||||
const alarmsControls: Array<AbstractControl> = [];
|
||||
if (alarms) {
|
||||
alarms.forEach((alarm) => {
|
||||
alarmsControls.push(this.fb.control(alarm, [Validators.required]));
|
||||
});
|
||||
}
|
||||
this.deviceProfileAlarmsFormGroup.setControl('alarms', this.fb.array(alarmsControls));
|
||||
if (this.disabled) {
|
||||
this.deviceProfileAlarmsFormGroup.disable({emitEvent: false});
|
||||
if (alarms?.length === this.alarmsFormArray.length) {
|
||||
this.alarmsFormArray.patchValue(alarms, {emitEvent: false});
|
||||
} else {
|
||||
this.deviceProfileAlarmsFormGroup.enable({emitEvent: false});
|
||||
const alarmsControls: Array<AbstractControl> = [];
|
||||
if (alarms) {
|
||||
alarms.forEach((alarm) => {
|
||||
alarmsControls.push(this.fb.control(alarm, [Validators.required]));
|
||||
});
|
||||
}
|
||||
this.deviceProfileAlarmsFormGroup.setControl('alarms', this.fb.array(alarmsControls), {emitEvent: false});
|
||||
if (this.disabled) {
|
||||
this.deviceProfileAlarmsFormGroup.disable({emitEvent: false});
|
||||
} else {
|
||||
this.deviceProfileAlarmsFormGroup.enable({emitEvent: false});
|
||||
}
|
||||
}
|
||||
this.valueChangeSubscription = this.deviceProfileAlarmsFormGroup.valueChanges.subscribe(() => {
|
||||
this.updateModel();
|
||||
});
|
||||
}
|
||||
|
||||
public trackByAlarm(index: number, alarmControl: AbstractControl): string {
|
||||
|
||||
@ -26,7 +26,7 @@ import {
|
||||
Validator,
|
||||
Validators
|
||||
} from '@angular/forms';
|
||||
import { Subject, Subscription } from 'rxjs';
|
||||
import { Subject } from 'rxjs';
|
||||
import {
|
||||
AttributeName,
|
||||
AttributeNameTranslationMap,
|
||||
@ -70,7 +70,6 @@ export class Lwm2mAttributesKeyListComponent extends PageComponent implements Co
|
||||
attributesValueFormGroup: UntypedFormGroup;
|
||||
|
||||
private propagateChange = null;
|
||||
private valueChange$: Subscription = null;
|
||||
private destroy$ = new Subject<void>();
|
||||
private usedAttributesName: AttributeName[] = [];
|
||||
|
||||
@ -80,6 +79,9 @@ export class Lwm2mAttributesKeyListComponent extends PageComponent implements Co
|
||||
this.attributesValueFormGroup = this.fb.group({
|
||||
attributesValue: this.fb.array([])
|
||||
});
|
||||
this.attributesValueFormGroup.valueChanges.pipe(
|
||||
takeUntil(this.destroy$)
|
||||
).subscribe(() => this.updateModel());
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
@ -92,9 +94,6 @@ export class Lwm2mAttributesKeyListComponent extends PageComponent implements Co
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (this.valueChange$) {
|
||||
this.valueChange$.unsubscribe();
|
||||
}
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
@ -116,24 +115,18 @@ export class Lwm2mAttributesKeyListComponent extends PageComponent implements Co
|
||||
}
|
||||
|
||||
writeValue(keyValMap: AttributesNameValueMap): void {
|
||||
if (this.valueChange$) {
|
||||
this.valueChange$.unsubscribe();
|
||||
}
|
||||
const attributesValueControls: Array<AbstractControl> = [];
|
||||
if (keyValMap) {
|
||||
(Object.keys(keyValMap) as AttributeName[]).forEach(name => {
|
||||
attributesValueControls.push(this.createdFormGroup({name, value: keyValMap[name]}));
|
||||
});
|
||||
}
|
||||
this.attributesValueFormGroup.setControl('attributesValue', this.fb.array(attributesValueControls));
|
||||
this.attributesValueFormGroup.setControl('attributesValue', this.fb.array(attributesValueControls), {emitEvent: false});
|
||||
if (this.disabled) {
|
||||
this.attributesValueFormGroup.disable({emitEvent: false});
|
||||
} else {
|
||||
this.attributesValueFormGroup.enable({emitEvent: false});
|
||||
}
|
||||
this.valueChange$ = this.attributesValueFormGroup.valueChanges.subscribe(() => {
|
||||
this.updateModel();
|
||||
});
|
||||
this.updateUsedAttributesName();
|
||||
}
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
-->
|
||||
<div fxLayout="column">
|
||||
<mat-accordion multi="true">
|
||||
<div *ngFor="let serverConfig of serverConfigsFromArray().controls; trackBy: trackByParams; let $index = index;">
|
||||
<div *ngFor="let serverConfig of serverConfigsFromArray.controls; trackBy: trackByParams; let $index = index;">
|
||||
<tb-profile-lwm2m-device-config-server
|
||||
[formControl]="serverConfig"
|
||||
(removeServer)="removeServerConfig($event, $index)"
|
||||
@ -25,7 +25,7 @@
|
||||
</tb-profile-lwm2m-device-config-server>
|
||||
</div>
|
||||
</mat-accordion>
|
||||
<div *ngIf="!serverConfigsFromArray().controls.length" style="margin:32px 0">
|
||||
<div *ngIf="!serverConfigsFromArray.controls.length" style="margin:32px 0">
|
||||
<span translate fxLayoutAlign="center center"
|
||||
class="tb-prompt">device-profile.lwm2m.no-config-servers</span>
|
||||
</div>
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
/// limitations under the License.
|
||||
///
|
||||
|
||||
import { Component, EventEmitter, forwardRef, Input, OnInit, Output } from '@angular/core';
|
||||
import { Component, EventEmitter, forwardRef, Input, OnDestroy, OnInit, Output } from '@angular/core';
|
||||
import {
|
||||
AbstractControl,
|
||||
ControlValueAccessor,
|
||||
@ -24,13 +24,13 @@ import {
|
||||
NG_VALIDATORS,
|
||||
NG_VALUE_ACCESSOR
|
||||
} from '@angular/forms';
|
||||
import { of, Subscription } from 'rxjs';
|
||||
import { of, Subject } from 'rxjs';
|
||||
import { ServerSecurityConfig } from '@home/components/profile/device/lwm2m/lwm2m-profile-config.models';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { DialogService } from '@core/services/dialog.service';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { Lwm2mBootstrapAddConfigServerDialogComponent } from '@home/components/profile/device/lwm2m/lwm2m-bootstrap-add-config-server-dialog.component';
|
||||
import { mergeMap } from 'rxjs/operators';
|
||||
import { mergeMap, takeUntil } from 'rxjs/operators';
|
||||
import { DeviceProfileService } from '@core/http/device-profile.service';
|
||||
import { Lwm2mSecurityType } from '@shared/models/lwm2m-security-config.models';
|
||||
|
||||
@ -50,7 +50,7 @@ import { Lwm2mSecurityType } from '@shared/models/lwm2m-security-config.models';
|
||||
}
|
||||
]
|
||||
})
|
||||
export class Lwm2mBootstrapConfigServersComponent implements OnInit, ControlValueAccessor {
|
||||
export class Lwm2mBootstrapConfigServersComponent implements OnInit, ControlValueAccessor, OnDestroy {
|
||||
|
||||
bootstrapConfigServersFormGroup: UntypedFormGroup;
|
||||
|
||||
@ -72,8 +72,7 @@ export class Lwm2mBootstrapConfigServersComponent implements OnInit, ControlValu
|
||||
}
|
||||
}
|
||||
|
||||
private valueChangeSubscription: Subscription = null;
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
private propagateChange = (v: any) => { };
|
||||
|
||||
constructor(public translate: TranslateService,
|
||||
@ -94,9 +93,17 @@ export class Lwm2mBootstrapConfigServersComponent implements OnInit, ControlValu
|
||||
this.bootstrapConfigServersFormGroup = this.fb.group({
|
||||
serverConfigs: this.fb.array([])
|
||||
});
|
||||
this.bootstrapConfigServersFormGroup.valueChanges.pipe(
|
||||
takeUntil(this.destroy$)
|
||||
).subscribe(() => this.updateModel());
|
||||
}
|
||||
|
||||
serverConfigsFromArray(): UntypedFormArray {
|
||||
ngOnDestroy() {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
get serverConfigsFromArray(): UntypedFormArray {
|
||||
return this.bootstrapConfigServersFormGroup.get('serverConfigs') as UntypedFormArray;
|
||||
}
|
||||
|
||||
@ -110,24 +117,22 @@ export class Lwm2mBootstrapConfigServersComponent implements OnInit, ControlValu
|
||||
}
|
||||
|
||||
writeValue(serverConfigs: Array<ServerSecurityConfig> | null): void {
|
||||
if (this.valueChangeSubscription) {
|
||||
this.valueChangeSubscription.unsubscribe();
|
||||
}
|
||||
const serverConfigsControls: Array<AbstractControl> = [];
|
||||
if (serverConfigs) {
|
||||
serverConfigs.forEach((serverConfig) => {
|
||||
serverConfigsControls.push(this.fb.control(serverConfig));
|
||||
});
|
||||
}
|
||||
this.bootstrapConfigServersFormGroup.setControl('serverConfigs', this.fb.array(serverConfigsControls));
|
||||
if (this.disabled) {
|
||||
this.bootstrapConfigServersFormGroup.disable({emitEvent: false});
|
||||
if (serverConfigs?.length === this.serverConfigsFromArray.length) {
|
||||
this.serverConfigsFromArray.patchValue(serverConfigs, {emitEvent: false});
|
||||
} else {
|
||||
this.bootstrapConfigServersFormGroup.enable({emitEvent: false});
|
||||
const serverConfigsControls: Array<AbstractControl> = [];
|
||||
if (serverConfigs) {
|
||||
serverConfigs.forEach((serverConfig) => {
|
||||
serverConfigsControls.push(this.fb.control(serverConfig));
|
||||
});
|
||||
}
|
||||
this.bootstrapConfigServersFormGroup.setControl('serverConfigs', this.fb.array(serverConfigsControls), {emitEvent: false});
|
||||
if (this.disabled) {
|
||||
this.bootstrapConfigServersFormGroup.disable({emitEvent: false});
|
||||
} else {
|
||||
this.bootstrapConfigServersFormGroup.enable({emitEvent: false});
|
||||
}
|
||||
}
|
||||
this.valueChangeSubscription = this.bootstrapConfigServersFormGroup.valueChanges.subscribe(() => {
|
||||
this.updateModel();
|
||||
});
|
||||
}
|
||||
|
||||
trackByParams(index: number): number {
|
||||
@ -147,7 +152,7 @@ export class Lwm2mBootstrapConfigServersComponent implements OnInit, ControlValu
|
||||
true
|
||||
).subscribe((result) => {
|
||||
if (result) {
|
||||
this.serverConfigsFromArray().removeAt(index);
|
||||
this.serverConfigsFromArray.removeAt(index);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -169,7 +174,7 @@ export class Lwm2mBootstrapConfigServersComponent implements OnInit, ControlValu
|
||||
addServerConfigObs.subscribe((serverConfig) => {
|
||||
if (serverConfig) {
|
||||
serverConfig.securityMode = Lwm2mSecurityType.NO_SEC;
|
||||
this.serverConfigsFromArray().push(this.fb.control(serverConfig));
|
||||
this.serverConfigsFromArray.push(this.fb.control(serverConfig));
|
||||
this.updateModel();
|
||||
} else {
|
||||
this.isTransportWasRunWithBootstrap = false;
|
||||
@ -196,7 +201,7 @@ export class Lwm2mBootstrapConfigServersComponent implements OnInit, ControlValu
|
||||
}
|
||||
|
||||
private isBootstrapAdded(): boolean {
|
||||
const serverConfigsArray = this.serverConfigsFromArray().getRawValue();
|
||||
const serverConfigsArray = this.serverConfigsFromArray.getRawValue();
|
||||
for (let i = 0; i < serverConfigsArray.length; i++) {
|
||||
if (serverConfigsArray[i].bootstrapServerIs) {
|
||||
return true;
|
||||
@ -207,15 +212,15 @@ export class Lwm2mBootstrapConfigServersComponent implements OnInit, ControlValu
|
||||
|
||||
private removeBootstrapServerConfig(): void {
|
||||
if (this.bootstrapConfigServersFormGroup) {
|
||||
const bootstrapServerIndex = this.serverConfigsFromArray().getRawValue().findIndex(server => server.bootstrapServerIs === true);
|
||||
const bootstrapServerIndex = this.serverConfigsFromArray.getRawValue().findIndex(server => server.bootstrapServerIs === true);
|
||||
if (bootstrapServerIndex !== -1) {
|
||||
this.serverConfigsFromArray().removeAt(bootstrapServerIndex);
|
||||
this.serverConfigsFromArray.removeAt(bootstrapServerIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private updateModel() {
|
||||
const serverConfigs: Array<ServerSecurityConfig> = this.serverConfigsFromArray().value;
|
||||
const serverConfigs: Array<ServerSecurityConfig> = this.serverConfigsFromArray.value;
|
||||
this.propagateChange(serverConfigs);
|
||||
}
|
||||
}
|
||||
|
||||
@ -80,6 +80,8 @@ export class Lwm2mObserveAttrTelemetryInstancesComponent implements ControlValue
|
||||
this.instancesFormGroup = this.fb.group({
|
||||
instances: this.fb.array([])
|
||||
});
|
||||
|
||||
this.valueChange$ = this.instancesFormGroup.valueChanges.subscribe(value => this.updateModel(value.instances));
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
@ -122,22 +124,16 @@ export class Lwm2mObserveAttrTelemetryInstancesComponent implements ControlValue
|
||||
if (instances.length === this.instancesFormArray.length) {
|
||||
this.instancesFormArray.patchValue(instances, {emitEvent: false});
|
||||
} else {
|
||||
if (this.valueChange$) {
|
||||
this.valueChange$.unsubscribe();
|
||||
}
|
||||
const instancesControl: Array<AbstractControl> = [];
|
||||
if (instances) {
|
||||
instances.forEach((instance) => {
|
||||
instancesControl.push(this.createInstanceFormGroup(instance));
|
||||
});
|
||||
}
|
||||
this.instancesFormGroup.setControl('instances', this.fb.array(instancesControl));
|
||||
this.instancesFormGroup.setControl('instances', this.fb.array(instancesControl), {emitEvent: false});
|
||||
if (this.disabled) {
|
||||
this.instancesFormGroup.disable({emitEvent: false});
|
||||
}
|
||||
this.valueChange$ = this.instancesFormGroup.valueChanges.subscribe(value => {
|
||||
this.updateModel(value.instances);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -29,7 +29,7 @@ import {
|
||||
} from '@angular/forms';
|
||||
import { ResourceLwM2M } from '@home/components/profile/device/lwm2m/lwm2m-profile-config.models';
|
||||
import { coerceBooleanProperty } from '@angular/cdk/coercion';
|
||||
import { combineLatest, Subject, Subscription } from 'rxjs';
|
||||
import { combineLatest, Subject } from 'rxjs';
|
||||
import { startWith, takeUntil } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
@ -71,19 +71,19 @@ export class Lwm2mObserveAttrTelemetryResourcesComponent implements ControlValue
|
||||
}
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
private valueChange$: Subscription = null;
|
||||
private propagateChange = (v: any) => { };
|
||||
|
||||
constructor(private fb: UntypedFormBuilder) {
|
||||
this.resourcesFormGroup = this.fb.group({
|
||||
resources: this.fb.array([])
|
||||
});
|
||||
|
||||
this.resourcesFormGroup.valueChanges.pipe(
|
||||
takeUntil(this.destroy$)
|
||||
).subscribe(() => this.updateModel(this.resourcesFormGroup.getRawValue().resources));
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (this.valueChange$) {
|
||||
this.valueChange$.unsubscribe();
|
||||
}
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
@ -131,24 +131,18 @@ export class Lwm2mObserveAttrTelemetryResourcesComponent implements ControlValue
|
||||
|
||||
private updatedResources(resources: ResourceLwM2M[]): void {
|
||||
if (resources.length === this.resourcesFormArray.length) {
|
||||
this.resourcesFormArray.patchValue(resources, {onlySelf: true});
|
||||
this.resourcesFormArray.patchValue(resources, {onlySelf: true, emitEvent: false});
|
||||
} else {
|
||||
if (this.valueChange$) {
|
||||
this.valueChange$.unsubscribe();
|
||||
}
|
||||
const resourcesControl: Array<AbstractControl> = [];
|
||||
if (resources) {
|
||||
resources.forEach((resource) => {
|
||||
resourcesControl.push(this.createdResourceFormGroup(resource));
|
||||
});
|
||||
}
|
||||
this.resourcesFormGroup.setControl('resources', this.fb.array(resourcesControl));
|
||||
this.resourcesFormGroup.setControl('resources', this.fb.array(resourcesControl), {emitEvent: false});
|
||||
if (this.disabled) {
|
||||
this.resourcesFormGroup.disable({emitEvent: false});
|
||||
}
|
||||
this.valueChange$ = this.resourcesFormGroup.valueChanges.subscribe(() => {
|
||||
this.updateModel(this.resourcesFormGroup.getRawValue().resources);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -86,6 +86,8 @@ export class Lwm2mObserveAttrTelemetryComponent implements ControlValueAccessor,
|
||||
this.modelsFormGroup = this.fb.group({
|
||||
models: this.fb.array([])
|
||||
});
|
||||
|
||||
this.valueChange$ = this.modelsFormGroup.valueChanges.subscribe(value => this.updateModel(value.models));
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
@ -130,20 +132,14 @@ export class Lwm2mObserveAttrTelemetryComponent implements ControlValueAccessor,
|
||||
if (models.length === this.modelsFormArray.length) {
|
||||
this.modelsFormArray.patchValue(models, {emitEvent: false});
|
||||
} else {
|
||||
if (this.valueChange$) {
|
||||
this.valueChange$.unsubscribe();
|
||||
}
|
||||
const modelControls: Array<AbstractControl> = [];
|
||||
models.forEach(model => {
|
||||
modelControls.push(this.createModelFormGroup(model));
|
||||
});
|
||||
this.modelsFormGroup.setControl('models', this.fb.array(modelControls));
|
||||
this.modelsFormGroup.setControl('models', this.fb.array(modelControls), {emitEvent: false});
|
||||
if (this.disabled) {
|
||||
this.modelsFormGroup.disable({emitEvent: false});
|
||||
}
|
||||
this.valueChange$ = this.modelsFormGroup.valueChanges.subscribe(value => {
|
||||
this.updateModel(value.models);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -21,24 +21,15 @@
|
||||
</mat-checkbox>
|
||||
<div *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('sparkplug').value"
|
||||
class="tb-hint" innerHTML="{{ 'device-profile.mqtt-device-topic-filters-spark-plug-hint' | translate }}"></div>
|
||||
<mat-form-field floatLabel="always" class="mat-block" style="padding-top: 8px;"
|
||||
*ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('sparkplug').value">
|
||||
<mat-label translate>device-profile.mqtt-device-topic-filters-spark-plug-attribute-metric-names</mat-label>
|
||||
<mat-chip-list #attrMetricNamesChipList formControlName="sparkplugAttributesMetricNames">
|
||||
<mat-chip
|
||||
*ngFor="let name of mqttDeviceProfileTransportConfigurationFormGroup.get('sparkplugAttributesMetricNames').value;"
|
||||
(removed)="removeAttributeMetricName(name)">
|
||||
{{name}}
|
||||
<mat-icon matChipRemove>close</mat-icon>
|
||||
</mat-chip>
|
||||
<input matInput type="text" placeholder="{{'device-profile.mqtt-device-topic-filters-spark-plug-attribute-metric-names' | translate}}"
|
||||
[matChipInputFor]="attrMetricNamesChipList"
|
||||
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
|
||||
matChipInputAddOnBlur
|
||||
(matChipInputTokenEnd)="addAttributeMetricName($event)">
|
||||
</mat-chip-list>
|
||||
<mat-hint innerHTML="{{ 'device-profile.mqtt-device-topic-filters-spark-plug-attribute-metric-names-hint' | translate }}"></mat-hint>
|
||||
</mat-form-field>
|
||||
<tb-string-items-list *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('sparkplug').value"
|
||||
editable
|
||||
label="{{ 'device-profile.mqtt-device-topic-filters-spark-plug-attribute-metric-names' | translate }}"
|
||||
placeholder="{{'device-profile.mqtt-device-topic-filters-spark-plug-attribute-metric-names' | translate}}"
|
||||
hint="{{ 'device-profile.mqtt-device-topic-filters-spark-plug-attribute-metric-names-hint' | translate }}"
|
||||
floatLabel="always"
|
||||
subscriptSizing="dynamic"
|
||||
formControlName="sparkplugAttributesMetricNames">
|
||||
</tb-string-items-list>
|
||||
</form>
|
||||
<form [formGroup]="mqttDeviceProfileTransportConfigurationFormGroup" style="padding-bottom: 16px;" *ngIf="!mqttDeviceProfileTransportConfigurationFormGroup.get('sparkplug').value">
|
||||
<fieldset class="fields-group">
|
||||
|
||||
@ -41,8 +41,6 @@ import {
|
||||
import { isDefinedAndNotNull } from '@core/utils';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes';
|
||||
import { MatChipInputEvent } from '@angular/material/chips';
|
||||
|
||||
@Component({
|
||||
selector: 'tb-mqtt-device-profile-transport-configuration',
|
||||
@ -79,8 +77,6 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control
|
||||
|
||||
private propagateChange = (v: any) => { };
|
||||
|
||||
separatorKeysCodes = [ENTER, COMMA, SEMICOLON];
|
||||
|
||||
constructor(private store: Store<AppState>,
|
||||
private fb: UntypedFormBuilder) {
|
||||
}
|
||||
@ -175,34 +171,6 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control
|
||||
}
|
||||
}
|
||||
|
||||
removeAttributeMetricName(name: string): void {
|
||||
const names: string[] = this.mqttDeviceProfileTransportConfigurationFormGroup.get('sparkplugAttributesMetricNames').value;
|
||||
const index = names.indexOf(name);
|
||||
if (index >= 0) {
|
||||
names.splice(index, 1);
|
||||
this.mqttDeviceProfileTransportConfigurationFormGroup.get('sparkplugAttributesMetricNames').setValue(names);
|
||||
}
|
||||
}
|
||||
|
||||
addAttributeMetricName(event: MatChipInputEvent): void {
|
||||
const input = event.input;
|
||||
let value = event.value;
|
||||
if ((value || '').trim()) {
|
||||
value = value.trim();
|
||||
let names: string[] = this.mqttDeviceProfileTransportConfigurationFormGroup.get('sparkplugAttributesMetricNames').value;
|
||||
if (!names || names.indexOf(value) === -1) {
|
||||
if (!names) {
|
||||
names = [];
|
||||
}
|
||||
names.push(value);
|
||||
this.mqttDeviceProfileTransportConfigurationFormGroup.get('sparkplugAttributesMetricNames').setValue(names, {emitEvent: true});
|
||||
}
|
||||
}
|
||||
if (input) {
|
||||
input.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
private updateModel() {
|
||||
let configuration: DeviceProfileTransportConfiguration = null;
|
||||
if (this.mqttDeviceProfileTransportConfigurationFormGroup.valid) {
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
|
||||
-->
|
||||
<div fxLayout="column">
|
||||
<div *ngFor="let deviceProfileCommunication of communicationConfigFormArray().controls; let $index = index;
|
||||
<div *ngFor="let deviceProfileCommunication of communicationConfigFormArray.controls; let $index = index;
|
||||
last as isLast;" fxLayout="row" fxLayoutAlign="start center"
|
||||
fxLayoutGap="8px" class="scope-row" [formGroup]="deviceProfileCommunication">
|
||||
<div class="communication-config" fxFlex fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="start">
|
||||
@ -58,7 +58,7 @@
|
||||
<mat-icon>remove_circle_outline</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div *ngIf="!communicationConfigFormArray().controls.length && !disabled">
|
||||
<div *ngIf="!communicationConfigFormArray.controls.length && !disabled">
|
||||
<span fxLayoutAlign="center center" class="tb-prompt required required-text" translate>device-profile.snmp.please-add-communication-config</span>
|
||||
</div>
|
||||
<div *ngIf="!disabled && isAddEnabled">
|
||||
|
||||
@ -27,7 +27,7 @@ import {
|
||||
Validators
|
||||
} from '@angular/forms';
|
||||
import { SnmpCommunicationConfig, SnmpSpecType, SnmpSpecTypeTranslationMap } from '@shared/models/device.models';
|
||||
import { Subject, Subscription } from 'rxjs';
|
||||
import { Subject } from 'rxjs';
|
||||
import { isUndefinedOrNull } from '@core/utils';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
|
||||
@ -58,7 +58,6 @@ export class SnmpDeviceProfileCommunicationConfigComponent implements OnInit, On
|
||||
disabled: boolean;
|
||||
|
||||
private usedSpecType: SnmpSpecType[] = [];
|
||||
private valueChange$: Subscription = null;
|
||||
private destroy$ = new Subject<void>();
|
||||
private propagateChange = (v: any) => { };
|
||||
|
||||
@ -68,17 +67,17 @@ export class SnmpDeviceProfileCommunicationConfigComponent implements OnInit, On
|
||||
this.deviceProfileCommunicationConfig = this.fb.group({
|
||||
communicationConfig: this.fb.array([])
|
||||
});
|
||||
this.deviceProfileCommunicationConfig.valueChanges.pipe(
|
||||
takeUntil(this.destroy$)
|
||||
).subscribe(() => this.updateModel());
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (this.valueChange$) {
|
||||
this.valueChange$.unsubscribe();
|
||||
}
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
communicationConfigFormArray(): UntypedFormArray {
|
||||
get communicationConfigFormArray(): UntypedFormArray {
|
||||
return this.deviceProfileCommunicationConfig.get('communicationConfig') as UntypedFormArray;
|
||||
}
|
||||
|
||||
@ -99,27 +98,27 @@ export class SnmpDeviceProfileCommunicationConfigComponent implements OnInit, On
|
||||
}
|
||||
|
||||
writeValue(communicationConfig: SnmpCommunicationConfig[]) {
|
||||
if (this.valueChange$) {
|
||||
this.valueChange$.unsubscribe();
|
||||
}
|
||||
const communicationConfigControl: Array<AbstractControl> = [];
|
||||
if (communicationConfig) {
|
||||
communicationConfig.forEach((config) => {
|
||||
communicationConfigControl.push(this.createdFormGroup(config));
|
||||
});
|
||||
}
|
||||
this.deviceProfileCommunicationConfig.setControl('communicationConfig', this.fb.array(communicationConfigControl));
|
||||
if (!communicationConfig || !communicationConfig.length) {
|
||||
this.addCommunicationConfig();
|
||||
}
|
||||
if (this.disabled) {
|
||||
this.deviceProfileCommunicationConfig.disable({emitEvent: false});
|
||||
if (communicationConfig?.length === this.communicationConfigFormArray.length) {
|
||||
this.communicationConfigFormArray.patchValue(communicationConfig, {emitEvent: false});
|
||||
} else {
|
||||
this.deviceProfileCommunicationConfig.enable({emitEvent: false});
|
||||
const communicationConfigControl: Array<AbstractControl> = [];
|
||||
if (communicationConfig) {
|
||||
communicationConfig.forEach((config) => {
|
||||
communicationConfigControl.push(this.createdFormGroup(config));
|
||||
});
|
||||
}
|
||||
this.deviceProfileCommunicationConfig.setControl(
|
||||
'communicationConfig', this.fb.array(communicationConfigControl), {emitEvent: false}
|
||||
);
|
||||
if (!communicationConfig || !communicationConfig.length) {
|
||||
this.addCommunicationConfig();
|
||||
}
|
||||
if (this.disabled) {
|
||||
this.deviceProfileCommunicationConfig.disable({emitEvent: false});
|
||||
} else {
|
||||
this.deviceProfileCommunicationConfig.enable({emitEvent: false});
|
||||
}
|
||||
}
|
||||
this.valueChange$ = this.deviceProfileCommunicationConfig.valueChanges.subscribe(() => {
|
||||
this.updateModel();
|
||||
});
|
||||
this.updateUsedSpecType();
|
||||
if (!this.disabled && !this.deviceProfileCommunicationConfig.valid) {
|
||||
this.updateModel();
|
||||
@ -133,16 +132,16 @@ export class SnmpDeviceProfileCommunicationConfigComponent implements OnInit, On
|
||||
}
|
||||
|
||||
public removeCommunicationConfig(index: number) {
|
||||
this.communicationConfigFormArray().removeAt(index);
|
||||
this.communicationConfigFormArray.removeAt(index);
|
||||
}
|
||||
|
||||
|
||||
get isAddEnabled(): boolean {
|
||||
return this.communicationConfigFormArray().length !== Object.keys(SnmpSpecType).length;
|
||||
return this.communicationConfigFormArray.length !== Object.keys(SnmpSpecType).length;
|
||||
}
|
||||
|
||||
public addCommunicationConfig() {
|
||||
this.communicationConfigFormArray().push(this.createdFormGroup());
|
||||
this.communicationConfigFormArray.push(this.createdFormGroup());
|
||||
this.deviceProfileCommunicationConfig.updateValueAndValidity();
|
||||
if (!this.deviceProfileCommunicationConfig.valid) {
|
||||
this.updateModel();
|
||||
|
||||
@ -25,7 +25,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<mat-divider></mat-divider>
|
||||
<div *ngFor="let mappingConfig of mappingsConfigFormArray().controls; let $index = index;
|
||||
<div *ngFor="let mappingConfig of mappingsConfigFormArray.controls; let $index = index;
|
||||
last as isLast;" fxLayout="row" fxLayoutAlign="start center"
|
||||
fxLayoutGap="8px" [formGroup]="mappingConfig" class="mapping-list">
|
||||
<div fxFlex fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="start">
|
||||
@ -64,7 +64,7 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="!mappingsConfigFormArray().controls.length && !disabled">
|
||||
<div *ngIf="!mappingsConfigFormArray.controls.length && !disabled">
|
||||
<span fxLayoutAlign="center center" class="tb-prompt required required-text" translate>device-profile.snmp.please-add-mapping-config</span>
|
||||
</div>
|
||||
<div *ngIf="!disabled">
|
||||
|
||||
@ -18,19 +18,20 @@ import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core';
|
||||
import {
|
||||
AbstractControl,
|
||||
ControlValueAccessor,
|
||||
NG_VALIDATORS,
|
||||
NG_VALUE_ACCESSOR,
|
||||
UntypedFormArray,
|
||||
UntypedFormBuilder,
|
||||
UntypedFormGroup,
|
||||
NG_VALIDATORS,
|
||||
NG_VALUE_ACCESSOR,
|
||||
ValidationErrors,
|
||||
Validator,
|
||||
Validators
|
||||
} from '@angular/forms';
|
||||
import { SnmpMapping } from '@shared/models/device.models';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { Subject } from 'rxjs';
|
||||
import { DataType, DataTypeTranslationMap } from '@shared/models/constants';
|
||||
import { isUndefinedOrNull } from '@core/utils';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'tb-snmp-device-profile-mapping',
|
||||
@ -60,7 +61,7 @@ export class SnmpDeviceProfileMappingComponent implements OnInit, OnDestroy, Con
|
||||
|
||||
private readonly oidPattern: RegExp = /^\.?([0-2])((\.0)|(\.[1-9][0-9]*))*$/;
|
||||
|
||||
private valueChange$: Subscription = null;
|
||||
private destroy$ = new Subject<void>();
|
||||
private propagateChange = (v: any) => { };
|
||||
|
||||
constructor(private fb: UntypedFormBuilder) { }
|
||||
@ -69,12 +70,14 @@ export class SnmpDeviceProfileMappingComponent implements OnInit, OnDestroy, Con
|
||||
this.mappingsConfigForm = this.fb.group({
|
||||
mappings: this.fb.array([])
|
||||
});
|
||||
this.mappingsConfigForm.valueChanges.pipe(
|
||||
takeUntil(this.destroy$)
|
||||
).subscribe(() => this.updateModel());
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (this.valueChange$) {
|
||||
this.valueChange$.unsubscribe();
|
||||
}
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
registerOnChange(fn: any) {
|
||||
@ -100,38 +103,36 @@ export class SnmpDeviceProfileMappingComponent implements OnInit, OnDestroy, Con
|
||||
}
|
||||
|
||||
writeValue(mappings: SnmpMapping[]) {
|
||||
if (this.valueChange$) {
|
||||
this.valueChange$.unsubscribe();
|
||||
}
|
||||
const mappingsControl: Array<AbstractControl> = [];
|
||||
if (mappings) {
|
||||
mappings.forEach((config) => {
|
||||
mappingsControl.push(this.createdFormGroup(config));
|
||||
});
|
||||
}
|
||||
this.mappingsConfigForm.setControl('mappings', this.fb.array(mappingsControl));
|
||||
if (!mappings || !mappings.length) {
|
||||
this.addMappingConfig();
|
||||
}
|
||||
if (this.disabled) {
|
||||
this.mappingsConfigForm.disable({emitEvent: false});
|
||||
if (mappings?.length === this.mappingsConfigFormArray.length) {
|
||||
this.mappingsConfigFormArray.patchValue(mappings, {emitEvent: false});
|
||||
} else {
|
||||
this.mappingsConfigForm.enable({emitEvent: false});
|
||||
const mappingsControl: Array<AbstractControl> = [];
|
||||
if (mappings) {
|
||||
mappings.forEach((config) => {
|
||||
mappingsControl.push(this.createdFormGroup(config));
|
||||
});
|
||||
}
|
||||
this.mappingsConfigForm.setControl('mappings', this.fb.array(mappingsControl), {emitEvent: false});
|
||||
if (!mappings || !mappings.length) {
|
||||
this.addMappingConfig();
|
||||
}
|
||||
if (this.disabled) {
|
||||
this.mappingsConfigForm.disable({emitEvent: false});
|
||||
} else {
|
||||
this.mappingsConfigForm.enable({emitEvent: false});
|
||||
}
|
||||
}
|
||||
this.valueChange$ = this.mappingsConfigForm.valueChanges.subscribe(() => {
|
||||
this.updateModel();
|
||||
});
|
||||
if (!this.disabled && !this.mappingsConfigForm.valid) {
|
||||
this.updateModel();
|
||||
}
|
||||
}
|
||||
|
||||
mappingsConfigFormArray(): UntypedFormArray {
|
||||
get mappingsConfigFormArray(): UntypedFormArray {
|
||||
return this.mappingsConfigForm.get('mappings') as UntypedFormArray;
|
||||
}
|
||||
|
||||
public addMappingConfig() {
|
||||
this.mappingsConfigFormArray().push(this.createdFormGroup());
|
||||
this.mappingsConfigFormArray.push(this.createdFormGroup());
|
||||
this.mappingsConfigForm.updateValueAndValidity();
|
||||
if (!this.mappingsConfigForm.valid) {
|
||||
this.updateModel();
|
||||
@ -139,7 +140,7 @@ export class SnmpDeviceProfileMappingComponent implements OnInit, OnDestroy, Con
|
||||
}
|
||||
|
||||
public removeMappingConfig(index: number) {
|
||||
this.mappingsConfigFormArray().removeAt(index);
|
||||
this.mappingsConfigFormArray.removeAt(index);
|
||||
}
|
||||
|
||||
private createdFormGroup(value?: SnmpMapping): UntypedFormGroup {
|
||||
|
||||
@ -30,10 +30,11 @@ import {
|
||||
import { Store } from '@ngrx/store';
|
||||
import { AppState } from '@app/core/core.state';
|
||||
import { coerceBooleanProperty } from '@angular/cdk/coercion';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { Subject } from 'rxjs';
|
||||
import { QueueInfo } from '@shared/models/queue.models';
|
||||
import { UtilsService } from '@core/services/utils.service';
|
||||
import { guid } from '@core/utils';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'tb-tenant-profile-queues',
|
||||
@ -70,8 +71,7 @@ export class TenantProfileQueuesComponent implements ControlValueAccessor, Valid
|
||||
@Input()
|
||||
disabled: boolean;
|
||||
|
||||
private valueChangeSubscription$: Subscription = null;
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
private propagateChange = (v: any) => { };
|
||||
|
||||
constructor(private store: Store<AppState>,
|
||||
@ -83,12 +83,6 @@ export class TenantProfileQueuesComponent implements ControlValueAccessor, Valid
|
||||
this.propagateChange = fn;
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (this.valueChangeSubscription$) {
|
||||
this.valueChangeSubscription$.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
registerOnTouched(fn: any): void {
|
||||
}
|
||||
|
||||
@ -96,6 +90,15 @@ export class TenantProfileQueuesComponent implements ControlValueAccessor, Valid
|
||||
this.tenantProfileQueuesFormGroup = this.fb.group({
|
||||
queues: this.fb.array([])
|
||||
});
|
||||
|
||||
this.tenantProfileQueuesFormGroup.valueChanges.pipe(
|
||||
takeUntil(this.destroy$)
|
||||
).subscribe(() => this.updateModel());
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
get queuesFormArray(): UntypedFormArray {
|
||||
@ -112,30 +115,28 @@ export class TenantProfileQueuesComponent implements ControlValueAccessor, Valid
|
||||
}
|
||||
|
||||
writeValue(queues: Array<QueueInfo> | null): void {
|
||||
if (this.valueChangeSubscription$) {
|
||||
this.valueChangeSubscription$.unsubscribe();
|
||||
}
|
||||
const queuesControls: Array<AbstractControl> = [];
|
||||
if (queues) {
|
||||
queues.forEach((queue, index) => {
|
||||
if (!queue.id) {
|
||||
if (!this.idMap[index]) {
|
||||
this.idMap.push(guid());
|
||||
}
|
||||
queue.id = this.idMap[index];
|
||||
}
|
||||
queuesControls.push(this.fb.control(queue, [Validators.required]));
|
||||
});
|
||||
}
|
||||
this.tenantProfileQueuesFormGroup.setControl('queues', this.fb.array(queuesControls));
|
||||
if (this.disabled) {
|
||||
this.tenantProfileQueuesFormGroup.disable({emitEvent: false});
|
||||
if (queues.length === this.queuesFormArray.length) {
|
||||
this.queuesFormArray.patchValue(queues, {emitEvent: false});
|
||||
} else {
|
||||
this.tenantProfileQueuesFormGroup.enable({emitEvent: false});
|
||||
const queuesControls: Array<AbstractControl> = [];
|
||||
if (queues) {
|
||||
queues.forEach((queue, index) => {
|
||||
if (!queue.id) {
|
||||
if (!this.idMap[index]) {
|
||||
this.idMap.push(guid());
|
||||
}
|
||||
queue.id = this.idMap[index];
|
||||
}
|
||||
queuesControls.push(this.fb.control(queue, [Validators.required]));
|
||||
});
|
||||
}
|
||||
this.tenantProfileQueuesFormGroup.setControl('queues', this.fb.array(queuesControls), {emitEvent: false});
|
||||
if (this.disabled) {
|
||||
this.tenantProfileQueuesFormGroup.disable({emitEvent: false});
|
||||
} else {
|
||||
this.tenantProfileQueuesFormGroup.enable({emitEvent: false});
|
||||
}
|
||||
}
|
||||
this.valueChangeSubscription$ = this.tenantProfileQueuesFormGroup.valueChanges.subscribe(() =>
|
||||
this.updateModel()
|
||||
);
|
||||
}
|
||||
|
||||
public trackByQueue(index: number, queueControl: AbstractControl) {
|
||||
|
||||
@ -45,7 +45,7 @@ export const rateLimitsLabelTranslationMap = new Map<RateLimitsType, string>(
|
||||
[RateLimitsType.DEVICE_MESSAGES, 'tenant-profile.rate-limits.transport-device-msg'],
|
||||
[RateLimitsType.DEVICE_TELEMETRY_MESSAGES, 'tenant-profile.rate-limits.transport-device-telemetry-msg'],
|
||||
[RateLimitsType.DEVICE_TELEMETRY_DATA_POINTS, 'tenant-profile.rate-limits.transport-device-telemetry-data-points'],
|
||||
[RateLimitsType.TENANT_SERVER_REST_LIMITS_CONFIGURATION, 'tenant-profile.transport-tenant-msg-rate-limit'],
|
||||
[RateLimitsType.TENANT_SERVER_REST_LIMITS_CONFIGURATION, 'tenant-profile.rest-requests-for-tenant'],
|
||||
[RateLimitsType.CUSTOMER_SERVER_REST_LIMITS_CONFIGURATION, 'tenant-profile.customer-rest-limits'],
|
||||
[RateLimitsType.WS_UPDATE_PER_SESSION_RATE_LIMIT, 'tenant-profile.ws-limit-updates-per-session'],
|
||||
[RateLimitsType.CASSANDRA_QUERY_TENANT_RATE_LIMITS_CONFIGURATION, 'tenant-profile.cassandra-tenant-limits-configuration'],
|
||||
@ -63,7 +63,7 @@ export const rateLimitsDialogTitleTranslationMap = new Map<RateLimitsType, strin
|
||||
[RateLimitsType.DEVICE_MESSAGES, 'tenant-profile.rate-limits.edit-transport-device-msg-title'],
|
||||
[RateLimitsType.DEVICE_TELEMETRY_MESSAGES, 'tenant-profile.rate-limits.edit-transport-device-telemetry-msg-title'],
|
||||
[RateLimitsType.DEVICE_TELEMETRY_DATA_POINTS, 'tenant-profile.rate-limits.edit-transport-device-telemetry-data-points-title'],
|
||||
[RateLimitsType.TENANT_SERVER_REST_LIMITS_CONFIGURATION, 'tenant-profile.rate-limits.edit-transport-tenant-msg-rate-limit-title'],
|
||||
[RateLimitsType.TENANT_SERVER_REST_LIMITS_CONFIGURATION, 'tenant-profile.rate-limits.edit-tenant-rest-limits-title'],
|
||||
[RateLimitsType.CUSTOMER_SERVER_REST_LIMITS_CONFIGURATION, 'tenant-profile.rate-limits.edit-customer-rest-limits-title'],
|
||||
[RateLimitsType.WS_UPDATE_PER_SESSION_RATE_LIMIT, 'tenant-profile.rate-limits.edit-ws-limit-updates-per-session-title'],
|
||||
[RateLimitsType.CASSANDRA_QUERY_TENANT_RATE_LIMITS_CONFIGURATION, 'tenant-profile.rate-limits.edit-cassandra-tenant-limits-configuration-title'],
|
||||
|
||||
@ -17,17 +17,17 @@
|
||||
-->
|
||||
<div class="tb-relation-filters" [formGroup]="relationFiltersFormGroup">
|
||||
<div class="container">
|
||||
<div class="header" [fxShow]="relationFiltersFormArray().length">
|
||||
<div class="header" [fxShow]="relationFiltersFormArray.length">
|
||||
<div fxLayout="row" fxLayoutAlign="start center">
|
||||
<span class="cell" style="width: 200px; min-width: 200px;" translate>relation.type</span>
|
||||
<span class="cell" fxFlex translate>entity.entity-types</span>
|
||||
<span class="cell" style="width: 40px; min-width: 40px;"> </span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="body" [fxShow]="relationFiltersFormArray().length">
|
||||
<div class="body" [fxShow]="relationFiltersFormArray.length">
|
||||
<div class="row" fxFlex fxLayout="row"
|
||||
fxLayoutAlign="start center" formArrayName="relationFilters"
|
||||
*ngFor="let relationFilterControl of relationFiltersFormArray().controls; let $index = index">
|
||||
*ngFor="let relationFilterControl of relationFiltersFormArray.controls; let $index = index">
|
||||
<div class="mat-elevation-z1" fxFlex fxLayout="row" fxLayoutAlign="start center" style="padding: 8px 0;">
|
||||
<tb-relation-type-autocomplete
|
||||
class="cell" style="width: 200px; min-width: 200px;"
|
||||
@ -51,7 +51,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="any-filter" [fxShow]="!relationFiltersFormArray().length">
|
||||
<div class="any-filter" [fxShow]="!relationFiltersFormArray.length">
|
||||
<span fxLayoutAlign="center center"
|
||||
class="tb-prompt" translate>relation.any-relation</span>
|
||||
</div>
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
/// limitations under the License.
|
||||
///
|
||||
|
||||
import { Component, forwardRef, Input, OnInit } from '@angular/core';
|
||||
import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core';
|
||||
import {
|
||||
AbstractControl,
|
||||
ControlValueAccessor,
|
||||
@ -28,7 +28,8 @@ import { RelationEntityTypeFilter } from '@shared/models/relation.models';
|
||||
import { PageComponent } from '@shared/components/page.component';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { AppState } from '@core/core.state';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'tb-relation-filters',
|
||||
@ -42,7 +43,7 @@ import { Subscription } from 'rxjs';
|
||||
}
|
||||
]
|
||||
})
|
||||
export class RelationFiltersComponent extends PageComponent implements ControlValueAccessor, OnInit {
|
||||
export class RelationFiltersComponent extends PageComponent implements ControlValueAccessor, OnInit, OnDestroy {
|
||||
|
||||
@Input() disabled: boolean;
|
||||
|
||||
@ -50,22 +51,32 @@ export class RelationFiltersComponent extends PageComponent implements ControlVa
|
||||
|
||||
relationFiltersFormGroup: UntypedFormGroup;
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
private propagateChange = null;
|
||||
|
||||
private valueChangeSubscription: Subscription = null;
|
||||
|
||||
constructor(protected store: Store<AppState>,
|
||||
private fb: UntypedFormBuilder) {
|
||||
super(store);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.relationFiltersFormGroup = this.fb.group({});
|
||||
this.relationFiltersFormGroup.addControl('relationFilters',
|
||||
this.fb.array([]));
|
||||
this.relationFiltersFormGroup = this.fb.group({
|
||||
relationFilters: this.fb.array([])
|
||||
});
|
||||
|
||||
this.relationFiltersFormGroup.valueChanges.pipe(
|
||||
takeUntil(this.destroy$)
|
||||
).subscribe(() => {
|
||||
this.updateModel();
|
||||
});
|
||||
}
|
||||
|
||||
relationFiltersFormArray(): UntypedFormArray {
|
||||
ngOnDestroy() {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
get relationFiltersFormArray(): UntypedFormArray {
|
||||
return this.relationFiltersFormGroup.get('relationFilters') as UntypedFormArray;
|
||||
}
|
||||
|
||||
@ -81,19 +92,17 @@ export class RelationFiltersComponent extends PageComponent implements ControlVa
|
||||
}
|
||||
|
||||
writeValue(filters: Array<RelationEntityTypeFilter>): void {
|
||||
if (this.valueChangeSubscription) {
|
||||
this.valueChangeSubscription.unsubscribe();
|
||||
if (filters?.length === this.relationFiltersFormArray.length) {
|
||||
this.relationFiltersFormArray.patchValue(filters, {emitEvent: false});
|
||||
} else {
|
||||
const relationFiltersControls: Array<AbstractControl> = [];
|
||||
if (filters && filters.length) {
|
||||
filters.forEach((filter) => {
|
||||
relationFiltersControls.push(this.createRelationFilterFormGroup(filter));
|
||||
});
|
||||
}
|
||||
this.relationFiltersFormGroup.setControl('relationFilters', this.fb.array(relationFiltersControls), {emitEvent: false});
|
||||
}
|
||||
const relationFiltersControls: Array<AbstractControl> = [];
|
||||
if (filters && filters.length) {
|
||||
filters.forEach((filter) => {
|
||||
relationFiltersControls.push(this.createRelationFilterFormGroup(filter));
|
||||
});
|
||||
}
|
||||
this.relationFiltersFormGroup.setControl('relationFilters', this.fb.array(relationFiltersControls));
|
||||
this.valueChangeSubscription = this.relationFiltersFormGroup.valueChanges.subscribe(() => {
|
||||
this.updateModel();
|
||||
});
|
||||
}
|
||||
|
||||
public removeFilter(index: number) {
|
||||
@ -101,12 +110,11 @@ export class RelationFiltersComponent extends PageComponent implements ControlVa
|
||||
}
|
||||
|
||||
public addFilter() {
|
||||
const relationFiltersFormArray = this.relationFiltersFormGroup.get('relationFilters') as UntypedFormArray;
|
||||
const filter: RelationEntityTypeFilter = {
|
||||
relationType: null,
|
||||
entityTypes: []
|
||||
};
|
||||
relationFiltersFormArray.push(this.createRelationFilterFormGroup(filter));
|
||||
this.relationFiltersFormArray.push(this.createRelationFilterFormGroup(filter));
|
||||
}
|
||||
|
||||
private createRelationFilterFormGroup(filter: RelationEntityTypeFilter): AbstractControl {
|
||||
|
||||
@ -15,8 +15,10 @@
|
||||
limitations under the License.
|
||||
|
||||
-->
|
||||
<div>
|
||||
<mat-card appearance="outlined" class="repository-settings" [ngClass]="{'settings-card': !detailsMode}" [ngStyle]="popoverComponent ? {boxShadow: 'none', width: '800px'} : {}">
|
||||
<div style="height: min-content; max-height: 80vh;">
|
||||
<mat-card appearance="outlined" class="repository-settings"
|
||||
[ngClass]="{'settings-card': !detailsMode}"
|
||||
[ngStyle]="popoverComponent ? {boxShadow: 'none', maxWidth: '100%', minWidth: '100%', width: '800px'} : {}">
|
||||
<mat-card-header>
|
||||
<mat-card-title>
|
||||
<span class="mat-headline-5" translate>admin.repository-settings</span>
|
||||
@ -41,10 +43,10 @@
|
||||
<mat-label translate>admin.default-branch</mat-label>
|
||||
<input matInput formControlName="defaultBranch">
|
||||
</mat-form-field>
|
||||
<mat-checkbox formControlName="readOnly">
|
||||
{{ 'admin.repository-read-only' | translate }}
|
||||
</mat-checkbox>
|
||||
<div>
|
||||
<div fxLayout="column">
|
||||
<mat-checkbox formControlName="readOnly">
|
||||
{{ 'admin.repository-read-only' | translate }}
|
||||
</mat-checkbox>
|
||||
<mat-checkbox formControlName="showMergeCommits">
|
||||
{{ 'admin.show-merge-commits' | translate }}
|
||||
</mat-checkbox>
|
||||
@ -74,6 +76,7 @@
|
||||
<input matInput formControlName="password" type="password"
|
||||
placeholder="{{ 'common.enter-password' | translate }}" autocomplete="new-password"/>
|
||||
<tb-toggle-password matSuffix></tb-toggle-password>
|
||||
<mat-hint [innerHTML] = "'admin.auth-method-username-password-hint' | translate"></mat-hint>
|
||||
</mat-form-field>
|
||||
</section>
|
||||
<section [fxShow]="repositorySettingsForm.get('authMethod').value === repositoryAuthMethod.PRIVATE_KEY" fxLayout="column">
|
||||
|
||||
@ -17,9 +17,6 @@
|
||||
.mat-mdc-card.repository-settings {
|
||||
margin: 8px;
|
||||
}
|
||||
.mat-mdc-checkbox {
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
.fields-group {
|
||||
padding: 0 16px 8px;
|
||||
margin-bottom: 10px;
|
||||
|
||||
@ -101,7 +101,7 @@
|
||||
|
||||
&.comparison {
|
||||
.mat-expansion-panel-header {
|
||||
height: 100%;
|
||||
height: fit-content;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -804,11 +804,13 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
|
||||
if (descriptors.length) {
|
||||
let entityId;
|
||||
let entityName;
|
||||
let entityLabel;
|
||||
if (alarm && alarm.originator) {
|
||||
entityId = alarm.originator;
|
||||
entityName = alarm.originatorName;
|
||||
entityLabel = alarm.originatorLabel;
|
||||
}
|
||||
this.ctx.actionsApi.handleWidgetAction($event, descriptors[0], entityId, entityName, {alarm});
|
||||
this.ctx.actionsApi.handleWidgetAction($event, descriptors[0], entityId, entityName, {alarm}, entityLabel);
|
||||
}
|
||||
}
|
||||
|
||||
@ -827,11 +829,13 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
|
||||
}
|
||||
let entityId;
|
||||
let entityName;
|
||||
let entityLabel;
|
||||
if (alarm && alarm.originator) {
|
||||
entityId = alarm.originator;
|
||||
entityName = alarm.originatorName;
|
||||
entityLabel = alarm.originatorLabel;
|
||||
}
|
||||
this.ctx.actionsApi.handleWidgetAction($event, actionDescriptor, entityId, entityName, {alarm});
|
||||
this.ctx.actionsApi.handleWidgetAction($event, actionDescriptor, entityId, entityName, {alarm}, entityLabel);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -405,7 +405,7 @@ export class TbCanvasDigitalGauge {
|
||||
(this.gauge.options as CanvasDigitalGaugeOptions).labelTimestamp =
|
||||
filter.transform(timestamp, this.localSettings.timestampFormat);
|
||||
}
|
||||
const value = tvPair[1];
|
||||
const value = parseFloat(tvPair[1]);
|
||||
if (value !== this.gauge.value) {
|
||||
if (!this.gauge.options.animation) {
|
||||
this.gauge._value = value;
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
-->
|
||||
<div fxLayout="column" class="mat-content mat-padding">
|
||||
<label class="tb-title" translate>entity.columns-to-display</label>
|
||||
<mat-checkbox style="padding-bottom: 8px;" [(ngModel)]="column.display" *ngFor="let column of (columns | selectableColumns)"
|
||||
<mat-checkbox [(ngModel)]="column.display" *ngFor="let column of (columns | selectableColumns)"
|
||||
(ngModelChange)="update()">
|
||||
{{ column.title }}
|
||||
</mat-checkbox>
|
||||
|
||||
@ -51,7 +51,7 @@
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<section fxLayout="column" fxLayoutGap="8px">
|
||||
<section fxLayout="column" fxLayoutGap.gt-xs="8px">
|
||||
<section fxLayout="column" fxLayout.gt-xs="row" fxLayoutGap="8px" fxLayoutAlign.gt-xs="start center">
|
||||
<mat-slide-toggle fxFlex formControlName="displayEntityName">
|
||||
{{ 'widgets.table.display-entity-name' | translate }}
|
||||
|
||||
@ -47,7 +47,7 @@
|
||||
</mat-form-field>
|
||||
<fieldset class="fields-group">
|
||||
<legend class="group-title" translate>widgets.label-widget.label-position</legend>
|
||||
<section fxLayout="row" fxLayoutGap="8px">
|
||||
<section fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
|
||||
<mat-form-field fxFlex>
|
||||
<mat-label translate>widgets.label-widget.x-pos</mat-label>
|
||||
<input matInput type="number" min="0" max="100" formControlName="x">
|
||||
|
||||
@ -24,7 +24,7 @@
|
||||
</fieldset>
|
||||
<fieldset class="fields-group">
|
||||
<legend class="group-title" translate>widgets.chart.border-settings</legend>
|
||||
<section fxLayout="row" fxLayoutGap="8px">
|
||||
<section fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
|
||||
<mat-form-field fxFlex class="mat-block">
|
||||
<mat-label translate>widgets.chart.border-width</mat-label>
|
||||
<input matInput type="number" min="0" formControlName="borderWidth">
|
||||
|
||||
@ -41,7 +41,7 @@
|
||||
</fieldset>
|
||||
<fieldset class="fields-group" formGroupName="stroke">
|
||||
<legend class="group-title" translate>widgets.chart.stroke-settings</legend>
|
||||
<section fxLayout="row" fxLayoutGap="8px">
|
||||
<section fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
|
||||
<mat-form-field fxFlex class="mat-block">
|
||||
<mat-label translate>widgets.chart.width-pixels</mat-label>
|
||||
<input matInput type="number" min="0" formControlName="width">
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
|
||||
-->
|
||||
<section [formGroup]="widgetFontFormGroup" fxLayout="column">
|
||||
<section fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="start center">
|
||||
<section fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px" fxLayoutAlign.gt-xs="start center">
|
||||
<mat-form-field fxFlex class="mat-block">
|
||||
<mat-label translate>widgets.widget-font.font-family</mat-label>
|
||||
<input matInput formControlName="family">
|
||||
@ -26,7 +26,7 @@
|
||||
<input matInput type="number" min="1" step="1" formControlName="size">
|
||||
</mat-form-field>
|
||||
</section>
|
||||
<section fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="start center">
|
||||
<section fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px" fxLayoutAlign.gt-xs="start center">
|
||||
<mat-form-field fxFlex class="mat-block">
|
||||
<mat-label translate>widgets.widget-font.font-style</mat-label>
|
||||
<mat-select formControlName="style">
|
||||
@ -68,7 +68,7 @@
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</section>
|
||||
<section fxLayout="row wrap" fxLayoutGap="8px" fxLayoutAlign="start center">
|
||||
<section fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
|
||||
<tb-color-input
|
||||
fxFlex
|
||||
formControlName="color"
|
||||
|
||||
@ -29,7 +29,7 @@
|
||||
<mat-label translate>widgets.rpc.initial-value</mat-label>
|
||||
<input matInput type="number" formControlName="initialValue">
|
||||
</mat-form-field>
|
||||
<section fxLayout="row" fxLayoutGap="8px">
|
||||
<section fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
|
||||
<mat-form-field fxFlex class="mat-block">
|
||||
<mat-label translate>widgets.rpc.min-value</mat-label>
|
||||
<input required matInput type="number" formControlName="minValue">
|
||||
|
||||
@ -33,7 +33,7 @@
|
||||
</mat-checkbox>
|
||||
</section>
|
||||
</section>
|
||||
<section fxLayout="column" fxLayoutGap="8px">
|
||||
<section fxLayout="column" fxLayoutGap.gt-xs="8px">
|
||||
<mat-slide-toggle fxFlex formControlName="displayDetails">
|
||||
{{ 'widgets.persistent-table.display-request-details' | translate }}
|
||||
</mat-slide-toggle>
|
||||
|
||||
@ -24,7 +24,7 @@
|
||||
<mat-slide-toggle formControlName="isPrimary" class="slide-block">
|
||||
{{ 'widgets.rpc.button-primary' | translate }}
|
||||
</mat-slide-toggle>
|
||||
<section fxLayout="row wrap" fxLayoutGap="8px">
|
||||
<section fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
|
||||
<tb-color-input fxFlex
|
||||
formControlName="textColor"
|
||||
icon="format_color_fill"
|
||||
|
||||
@ -22,7 +22,7 @@
|
||||
<mat-label translate>widgets.rpc.slide-toggle-label</mat-label>
|
||||
<input matInput formControlName="title">
|
||||
</mat-form-field>
|
||||
<section fxLayout="row" fxLayoutGap="8px">
|
||||
<section fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
|
||||
<mat-form-field fxFlex class="mat-block">
|
||||
<mat-label translate>widgets.rpc.label-position</mat-label>
|
||||
<mat-select formControlName="labelPosition">
|
||||
|
||||
@ -42,7 +42,7 @@
|
||||
icon="format_color_fill"
|
||||
label="{{ 'widgets.gauge.major-ticks-color' | translate }}" openOnInput colorClearButton>
|
||||
</tb-color-input>
|
||||
<section fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="start center">
|
||||
<section fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px" fxLayoutAlign.gt-xs="start center">
|
||||
<mat-form-field fxFlex class="mat-block">
|
||||
<mat-label translate>widgets.gauge.minor-ticks-count</mat-label>
|
||||
<input matInput type="number" min="0" formControlName="minorTicks">
|
||||
@ -71,7 +71,7 @@
|
||||
<mat-slide-toggle formControlName="showBorder" class="slide-block">
|
||||
{{ 'widgets.gauge.show-plate-border' | translate }}
|
||||
</mat-slide-toggle>
|
||||
<section fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="start center">
|
||||
<section fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px" fxLayoutAlign.gt-xs="start center">
|
||||
<tb-color-input fxFlex
|
||||
formControlName="colorBorder"
|
||||
icon="format_color_fill"
|
||||
@ -89,7 +89,7 @@
|
||||
<mat-label translate>widgets.gauge.needle-circle-size</mat-label>
|
||||
<input matInput type="number" min="0" formControlName="needleCircleSize">
|
||||
</mat-form-field>
|
||||
<section fxLayout="row wrap" fxLayoutGap="8px" fxLayoutAlign="start center">
|
||||
<section fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px" fxLayoutAlign.gt-xs="start center">
|
||||
<tb-color-input fxFlex
|
||||
formControlName="colorNeedle"
|
||||
icon="format_color_fill"
|
||||
|
||||
@ -31,7 +31,7 @@
|
||||
</tb-color-input>
|
||||
<fieldset class="fields-group">
|
||||
<legend class="group-title" translate>widgets.gauge.ticks-settings</legend>
|
||||
<section fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="start center">
|
||||
<section fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
|
||||
<mat-form-field fxFlex class="mat-block">
|
||||
<mat-label translate>widgets.gauge.min-value</mat-label>
|
||||
<input matInput type="number" formControlName="minValue">
|
||||
@ -41,7 +41,7 @@
|
||||
<input matInput type="number" formControlName="maxValue">
|
||||
</mat-form-field>
|
||||
</section>
|
||||
<section fxLayout="column" fxLayout.gt-xs="row" fxLayoutGap="0px" fxLayoutGap.gt-xs="8px" fxLayoutAlign.gt-xs="start center">
|
||||
<section fxLayout="column" fxLayout.gt-xs="row" fxLayoutGap.gt-xs="8px" fxLayoutAlign.gt-xs="start center">
|
||||
<mat-form-field fxFlex class="mat-block">
|
||||
<mat-label translate>widgets.gauge.major-ticks-count</mat-label>
|
||||
<input matInput type="number" min="0" formControlName="majorTicksCount">
|
||||
@ -52,7 +52,7 @@
|
||||
label="{{ 'widgets.gauge.major-ticks-color' | translate }}" openOnInput colorClearButton>
|
||||
</tb-color-input>
|
||||
</section>
|
||||
<section fxLayout="column" fxLayout.gt-xs="row" fxLayoutGap="0px" fxLayoutGap.gt-xs="8px" fxLayoutAlign.gt-xs="start center">
|
||||
<section fxLayout="column" fxLayout.gt-xs="row" fxLayoutGap.gt-xs="8px" fxLayoutAlign.gt-xs="start center">
|
||||
<mat-form-field fxFlex class="mat-block">
|
||||
<mat-label translate>widgets.gauge.minor-ticks-count</mat-label>
|
||||
<input matInput type="number" min="0" formControlName="minorTicks">
|
||||
@ -127,7 +127,7 @@
|
||||
<legend class="group-title" translate>widgets.gauge.value-font</legend>
|
||||
<tb-widget-font formControlName="valueFont" [hasShadowColor]="true"></tb-widget-font>
|
||||
</fieldset>
|
||||
<section fxLayout="row wrap" fxLayoutGap="8px" fxLayoutAlign="start center">
|
||||
<section fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
|
||||
<tb-color-input fxFlex
|
||||
formControlName="colorValueBoxRect"
|
||||
icon="format_color_fill"
|
||||
@ -139,7 +139,7 @@
|
||||
label="{{ 'widgets.gauge.value-box-rect-stroke-color-end' | translate }}" openOnInput colorClearButton>
|
||||
</tb-color-input>
|
||||
</section>
|
||||
<section fxLayout="row wrap" fxLayoutGap="8px" fxLayoutAlign="start center">
|
||||
<section fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
|
||||
<tb-color-input fxFlex
|
||||
formControlName="colorValueBoxBackground"
|
||||
icon="format_color_fill"
|
||||
@ -168,7 +168,7 @@
|
||||
</fieldset>
|
||||
<fieldset class="fields-group">
|
||||
<legend class="group-title" translate>widgets.gauge.needle-settings</legend>
|
||||
<section fxLayout="row wrap" fxLayoutGap="8px" fxLayoutAlign="start center">
|
||||
<section fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
|
||||
<tb-color-input fxFlex
|
||||
formControlName="colorNeedle"
|
||||
icon="format_color_fill"
|
||||
@ -180,7 +180,7 @@
|
||||
label="{{ 'widgets.gauge.needle-color-end' | translate }}" openOnInput colorClearButton>
|
||||
</tb-color-input>
|
||||
</section>
|
||||
<section fxLayout="row wrap" fxLayoutGap="8px" fxLayoutAlign="start center">
|
||||
<section fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
|
||||
<tb-color-input fxFlex
|
||||
formControlName="colorNeedleShadowUp"
|
||||
icon="format_color_fill"
|
||||
@ -294,7 +294,7 @@
|
||||
<ng-template #radialGauge let-settingsForm="settingsForm">
|
||||
<fieldset class="fields-group" [formGroup]="settingsForm">
|
||||
<legend class="group-title" translate>widgets.gauge.radial-gauge-settings</legend>
|
||||
<section fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="start center">
|
||||
<section fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px" fxLayoutAlign.gt-xs="start center">
|
||||
<mat-form-field fxFlex class="mat-block">
|
||||
<mat-label translate>widgets.gauge.start-ticks-angle</mat-label>
|
||||
<input matInput type="number" min="0" max="360" formControlName="startAngle">
|
||||
@ -314,7 +314,7 @@
|
||||
<ng-template #linearGauge let-settingsForm="settingsForm">
|
||||
<fieldset class="fields-group" [formGroup]="settingsForm">
|
||||
<legend class="group-title" translate>widgets.gauge.linear-gauge-settings</legend>
|
||||
<section fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="start center">
|
||||
<section fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
|
||||
<mat-form-field fxFlex class="mat-block">
|
||||
<mat-label translate>widgets.gauge.bar-stroke-width</mat-label>
|
||||
<input matInput type="number" min="0" formControlName="barStrokeWidth">
|
||||
@ -325,7 +325,7 @@
|
||||
label="{{ 'widgets.gauge.bar-stroke-color' | translate }}" openOnInput colorClearButton>
|
||||
</tb-color-input>
|
||||
</section>
|
||||
<section fxLayout="row wrap" fxLayoutGap="8px" fxLayoutAlign="start center">
|
||||
<section fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
|
||||
<tb-color-input fxFlex
|
||||
formControlName="colorBar"
|
||||
icon="format_color_fill"
|
||||
@ -337,7 +337,7 @@
|
||||
label="{{ 'widgets.gauge.bar-background-color-end' | translate }}" openOnInput colorClearButton>
|
||||
</tb-color-input>
|
||||
</section>
|
||||
<section fxLayout="row wrap" fxLayoutGap="8px" fxLayoutAlign="start center">
|
||||
<section fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
|
||||
<tb-color-input fxFlex
|
||||
formControlName="colorBarProgress"
|
||||
icon="format_color_fill"
|
||||
|
||||
@ -18,7 +18,7 @@
|
||||
<section class="tb-widget-settings" [formGroup]="digitalGaugeWidgetSettingsForm" fxLayout="column">
|
||||
<fieldset class="fields-group">
|
||||
<legend class="group-title" translate>widgets.gauge.common-settings</legend>
|
||||
<section fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="start center">
|
||||
<section fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px" fxLayoutAlign.gt-xs="start center">
|
||||
<mat-form-field fxFlex class="mat-block">
|
||||
<mat-label translate>widgets.gauge.min-value</mat-label>
|
||||
<input matInput type="number" formControlName="minValue">
|
||||
@ -182,7 +182,7 @@
|
||||
</fieldset>
|
||||
<fieldset class="fields-group">
|
||||
<legend class="group-title" translate>widgets.gauge.unit-title-and-timestamp-settings</legend>
|
||||
<section fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="start center">
|
||||
<section fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px" fxLayoutAlign.gt-xs="start center">
|
||||
<mat-slide-toggle fxFlex formControlName="showUnitTitle">
|
||||
{{ 'widgets.gauge.show-unit-title' | translate }}
|
||||
</mat-slide-toggle>
|
||||
@ -191,7 +191,7 @@
|
||||
<input matInput formControlName="unitTitle">
|
||||
</mat-form-field>
|
||||
</section>
|
||||
<section fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="start center">
|
||||
<section fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px" fxLayoutAlign.gt-xs="start center">
|
||||
<mat-slide-toggle fxFlex formControlName="showTimestamp">
|
||||
{{ 'widgets.gauge.show-timestamp' | translate }}
|
||||
</mat-slide-toggle>
|
||||
|
||||
@ -40,7 +40,7 @@
|
||||
<div fxLayout="column" fxLayoutGap="0.5em">
|
||||
<mat-divider></mat-divider>
|
||||
<section class="tb-widget-settings" fxLayout="column">
|
||||
<section fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="start center">
|
||||
<section fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px" fxLayoutAlign.gt-xs="start center">
|
||||
<mat-form-field fxFlex>
|
||||
<mat-label translate>widgets.gauge.highlight-from</mat-label>
|
||||
<input matInput type="number" formControlName="from">
|
||||
|
||||
@ -45,7 +45,7 @@
|
||||
<div fxLayout="column" fxLayoutGap="0.5em">
|
||||
<mat-divider></mat-divider>
|
||||
<section class="tb-widget-settings" fxLayout="column">
|
||||
<section fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="start center">
|
||||
<section fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px" fxLayoutAlign.gt-xs="start center">
|
||||
<mat-form-field fxFlex class="mat-block">
|
||||
<mat-label translate>widgets.gpio.pin</mat-label>
|
||||
<input required matInput type="number" min="1" step="1" formControlName="pin">
|
||||
@ -55,7 +55,7 @@
|
||||
<input required matInput formControlName="label">
|
||||
</mat-form-field>
|
||||
</section>
|
||||
<section fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="start center">
|
||||
<section fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px" fxLayoutAlign.gt-xs="start center">
|
||||
<mat-form-field fxFlex class="mat-block">
|
||||
<mat-label translate>widgets.gpio.row</mat-label>
|
||||
<input required matInput type="number" min="0" step="1" formControlName="row">
|
||||
|
||||
@ -28,7 +28,7 @@
|
||||
</fieldset>
|
||||
<fieldset class="fields-group">
|
||||
<legend class="group-title" translate>widgets.input-widgets.checkbox-settings</legend>
|
||||
<section fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="start center">
|
||||
<section fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px" fxLayoutAlign.gt-xs="start center">
|
||||
<mat-form-field fxFlex class="mat-block">
|
||||
<mat-label translate>widgets.input-widgets.true-label</mat-label>
|
||||
<input matInput formControlName="trueValue">
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
</tb-update-attribute-general-settings>
|
||||
<fieldset class="fields-group">
|
||||
<legend class="group-title" translate>widgets.input-widgets.double-field-settings</legend>
|
||||
<section fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="start center">
|
||||
<section fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px" fxLayoutAlign.gt-xs="start center">
|
||||
<mat-form-field fxFlex class="mat-block">
|
||||
<mat-label translate>widgets.input-widgets.min-value</mat-label>
|
||||
<input matInput type="number" step="any" formControlName="minValue">
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
</tb-update-attribute-general-settings>
|
||||
<fieldset class="fields-group">
|
||||
<legend class="group-title" translate>widgets.input-widgets.integer-field-settings</legend>
|
||||
<section fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="start center">
|
||||
<section fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px" fxLayoutAlign.gt-xs="start center">
|
||||
<mat-form-field fxFlex class="mat-block">
|
||||
<mat-label translate>widgets.input-widgets.min-value</mat-label>
|
||||
<input matInput type="number" step="1" formControlName="minValue">
|
||||
|
||||
@ -37,7 +37,7 @@
|
||||
</fieldset>
|
||||
<fieldset class="fields-group">
|
||||
<legend class="group-title" translate>widgets.input-widgets.attribute-settings</legend>
|
||||
<section fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="start center">
|
||||
<section fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
|
||||
<mat-form-field fxFlex class="mat-block">
|
||||
<mat-label translate>widgets.input-widgets.widget-mode</mat-label>
|
||||
<mat-select formControlName="widgetMode">
|
||||
|
||||
@ -25,7 +25,7 @@
|
||||
<mat-slide-toggle formControlName="showResultMessage" class="slide-block">
|
||||
{{ 'widgets.input-widgets.show-result-message' | translate }}
|
||||
</mat-slide-toggle>
|
||||
<section fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="start center">
|
||||
<section fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px" fxLayoutAlign.gt-xs="start center">
|
||||
<mat-form-field fxFlex class="mat-block">
|
||||
<mat-label translate>widgets.input-widgets.latitude-key-name</mat-label>
|
||||
<input matInput formControlName="latKeyName">
|
||||
@ -47,7 +47,7 @@
|
||||
<mat-slide-toggle formControlName="showLabel" class="slide-block">
|
||||
{{ 'widgets.input-widgets.show-label' | translate }}
|
||||
</mat-slide-toggle>
|
||||
<section fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="start center">
|
||||
<section fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px" fxLayoutAlign.gt-xs="start center">
|
||||
<mat-form-field fxFlex class="mat-block">
|
||||
<mat-label translate>widgets.input-widgets.latitude-label</mat-label>
|
||||
<input matInput formControlName="latLabel">
|
||||
|
||||
@ -45,7 +45,7 @@
|
||||
<mat-checkbox formControlName="updateAllValues" style="margin-bottom: 8px;">
|
||||
{{ 'widgets.input-widgets.update-all-values' | translate }}
|
||||
</mat-checkbox>
|
||||
<section fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="start center">
|
||||
<section fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px" fxLayoutAlign.gt-xs="start center">
|
||||
<mat-form-field fxFlex class="mat-block">
|
||||
<mat-label translate>widgets.input-widgets.save-button-label</mat-label>
|
||||
<input matInput formControlName="saveButtonLabel">
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
</tb-update-attribute-general-settings>
|
||||
<fieldset class="fields-group">
|
||||
<legend class="group-title" translate>widgets.input-widgets.text-field-settings</legend>
|
||||
<section fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="start center">
|
||||
<section fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px" fxLayoutAlign.gt-xs="start center">
|
||||
<mat-form-field fxFlex class="mat-block">
|
||||
<mat-label translate>widgets.input-widgets.min-length</mat-label>
|
||||
<input matInput type="number" step="1" min="0" formControlName="minLength">
|
||||
|
||||
@ -18,7 +18,7 @@
|
||||
<section class="tb-widget-settings" [formGroup]="commonMapSettingsFormGroup">
|
||||
<fieldset class="fields-group">
|
||||
<legend class="group-title" translate>widgets.maps.common-map-settings</legend>
|
||||
<section fxLayout="column" fxLayout.gt-xs="row" fxLayoutGap="8px">
|
||||
<section fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
|
||||
<tb-datasources-key-autocomplete fxFlex
|
||||
[fxShow]="provider === mapProvider.image"
|
||||
required
|
||||
@ -55,7 +55,7 @@
|
||||
</mat-panel-description>
|
||||
</mat-expansion-panel-header>
|
||||
<ng-template matExpansionPanelContent>
|
||||
<section [fxShow]="provider !== mapProvider.image" fxLayout="row" fxLayoutGap="8px">
|
||||
<section [fxShow]="provider !== mapProvider.image" fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
|
||||
<mat-form-field fxFlex class="mat-block">
|
||||
<mat-label translate>widgets.maps.default-map-zoom-level</mat-label>
|
||||
<input matInput type="number" min="0" max="20" step="1" formControlName="defaultZoomLevel">
|
||||
@ -65,7 +65,7 @@
|
||||
<input matInput formControlName="defaultCenterPosition">
|
||||
</mat-form-field>
|
||||
</section>
|
||||
<section fxLayout="column" fxLayout.gt-xs="row" fxLayoutGap.gt-xs="8px" style="margin-bottom: 15px;">
|
||||
<section fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px" style="margin-bottom: 15px;">
|
||||
<section fxFlex fxLayout="column">
|
||||
<mat-slide-toggle formControlName="disableScrollZooming" class="slide-block">
|
||||
{{ 'widgets.maps.disable-scroll-zooming' | translate }}
|
||||
|
||||
@ -34,7 +34,7 @@
|
||||
<mat-slide-toggle formControlName="zoomOnClick" class="slide-block">
|
||||
{{ 'widgets.maps.zoom-on-cluster-click' | translate }}
|
||||
</mat-slide-toggle>
|
||||
<section fxLayout="column" fxLayout.gt-xs="row" fxLayoutGap="8px">
|
||||
<section fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
|
||||
<mat-form-field fxFlex class="mat-block">
|
||||
<mat-label translate>widgets.maps.max-cluster-zoom</mat-label>
|
||||
<input matInput type="number" min="0" max="18" step="1" formControlName="maxZoom">
|
||||
|
||||
@ -18,7 +18,7 @@
|
||||
<section class="tb-widget-settings" [formGroup]="markersSettingsFormGroup">
|
||||
<fieldset class="fields-group">
|
||||
<legend class="group-title" translate>widgets.maps.markers-settings</legend>
|
||||
<section fxLayout="row" fxLayoutGap="8px">
|
||||
<section fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
|
||||
<mat-form-field fxFlex class="mat-block">
|
||||
<mat-label translate>widgets.maps.marker-offset-x</mat-label>
|
||||
<input matInput type="number" formControlName="markerOffsetX">
|
||||
@ -113,7 +113,7 @@
|
||||
functionTitle="{{ 'widgets.maps.tooltip-function' | translate }}"
|
||||
helpId="widget/lib/map/tooltip_fn">
|
||||
</tb-js-func>
|
||||
<section fxLayout="row" fxLayoutGap="8px">
|
||||
<section fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
|
||||
<mat-form-field fxFlex class="mat-block">
|
||||
<mat-label translate>widgets.maps.tooltip-offset-x</mat-label>
|
||||
<input matInput type="number" formControlName="tooltipOffsetX">
|
||||
|
||||
@ -18,7 +18,7 @@
|
||||
<section class="tb-widget-settings" [formGroup]="routeMapSettingsFormGroup">
|
||||
<fieldset class="fields-group">
|
||||
<legend class="group-title" translate>widgets.maps.route-map-settings</legend>
|
||||
<section fxLayout="row" fxLayoutGap="8px">
|
||||
<section fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
|
||||
<mat-form-field fxFlex class="mat-block">
|
||||
<mat-label translate>widgets.maps.stroke-weight</mat-label>
|
||||
<input matInput type="number" min="0" formControlName="strokeWeight">
|
||||
|
||||
@ -417,9 +417,6 @@ export function constructTableCssString(widgetConfig: WidgetConfig): string {
|
||||
'.mat-mdc-table .mat-mdc-header-cell {\n' +
|
||||
'color: ' + mdDarkSecondary + ';\n' +
|
||||
'}\n' +
|
||||
'.mat-mdc-table .mat-mdc-header-cell .mat-sort-header-arrow {\n' +
|
||||
'color: ' + mdDarkDisabled + ';\n' +
|
||||
'}\n' +
|
||||
'.mat-mdc-table .mat-mdc-cell, .mat-mdc-table .mat-mdc-header-cell {\n' +
|
||||
'border-bottom-color: ' + mdDarkDivider + ';\n' +
|
||||
'}\n' +
|
||||
|
||||
@ -19,8 +19,8 @@
|
||||
<mat-tab label="{{ 'widget-config.data' | translate }}" *ngIf="widgetType !== widgetTypes.static">
|
||||
<div [formGroup]="dataSettings" class="mat-content mat-padding" fxLayout="column" fxLayoutGap="8px">
|
||||
<div *ngIf="displayTimewindowConfig()" fxFlex="100"
|
||||
fxLayout.xs="column" fxLayoutGap="8px" fxLayoutAlign.xs="center" fxLayout="row" fxLayoutAlign="start center">
|
||||
<div fxLayout="column" fxLayoutGap="8px" fxFlex.gt-xs>
|
||||
fxLayout.xs="column" fxLayoutGap.gt-xs="8px" fxLayoutAlign.xs="center" fxLayout="row" fxLayoutAlign="start center">
|
||||
<div fxLayout="column" fxFlex.gt-xs>
|
||||
<mat-checkbox formControlName="useDashboardTimewindow">
|
||||
{{ 'widget-config.use-dashboard-timewindow' | translate }}
|
||||
</mat-checkbox>
|
||||
@ -29,7 +29,7 @@
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
<section fxLayout="row" fxLayoutAlign="start center" fxLayout.lt-lg="column" fxLayoutAlign.lt-lg="center start"
|
||||
fxFlex.gt-xs style="margin-bottom: 16px;">
|
||||
fxFlex.gt-xs>
|
||||
<span [ngClass]="{'tb-disabled-label': dataSettings.get('useDashboardTimewindow').value}" translate
|
||||
style="padding-right: 8px;">widget-config.timewindow</span>
|
||||
<tb-timewindow asButton="true"
|
||||
@ -43,7 +43,7 @@
|
||||
</div>
|
||||
<div *ngIf="widgetType === widgetTypes.alarm" fxLayout="column" fxLayoutAlign="center">
|
||||
<div fxLayout="column" fxLayoutAlign="center" fxLayout.gt-xs="row" fxLayoutAlign.gt-xs="start center"
|
||||
fxLayoutGap="8px">
|
||||
fxLayoutGap.gt-xs="8px">
|
||||
<mat-form-field fxFlex class="mat-block" floatLabel="always">
|
||||
<mat-label translate>alarm.alarm-status-list</mat-label>
|
||||
<mat-select formControlName="alarmStatusList" multiple
|
||||
@ -63,8 +63,7 @@
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div fxLayout="column" fxLayoutAlign="center" fxLayout.gt-xs="row" fxLayoutAlign.gt-xs="start center"
|
||||
fxLayoutGap="8px">
|
||||
<div fxLayout="column" fxLayoutAlign="center" fxLayout.gt-xs="row" fxLayoutAlign.gt-xs="start center">
|
||||
<mat-form-field fxFlex class="mat-block" floatLabel="always">
|
||||
<mat-label translate>alarm.alarm-type-list</mat-label>
|
||||
<mat-chip-grid #alarmTypeChipList formControlName="alarmTypeList">
|
||||
@ -355,7 +354,7 @@
|
||||
<mat-slide-toggle class="mat-slide" formControlName="showTitle">
|
||||
{{ 'widget-config.display-title' | translate }}
|
||||
</mat-slide-toggle>
|
||||
<div fxLayout.xs="column" fxLayout="row" fxLayoutGap="8px">
|
||||
<div fxLayout.xs="column" fxLayout="row" fxLayoutGap.gt-xs="8px">
|
||||
<mat-form-field fxFlex>
|
||||
<mat-label translate>widget-config.title</mat-label>
|
||||
<input matInput formControlName="title">
|
||||
@ -365,12 +364,12 @@
|
||||
<input matInput formControlName="titleTooltip">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<fieldset class="fields-group" fxLayoutGap="8px" style="margin: 0">
|
||||
<fieldset class="fields-group" fxLayoutGap.gt-xs="8px" style="margin: 0">
|
||||
<legend class="group-title" translate>widget-config.title-icon</legend>
|
||||
<mat-slide-toggle class="mat-slide" formControlName="showTitleIcon">
|
||||
{{ 'widget-config.display-icon' | translate }}
|
||||
</mat-slide-toggle>
|
||||
<div fxLayout.xs="column" fxLayout="row wrap" fxLayoutGap="8px">
|
||||
<div fxLayout.xs="column" fxLayout="row" fxLayoutGap.gt-xs="8px">
|
||||
<tb-material-icon-select fxFlex
|
||||
formControlName="titleIcon">
|
||||
</tb-material-icon-select>
|
||||
@ -404,8 +403,8 @@
|
||||
</fieldset>
|
||||
<fieldset class="fields-group">
|
||||
<legend class="group-title" translate>widget-config.widget-style</legend>
|
||||
<div fxLayout="column" fxLayout.gt-md="row wrap" fxFlex fxLayoutGap="8px" class="tb-widget-style">
|
||||
<div fxLayout.xs="column" fxLayout="row" fxLayoutGap="8px" fxFlex>
|
||||
<div fxLayout="column" fxLayout.gt-md="row wrap" fxFlex fxLayoutGap.gt-xs="8px" class="tb-widget-style">
|
||||
<div fxLayout.xs="column" fxLayout="row" fxLayoutGap.gt-xs="8px" fxFlex>
|
||||
<tb-color-input fxFlex
|
||||
label="{{'widget-config.background-color' | translate}}"
|
||||
icon="format_color_fill"
|
||||
@ -419,7 +418,7 @@
|
||||
formControlName="color">
|
||||
</tb-color-input>
|
||||
</div>
|
||||
<div fxLayout.xs="column" fxLayout="row" fxLayoutGap="8px" fxFlex>
|
||||
<div fxLayout.xs="column" fxLayout="row" fxLayoutGap.gt-xs="8px" fxFlex>
|
||||
<mat-form-field fxFlex>
|
||||
<mat-label translate>widget-config.padding</mat-label>
|
||||
<input matInput formControlName="padding">
|
||||
@ -477,8 +476,8 @@
|
||||
<fieldset [formGroup]="layoutSettings" class="fields-group fields-group-slider">
|
||||
<legend class="group-title" translate>widget-config.mobile-mode-settings</legend>
|
||||
<mat-expansion-panel class="tb-settings">
|
||||
<mat-expansion-panel-header>
|
||||
<mat-panel-title fxLayout.xs="column" fxLayoutAlign.xs="center start" fxLayout="row" fxLayoutGap="8px" fxFlex="70">
|
||||
<mat-expansion-panel-header style="height: fit-content;">
|
||||
<mat-panel-title fxLayout.xs="column" fxLayoutAlign.xs="center start" fxLayout="row" fxLayoutGap.gt-xs="8px" fxFlex="70">
|
||||
<mat-slide-toggle formControlName="mobileHide" (click)="$event.stopPropagation()" fxLayoutAlign="center">
|
||||
{{ 'widget-config.mobile-hide' | translate }}
|
||||
</mat-slide-toggle>
|
||||
@ -491,14 +490,14 @@
|
||||
</mat-panel-description>
|
||||
</mat-expansion-panel-header>
|
||||
<ng-template matExpansionPanelContent>
|
||||
<div fxLayout.xs="column" fxLayout="row" fxLayoutGap="8px" fxFlex>
|
||||
<div fxLayout.xs="column" fxLayout="row" fxLayoutGap.gt-xs="8px" fxFlex>
|
||||
<mat-form-field fxFlex>
|
||||
<mat-label translate>widget-config.order</mat-label>
|
||||
<input matInput formControlName="mobileOrder" type="number" step="1">
|
||||
</mat-form-field>
|
||||
<mat-form-field fxFlex>
|
||||
<mat-label translate>widget-config.height</mat-label>
|
||||
<input matInput formControlName="mobileHeight" type="number" min="1" max="10" step="1">
|
||||
<input matInput formControlName="mobileHeight" type="number" min="1" step="1">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
@ -255,7 +255,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
|
||||
});
|
||||
this.layoutSettings = this.fb.group({
|
||||
mobileOrder: [null, [Validators.pattern(/^-?[0-9]+$/)]],
|
||||
mobileHeight: [null, [Validators.min(1), Validators.max(10), Validators.pattern(/^\d*$/)]],
|
||||
mobileHeight: [null, [Validators.min(1), Validators.pattern(/^\d*$/)]],
|
||||
mobileHide: [false],
|
||||
desktopHide: [false]
|
||||
});
|
||||
|
||||
@ -15,172 +15,172 @@
|
||||
limitations under the License.
|
||||
|
||||
-->
|
||||
<div>
|
||||
<mat-toolbar color="primary">
|
||||
<h2 translate>device.add-device-text</h2>
|
||||
<span fxFlex></span>
|
||||
<div [tb-help]="'devices'"></div>
|
||||
<button mat-icon-button
|
||||
(click)="cancel()"
|
||||
type="button">
|
||||
<mat-icon class="material-icons">close</mat-icon>
|
||||
</button>
|
||||
</mat-toolbar>
|
||||
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
|
||||
</mat-progress-bar>
|
||||
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
|
||||
<div mat-dialog-content>
|
||||
<mat-horizontal-stepper [linear]="true" [labelPosition]="labelPosition" #addDeviceWizardStepper (selectionChange)="changeStep($event)">
|
||||
<ng-template matStepperIcon="edit">
|
||||
<mat-icon>check</mat-icon>
|
||||
</ng-template>
|
||||
<mat-step [stepControl]="deviceWizardFormGroup">
|
||||
<form [formGroup]="deviceWizardFormGroup" style="padding-bottom: 16px;">
|
||||
<ng-template matStepLabel>{{ 'device.wizard.device-details' | translate}}</ng-template>
|
||||
<fieldset [disabled]="isLoading$ | async">
|
||||
<mat-form-field class="mat-block">
|
||||
<mat-label translate>device.name</mat-label>
|
||||
<input matInput formControlName="name" required>
|
||||
<mat-error *ngIf="deviceWizardFormGroup.get('name').hasError('required')">
|
||||
{{ 'device.name-required' | translate }}
|
||||
</mat-error>
|
||||
<mat-error *ngIf="deviceWizardFormGroup.get('name').hasError('maxlength')">
|
||||
{{ 'device.name-max-length' | translate }}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="mat-block">
|
||||
<mat-label translate>device.label</mat-label>
|
||||
<input matInput formControlName="label">
|
||||
<mat-error *ngIf="deviceWizardFormGroup.get('label').hasError('maxlength')">
|
||||
{{ 'device.label-max-length' | translate }}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
<div fxLayout="row" fxLayoutGap="16px">
|
||||
<mat-radio-group fxLayout="column" formControlName="addProfileType" fxLayoutAlign="space-around">
|
||||
<mat-radio-button [value]="0" color="primary">
|
||||
<span translate>device.wizard.existing-device-profile</span>
|
||||
</mat-radio-button>
|
||||
<mat-radio-button [value]="1" color="primary">
|
||||
<span translate>device.wizard.new-device-profile</span>
|
||||
</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
<div fxLayout="column">
|
||||
<tb-device-profile-autocomplete
|
||||
[required]="!createProfile"
|
||||
formControlName="deviceProfileId"
|
||||
[ngClass]="{invisible: deviceWizardFormGroup.get('addProfileType').value !== 0}"
|
||||
[addNewProfile]="false"
|
||||
[selectDefaultProfile]="true"
|
||||
[editProfileEnabled]="false"
|
||||
(deviceProfileChanged)="deviceProfileChanged($event)">
|
||||
</tb-device-profile-autocomplete>
|
||||
<mat-form-field fxFlex class="mat-block"
|
||||
[ngClass]="{invisible: deviceWizardFormGroup.get('addProfileType').value !== 1}">
|
||||
<mat-label translate>device-profile.new-device-profile-name</mat-label>
|
||||
<input matInput formControlName="newDeviceProfileTitle"
|
||||
[required]="createProfile">
|
||||
<mat-error *ngIf="deviceWizardFormGroup.get('newDeviceProfileTitle').hasError('required')">
|
||||
{{ 'device-profile.new-device-profile-name-required' | translate }}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div fxLayout="column" fxLayoutAlign="flex-end start">
|
||||
<tb-rule-chain-autocomplete
|
||||
[ngClass]="{invisible: deviceWizardFormGroup.get('addProfileType').value !== 1}"
|
||||
labelText="device-profile.default-rule-chain"
|
||||
formControlName="defaultRuleChainId">
|
||||
</tb-rule-chain-autocomplete>
|
||||
</div>
|
||||
<div fxLayout="column" fxLayoutAlign="flex-end start">
|
||||
<tb-queue-autocomplete
|
||||
[ngClass]="{invisible: deviceWizardFormGroup.get('addProfileType').value !== 1}"
|
||||
[queueType]="serviceType"
|
||||
formControlName="defaultQueueName">
|
||||
</tb-queue-autocomplete>
|
||||
</div>
|
||||
<mat-toolbar color="primary">
|
||||
<h2 translate>device.add-device-text</h2>
|
||||
<span fxFlex></span>
|
||||
<div [tb-help]="'devices'"></div>
|
||||
<button mat-icon-button
|
||||
(click)="cancel()"
|
||||
type="button">
|
||||
<mat-icon class="material-icons">close</mat-icon>
|
||||
</button>
|
||||
</mat-toolbar>
|
||||
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
|
||||
</mat-progress-bar>
|
||||
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
|
||||
<div mat-dialog-content>
|
||||
<mat-horizontal-stepper [linear]="true" [labelPosition]="labelPosition" #addDeviceWizardStepper (selectionChange)="changeStep($event)">
|
||||
<ng-template matStepperIcon="edit">
|
||||
<mat-icon>check</mat-icon>
|
||||
</ng-template>
|
||||
<mat-step [stepControl]="deviceWizardFormGroup">
|
||||
<form [formGroup]="deviceWizardFormGroup" style="padding-bottom: 16px;">
|
||||
<ng-template matStepLabel>{{ 'device.wizard.device-details' | translate}}</ng-template>
|
||||
<fieldset [disabled]="isLoading$ | async">
|
||||
<mat-form-field class="mat-block">
|
||||
<mat-label translate>device.name</mat-label>
|
||||
<input matInput formControlName="name" required>
|
||||
<mat-error *ngIf="deviceWizardFormGroup.get('name').hasError('required')">
|
||||
{{ 'device.name-required' | translate }}
|
||||
</mat-error>
|
||||
<mat-error *ngIf="deviceWizardFormGroup.get('name').hasError('maxlength')">
|
||||
{{ 'device.name-max-length' | translate }}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="mat-block">
|
||||
<mat-label translate>device.label</mat-label>
|
||||
<input matInput formControlName="label">
|
||||
<mat-error *ngIf="deviceWizardFormGroup.get('label').hasError('maxlength')">
|
||||
{{ 'device.label-max-length' | translate }}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
<div fxLayout="row" fxLayoutGap="16px">
|
||||
<mat-radio-group fxLayout="column" formControlName="addProfileType" fxLayoutAlign="space-around">
|
||||
<mat-radio-button [value]="0" color="primary">
|
||||
<span translate>device.wizard.existing-device-profile</span>
|
||||
</mat-radio-button>
|
||||
<mat-radio-button [value]="1" color="primary">
|
||||
<span translate>device.wizard.new-device-profile</span>
|
||||
</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
<div fxLayout="column">
|
||||
<tb-device-profile-autocomplete
|
||||
[required]="!createProfile"
|
||||
formControlName="deviceProfileId"
|
||||
[ngClass]="{invisible: deviceWizardFormGroup.get('addProfileType').value !== 0}"
|
||||
[addNewProfile]="false"
|
||||
[selectDefaultProfile]="true"
|
||||
[editProfileEnabled]="false"
|
||||
(deviceProfileChanged)="deviceProfileChanged($event)">
|
||||
</tb-device-profile-autocomplete>
|
||||
<mat-form-field fxFlex class="mat-block"
|
||||
[ngClass]="{invisible: deviceWizardFormGroup.get('addProfileType').value !== 1}">
|
||||
<mat-label translate>device-profile.new-device-profile-name</mat-label>
|
||||
<input matInput formControlName="newDeviceProfileTitle"
|
||||
[required]="createProfile">
|
||||
<mat-error *ngIf="deviceWizardFormGroup.get('newDeviceProfileTitle').hasError('required')">
|
||||
{{ 'device-profile.new-device-profile-name-required' | translate }}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div fxLayout="row" fxLayoutGap="16px" style="padding-bottom: 16px;">
|
||||
<mat-checkbox formControlName="gateway">
|
||||
{{ 'device.is-gateway' | translate }}
|
||||
</mat-checkbox>
|
||||
<mat-checkbox *ngIf="deviceWizardFormGroup.get('gateway').value"
|
||||
formControlName="overwriteActivityTime">
|
||||
{{ 'device.overwrite-activity-time' | translate }}
|
||||
</mat-checkbox>
|
||||
<div fxLayout="column" fxLayoutAlign="flex-end start">
|
||||
<tb-rule-chain-autocomplete
|
||||
[ngClass]="{invisible: deviceWizardFormGroup.get('addProfileType').value !== 1}"
|
||||
labelText="device-profile.default-rule-chain"
|
||||
formControlName="defaultRuleChainId">
|
||||
</tb-rule-chain-autocomplete>
|
||||
</div>
|
||||
<mat-form-field class="mat-block">
|
||||
<mat-label translate>device.description</mat-label>
|
||||
<textarea matInput formControlName="description" rows="2"></textarea>
|
||||
</mat-form-field>
|
||||
</fieldset>
|
||||
</form>
|
||||
</mat-step>
|
||||
<mat-step [stepControl]="transportConfigFormGroup" [optional]="true" *ngIf="createProfile">
|
||||
<form [formGroup]="transportConfigFormGroup" style="padding-bottom: 16px;">
|
||||
<ng-template matStepLabel>{{ 'device-profile.transport-configuration' | translate }}</ng-template><mat-form-field class="mat-block" style="padding-bottom: 14px;">
|
||||
<mat-label translate>device-profile.transport-type</mat-label>
|
||||
<mat-select formControlName="transportType" required>
|
||||
<mat-option *ngFor="let type of deviceTransportTypes" [value]="type">
|
||||
{{deviceTransportTypeTranslations.get(type) | translate}}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
<mat-hint *ngIf="transportConfigFormGroup.get('transportType').value">
|
||||
{{deviceTransportTypeHints.get(transportConfigFormGroup.get('transportType').value) | translate}}
|
||||
</mat-hint>
|
||||
<mat-error *ngIf="transportConfigFormGroup.get('transportType').hasError('required')">
|
||||
{{ 'device-profile.transport-type-required' | translate }}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
<tb-device-profile-transport-configuration
|
||||
formControlName="transportConfiguration"
|
||||
isAdd="true"
|
||||
required>
|
||||
</tb-device-profile-transport-configuration>
|
||||
</form>
|
||||
</mat-step>
|
||||
<mat-step [stepControl]="alarmRulesFormGroup" [optional]="true" *ngIf="createProfile">
|
||||
<form [formGroup]="alarmRulesFormGroup" style="padding-bottom: 16px;">
|
||||
<ng-template matStepLabel>{{'device-profile.alarm-rules-with-count' | translate:
|
||||
{count: alarmRulesFormGroup.get('alarms').value ?
|
||||
alarmRulesFormGroup.get('alarms').value.length : 0} }}</ng-template>
|
||||
<tb-device-profile-alarms
|
||||
formControlName="alarms"
|
||||
[deviceProfileId]="null">
|
||||
</tb-device-profile-alarms>
|
||||
</form>
|
||||
</mat-step>
|
||||
<mat-step [stepControl]="provisionConfigFormGroup" [optional]="true" *ngIf="createProfile">
|
||||
<form [formGroup]="provisionConfigFormGroup" style="padding-bottom: 16px;">
|
||||
<ng-template matStepLabel>{{ 'device-profile.device-provisioning' | translate }}</ng-template>
|
||||
<tb-device-profile-provision-configuration
|
||||
formControlName="provisionConfiguration">
|
||||
</tb-device-profile-provision-configuration>
|
||||
</form>
|
||||
</mat-step>
|
||||
<mat-step [stepControl]="credentialsFormGroup" [optional]="true">
|
||||
<ng-template matStepLabel>{{ 'device.credentials' | translate }}</ng-template>
|
||||
<form [formGroup]="credentialsFormGroup" style="padding-bottom: 16px;">
|
||||
<mat-checkbox style="padding-bottom: 16px;" formControlName="setCredential">{{ 'device.wizard.add-credentials' | translate }}</mat-checkbox>
|
||||
<tb-device-credentials
|
||||
[fxShow]="credentialsFormGroup.get('setCredential').value"
|
||||
[deviceTransportType]="deviceTransportType"
|
||||
formControlName="credential">
|
||||
</tb-device-credentials>
|
||||
</form>
|
||||
</mat-step>
|
||||
<mat-step [stepControl]="customerFormGroup" [optional]="true">
|
||||
<ng-template matStepLabel>{{ 'customer.customer' | translate }}</ng-template>
|
||||
<form [formGroup]="customerFormGroup" style="padding-bottom: 16px;">
|
||||
<tb-entity-autocomplete
|
||||
formControlName="customerId"
|
||||
labelText="device.wizard.customer-to-assign-device"
|
||||
[entityType]="entityType.CUSTOMER">
|
||||
</tb-entity-autocomplete>
|
||||
</form>
|
||||
</mat-step>
|
||||
</mat-horizontal-stepper>
|
||||
</div>
|
||||
<div mat-dialog-actions fxLayout="row">
|
||||
<div fxLayout="column" fxLayoutAlign="flex-end start">
|
||||
<tb-queue-autocomplete
|
||||
[ngClass]="{invisible: deviceWizardFormGroup.get('addProfileType').value !== 1}"
|
||||
[queueType]="serviceType"
|
||||
formControlName="defaultQueueName">
|
||||
</tb-queue-autocomplete>
|
||||
</div>
|
||||
</div>
|
||||
<div fxLayout="row" fxLayoutGap="16px" style="padding-bottom: 16px;">
|
||||
<mat-checkbox formControlName="gateway">
|
||||
{{ 'device.is-gateway' | translate }}
|
||||
</mat-checkbox>
|
||||
<mat-checkbox *ngIf="deviceWizardFormGroup.get('gateway').value"
|
||||
formControlName="overwriteActivityTime">
|
||||
{{ 'device.overwrite-activity-time' | translate }}
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
<mat-form-field class="mat-block">
|
||||
<mat-label translate>device.description</mat-label>
|
||||
<textarea matInput formControlName="description" rows="2"></textarea>
|
||||
</mat-form-field>
|
||||
</fieldset>
|
||||
</form>
|
||||
</mat-step>
|
||||
<mat-step [stepControl]="transportConfigFormGroup" [optional]="true" *ngIf="createProfile">
|
||||
<form [formGroup]="transportConfigFormGroup" style="padding-bottom: 16px;">
|
||||
<ng-template matStepLabel>{{ 'device-profile.transport-configuration' | translate }}</ng-template><mat-form-field class="mat-block" style="padding-bottom: 14px;">
|
||||
<mat-label translate>device-profile.transport-type</mat-label>
|
||||
<mat-select formControlName="transportType" required>
|
||||
<mat-option *ngFor="let type of deviceTransportTypes" [value]="type">
|
||||
{{deviceTransportTypeTranslations.get(type) | translate}}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
<mat-hint *ngIf="transportConfigFormGroup.get('transportType').value">
|
||||
{{deviceTransportTypeHints.get(transportConfigFormGroup.get('transportType').value) | translate}}
|
||||
</mat-hint>
|
||||
<mat-error *ngIf="transportConfigFormGroup.get('transportType').hasError('required')">
|
||||
{{ 'device-profile.transport-type-required' | translate }}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
<tb-device-profile-transport-configuration
|
||||
formControlName="transportConfiguration"
|
||||
isAdd="true"
|
||||
required>
|
||||
</tb-device-profile-transport-configuration>
|
||||
</form>
|
||||
</mat-step>
|
||||
<mat-step [stepControl]="alarmRulesFormGroup" [optional]="true" *ngIf="createProfile">
|
||||
<form [formGroup]="alarmRulesFormGroup" style="padding-bottom: 16px;">
|
||||
<ng-template matStepLabel>{{'device-profile.alarm-rules-with-count' | translate:
|
||||
{count: alarmRulesFormGroup.get('alarms').value ?
|
||||
alarmRulesFormGroup.get('alarms').value.length : 0} }}</ng-template>
|
||||
<tb-device-profile-alarms
|
||||
formControlName="alarms"
|
||||
[deviceProfileId]="null">
|
||||
</tb-device-profile-alarms>
|
||||
</form>
|
||||
</mat-step>
|
||||
<mat-step [stepControl]="provisionConfigFormGroup" [optional]="true" *ngIf="createProfile">
|
||||
<form [formGroup]="provisionConfigFormGroup" style="padding-bottom: 16px;">
|
||||
<ng-template matStepLabel>{{ 'device-profile.device-provisioning' | translate }}</ng-template>
|
||||
<tb-device-profile-provision-configuration
|
||||
formControlName="provisionConfiguration">
|
||||
</tb-device-profile-provision-configuration>
|
||||
</form>
|
||||
</mat-step>
|
||||
<mat-step [stepControl]="credentialsFormGroup" [optional]="true">
|
||||
<ng-template matStepLabel>{{ 'device.credentials' | translate }}</ng-template>
|
||||
<form [formGroup]="credentialsFormGroup" style="padding-bottom: 16px;">
|
||||
<mat-checkbox style="padding-bottom: 16px;" formControlName="setCredential">{{ 'device.wizard.add-credentials' | translate }}</mat-checkbox>
|
||||
<tb-device-credentials
|
||||
[fxShow]="credentialsFormGroup.get('setCredential').value"
|
||||
[deviceTransportType]="deviceTransportType"
|
||||
formControlName="credential">
|
||||
</tb-device-credentials>
|
||||
</form>
|
||||
</mat-step>
|
||||
<mat-step [stepControl]="customerFormGroup" [optional]="true">
|
||||
<ng-template matStepLabel>{{ 'customer.customer' | translate }}</ng-template>
|
||||
<form [formGroup]="customerFormGroup" style="padding-bottom: 16px;">
|
||||
<tb-entity-autocomplete
|
||||
formControlName="customerId"
|
||||
labelText="device.wizard.customer-to-assign-device"
|
||||
[entityType]="entityType.CUSTOMER">
|
||||
</tb-entity-autocomplete>
|
||||
</form>
|
||||
</mat-step>
|
||||
</mat-horizontal-stepper>
|
||||
</div>
|
||||
<div mat-dialog-actions style="padding: 0">
|
||||
<div class="dialog-actions-row" fxFlex fxLayout="row" fxLayoutAlign="end center">
|
||||
<button mat-stroked-button *ngIf="selectedIndex > 0"
|
||||
[disabled]="(isLoading$ | async)"
|
||||
(click)="previousStep()">{{ 'action.back' | translate }}</button>
|
||||
@ -191,8 +191,8 @@
|
||||
[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">
|
||||
<mat-divider style="width: 100%"></mat-divider>
|
||||
<div class="dialog-actions-row" fxFlex fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="end center">
|
||||
<button mat-button
|
||||
[disabled]="(isLoading$ | async)"
|
||||
(click)="cancel()">{{ 'action.cancel' | translate }}</button>
|
||||
|
||||
@ -15,6 +15,15 @@
|
||||
*/
|
||||
@import "../../../../../scss/constants";
|
||||
|
||||
:host {
|
||||
height: 100%;
|
||||
display: grid;
|
||||
|
||||
.dialog-actions-row {
|
||||
padding: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
:host-context(.tb-fullscreen-dialog .mat-mdc-dialog-container) {
|
||||
@media #{$mat-lt-sm} {
|
||||
.mat-mdc-dialog-content {
|
||||
@ -36,31 +45,21 @@
|
||||
|
||||
.mat-stepper-horizontal {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
@media #{$mat-lt-sm} {
|
||||
.mat-step-label {
|
||||
white-space: normal;
|
||||
overflow: visible;
|
||||
.mat-step-text-label {
|
||||
overflow: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.mat-horizontal-stepper-wrapper {
|
||||
flex: 1 1 100%;
|
||||
}
|
||||
|
||||
.mat-horizontal-content-container {
|
||||
height: 530px;
|
||||
height: 680px;
|
||||
max-height: 100%;
|
||||
width: 100%;;
|
||||
overflow-y: auto;
|
||||
scrollbar-gutter: stable;
|
||||
@media #{$mat-gt-sm} {
|
||||
min-width: 800px;
|
||||
}
|
||||
}
|
||||
.mat-horizontal-stepper-content[aria-expanded=true] {
|
||||
height: 100%;
|
||||
form {
|
||||
height: 100%;
|
||||
min-width: 500px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -124,13 +124,13 @@
|
||||
<textarea matInput formControlName="description" rows="2"></textarea>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div style="padding-bottom: 16px;" translate>dashboard.mobile-app-settings</div>
|
||||
<div translate>dashboard.mobile-app-settings</div>
|
||||
<tb-image-input fxFlex
|
||||
label="{{'dashboard.image' | translate}}"
|
||||
maxSizeByte="524288"
|
||||
formControlName="image">
|
||||
</tb-image-input>
|
||||
<mat-checkbox fxFlex formControlName="mobileHide" style="padding-bottom: 16px; padding-top: 16px;">
|
||||
<mat-checkbox fxFlex formControlName="mobileHide">
|
||||
{{ 'dashboard.mobile-hide' | translate }}
|
||||
</mat-checkbox>
|
||||
<mat-form-field class="mat-block">
|
||||
|
||||
@ -15,6 +15,7 @@
|
||||
*/
|
||||
:host {
|
||||
button.tb-add-new-widget {
|
||||
height: auto;
|
||||
padding-right: 12px;
|
||||
font-size: 24px;
|
||||
border-style: dashed;
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
|
||||
import {
|
||||
Component,
|
||||
ElementRef,
|
||||
forwardRef,
|
||||
Inject,
|
||||
Injector,
|
||||
@ -45,8 +46,7 @@ import { WINDOW } from '@core/services/window.service';
|
||||
import { ComponentPortal } from '@angular/cdk/portal';
|
||||
import {
|
||||
DASHBOARD_SELECT_PANEL_DATA,
|
||||
DashboardSelectPanelComponent,
|
||||
DashboardSelectPanelData
|
||||
DashboardSelectPanelComponent
|
||||
} from './dashboard-select-panel.component';
|
||||
import { NULL_UUID } from '@shared/models/id/has-uuid';
|
||||
|
||||
@ -97,6 +97,7 @@ export class DashboardSelectComponent implements ControlValueAccessor, OnInit {
|
||||
private overlay: Overlay,
|
||||
private breakpointObserver: BreakpointObserver,
|
||||
private viewContainerRef: ViewContainerRef,
|
||||
private nativeElement: ElementRef,
|
||||
@Inject(DOCUMENT) private document: Document,
|
||||
@Inject(WINDOW) private window: Window) {
|
||||
}
|
||||
@ -131,77 +132,48 @@ export class DashboardSelectComponent implements ControlValueAccessor, OnInit {
|
||||
}
|
||||
|
||||
openDashboardSelectPanel() {
|
||||
if (this.disabled) {
|
||||
return;
|
||||
}
|
||||
const panelHeight = this.breakpointObserver.isMatched('min-height: 350px') ? 250 : 150;
|
||||
const panelWidth = 300;
|
||||
const position = this.overlay.position();
|
||||
const config = new OverlayConfig({
|
||||
panelClass: 'tb-dashboard-select-panel',
|
||||
backdropClass: 'cdk-overlay-transparent-backdrop',
|
||||
hasBackdrop: true,
|
||||
});
|
||||
const el = this.dashboardSelectPanelOrigin.elementRef.nativeElement;
|
||||
const offset = el.getBoundingClientRect();
|
||||
const scrollTop = this.window.pageYOffset || this.document.documentElement.scrollTop || this.document.body.scrollTop || 0;
|
||||
const scrollLeft = this.window.pageXOffset || this.document.documentElement.scrollLeft || this.document.body.scrollLeft || 0;
|
||||
const bottomY = offset.bottom - scrollTop;
|
||||
const leftX = offset.left - scrollLeft;
|
||||
let originX;
|
||||
let originY;
|
||||
let overlayX;
|
||||
let overlayY;
|
||||
const wHeight = this.document.documentElement.clientHeight;
|
||||
const wWidth = this.document.documentElement.clientWidth;
|
||||
if (bottomY + panelHeight > wHeight) {
|
||||
originY = 'top';
|
||||
overlayY = 'bottom';
|
||||
} else {
|
||||
originY = 'bottom';
|
||||
overlayY = 'top';
|
||||
}
|
||||
if (leftX + panelWidth > wWidth) {
|
||||
originX = 'end';
|
||||
overlayX = 'end';
|
||||
} else {
|
||||
originX = 'start';
|
||||
overlayX = 'start';
|
||||
}
|
||||
const connectedPosition: ConnectedPosition = {
|
||||
originX,
|
||||
originY,
|
||||
overlayX,
|
||||
overlayY
|
||||
};
|
||||
config.positionStrategy = position.flexibleConnectedTo(this.dashboardSelectPanelOrigin.elementRef)
|
||||
.withPositions([connectedPosition]);
|
||||
const overlayRef = this.overlay.create(config);
|
||||
overlayRef.backdropClick().subscribe(() => {
|
||||
overlayRef.dispose();
|
||||
});
|
||||
if (!this.disabled) {
|
||||
const config = new OverlayConfig({
|
||||
panelClass: 'tb-dashboard-select-panel',
|
||||
backdropClass: 'cdk-overlay-transparent-backdrop',
|
||||
hasBackdrop: true
|
||||
});
|
||||
|
||||
const injector = this._createDashboardSelectPanelInjector(
|
||||
overlayRef,
|
||||
{
|
||||
dashboards$: this.dashboards$,
|
||||
dashboardId: this.dashboardId,
|
||||
onDashboardSelected: (dashboardId) => {
|
||||
overlayRef.dispose();
|
||||
this.dashboardId = dashboardId;
|
||||
this.updateView();
|
||||
const connectedPosition: ConnectedPosition = {
|
||||
originX: 'start',
|
||||
originY: 'bottom',
|
||||
overlayX: 'start',
|
||||
overlayY: 'top'
|
||||
};
|
||||
|
||||
config.positionStrategy = this.overlay.position().flexibleConnectedTo(this.nativeElement)
|
||||
.withPositions([connectedPosition]);
|
||||
const overlayRef = this.overlay.create(config);
|
||||
overlayRef.backdropClick().subscribe(() => {
|
||||
overlayRef.dispose();
|
||||
});
|
||||
|
||||
const providers: StaticProvider[] = [
|
||||
{
|
||||
provide: DASHBOARD_SELECT_PANEL_DATA,
|
||||
useValue: {
|
||||
dashboards$: this.dashboards$,
|
||||
dashboardId: this.dashboardId,
|
||||
onDashboardSelected: (dashboardId) => {
|
||||
overlayRef.dispose();
|
||||
this.dashboardId = dashboardId;
|
||||
this.updateView();
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
provide: OverlayRef,
|
||||
useValue: overlayRef
|
||||
}
|
||||
}
|
||||
);
|
||||
overlayRef.attach(new ComponentPortal(DashboardSelectPanelComponent, this.viewContainerRef, injector));
|
||||
}
|
||||
|
||||
private _createDashboardSelectPanelInjector(overlayRef: OverlayRef, data: DashboardSelectPanelData): Injector {
|
||||
const providers: StaticProvider[] = [
|
||||
{provide: DASHBOARD_SELECT_PANEL_DATA, useValue: data},
|
||||
{provide: OverlayRef, useValue: overlayRef}
|
||||
];
|
||||
return Injector.create({parent: this.viewContainerRef.injector, providers});
|
||||
];
|
||||
const injector = Injector.create({parent: this.viewContainerRef.injector, providers});
|
||||
overlayRef.attach(new ComponentPortal(DashboardSelectPanelComponent, this.viewContainerRef, injector));
|
||||
}
|
||||
}
|
||||
|
||||
private updateView() {
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
|
||||
-->
|
||||
<mat-form-field [formGroup]="selectEntityFormGroup" class="mat-block" [appearance]="appearance">
|
||||
<mat-label>{{ entityText | translate }}</mat-label>
|
||||
<mat-label>{{ label | translate }}</mat-label>
|
||||
<input matInput type="text"
|
||||
#entityInput
|
||||
formControlName="entity"
|
||||
@ -42,6 +42,6 @@
|
||||
</mat-option>
|
||||
</mat-autocomplete>
|
||||
<mat-error *ngIf="selectEntityFormGroup.get('entity').hasError('required')">
|
||||
{{ entityRequiredText | translate }}
|
||||
{{ requiredErrorText | translate }}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
@ -36,10 +36,10 @@ import { AliasEntityType, EntityType } from '@shared/models/entity-type.models';
|
||||
import { BaseData } from '@shared/models/base-data';
|
||||
import { EntityId } from '@shared/models/id/entity-id';
|
||||
import { EntityService } from '@core/http/entity.service';
|
||||
import { coerceBooleanProperty } from '@angular/cdk/coercion';
|
||||
import { getCurrentAuthUser } from '@core/auth/auth.selectors';
|
||||
import { Authority } from '@shared/models/authority.enum';
|
||||
import { isEqual } from '@core/utils';
|
||||
import { coerceBoolean } from '@shared/decorators/coerce-boolean';
|
||||
|
||||
@Component({
|
||||
selector: 'tb-entity-autocomplete',
|
||||
@ -61,6 +61,22 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit
|
||||
|
||||
entitySubtypeValue: string;
|
||||
|
||||
entityText: string;
|
||||
|
||||
noEntitiesMatchingText: string;
|
||||
|
||||
entityRequiredText: string;
|
||||
|
||||
filteredEntities: Observable<Array<BaseData<EntityId>>>;
|
||||
|
||||
searchText = '';
|
||||
|
||||
private dirty = false;
|
||||
|
||||
private refresh$ = new Subject<Array<BaseData<EntityId>>>();
|
||||
|
||||
private propagateChange = (v: any) => { };
|
||||
|
||||
@Input()
|
||||
set entityType(entityType: EntityType) {
|
||||
if (this.entityTypeValue !== entityType) {
|
||||
@ -100,16 +116,12 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit
|
||||
@Input()
|
||||
appearance: MatFormFieldAppearance = 'fill';
|
||||
|
||||
private requiredValue: boolean;
|
||||
get required(): boolean {
|
||||
return this.requiredValue;
|
||||
}
|
||||
@Input()
|
||||
set required(value: boolean) {
|
||||
this.requiredValue = coerceBooleanProperty(value);
|
||||
}
|
||||
@coerceBoolean()
|
||||
required: boolean;
|
||||
|
||||
@Input()
|
||||
@coerceBoolean()
|
||||
disabled: boolean;
|
||||
|
||||
@Output()
|
||||
@ -117,19 +129,20 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit
|
||||
|
||||
@ViewChild('entityInput', {static: true}) entityInput: ElementRef;
|
||||
|
||||
entityText: string;
|
||||
noEntitiesMatchingText: string;
|
||||
entityRequiredText: string;
|
||||
get requiredErrorText(): string {
|
||||
if (this.requiredText && this.requiredText.length) {
|
||||
return this.requiredText;
|
||||
}
|
||||
return this.entityRequiredText;
|
||||
}
|
||||
|
||||
filteredEntities: Observable<Array<BaseData<EntityId>>>;
|
||||
get label(): string {
|
||||
if (this.labelText && this.labelText.length) {
|
||||
return this.labelText;
|
||||
}
|
||||
return this.entityText;
|
||||
}
|
||||
|
||||
searchText = '';
|
||||
|
||||
private dirty = false;
|
||||
|
||||
private refresh$ = new Subject<Array<BaseData<EntityId>>>();
|
||||
|
||||
private propagateChange = (v: any) => { };
|
||||
|
||||
constructor(private store: Store<AppState>,
|
||||
public translate: TranslateService,
|
||||
@ -249,12 +262,6 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (this.labelText && this.labelText.length) {
|
||||
this.entityText = this.labelText;
|
||||
}
|
||||
if (this.requiredText && this.requiredText.length) {
|
||||
this.entityRequiredText = this.requiredText;
|
||||
}
|
||||
const currentEntity = this.getCurrentEntity();
|
||||
if (currentEntity) {
|
||||
const currentEntityType = currentEntity.id.entityType;
|
||||
@ -342,12 +349,9 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit
|
||||
map((data) => {
|
||||
if (data) {
|
||||
if (this.excludeEntityIds && this.excludeEntityIds.length) {
|
||||
const excludeEntityIdsSet = new Set(this.excludeEntityIds);
|
||||
const entities: Array<BaseData<EntityId>> = [];
|
||||
data.forEach((entity) => {
|
||||
if (this.excludeEntityIds.indexOf(entity.id.id) === -1) {
|
||||
entities.push(entity);
|
||||
}
|
||||
});
|
||||
data.forEach(entity => !excludeEntityIdsSet.has(entity.id.id) && entities.push(entity));
|
||||
return entities;
|
||||
} else {
|
||||
return data;
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
limitations under the License.
|
||||
|
||||
-->
|
||||
<mat-form-field [formGroup]="subTypeFormGroup" class="mat-block">
|
||||
<mat-form-field [formGroup]="subTypeFormGroup" class="mat-block" [appearance]="appearance">
|
||||
<mat-label>{{ entitySubtypeText | translate }}</mat-label>
|
||||
<input matInput type="text" placeholder="{{ selectEntitySubtypeText | translate }}"
|
||||
#subTypeInput
|
||||
|
||||
@ -37,6 +37,7 @@ import { coerceBooleanProperty } from '@angular/cdk/coercion';
|
||||
import { AssetService } from '@core/http/asset.service';
|
||||
import { EntityViewService } from '@core/http/entity-view.service';
|
||||
import { EdgeService } from '@core/http/edge.service';
|
||||
import { MatFormFieldAppearance } from '@angular/material/form-field';
|
||||
|
||||
@Component({
|
||||
selector: 'tb-entity-subtype-autocomplete',
|
||||
@ -71,6 +72,12 @@ export class EntitySubTypeAutocompleteComponent implements ControlValueAccessor,
|
||||
@Input()
|
||||
disabled: boolean;
|
||||
|
||||
@Input()
|
||||
excludeSubTypes: Array<string>;
|
||||
|
||||
@Input()
|
||||
appearance: MatFormFieldAppearance = 'fill';
|
||||
|
||||
@ViewChild('subTypeInput', {static: true}) subTypeInput: ElementRef;
|
||||
|
||||
selectEntitySubtypeText: string;
|
||||
@ -238,9 +245,14 @@ export class EntitySubTypeAutocompleteComponent implements ControlValueAccessor,
|
||||
break;
|
||||
}
|
||||
if (subTypesObservable) {
|
||||
const excludeSubTypesSet = new Set(this.excludeSubTypes);
|
||||
this.subTypes = subTypesObservable.pipe(
|
||||
catchError(() => of([] as Array<EntitySubtype>)),
|
||||
map(subTypes => subTypes.map(subType => subType.type)),
|
||||
map(subTypes => {
|
||||
const filteredSubTypes: Array<string> = [];
|
||||
subTypes.forEach(subType => !excludeSubTypesSet.has(subType.type) && filteredSubTypes.push(subType.type));
|
||||
return filteredSubTypes;
|
||||
}),
|
||||
publishReplay(1),
|
||||
refCount()
|
||||
);
|
||||
|
||||
@ -18,7 +18,7 @@
|
||||
<div class="tb-html" style="background: #fff;" [ngClass]="{'tb-disabled': disabled, 'fill-height': fillHeight}"
|
||||
tb-fullscreen
|
||||
[fullscreen]="fullscreen" fxLayout="column">
|
||||
<div fxLayout="row" fxLayoutAlign="start center" style="height: 40px;" class="tb-html-toolbar">
|
||||
<div fxLayout="row" fxLayoutAlign="start center" style="min-height: 40px;" class="tb-html-toolbar">
|
||||
<label class="tb-title no-padding" [ngClass]="{'tb-error': !disabled && (hasErrors || required && !modelValue), 'tb-required': !disabled && required}">{{ label }}</label>
|
||||
<span fxFlex></span>
|
||||
<button type='button' *ngIf="!disabled" mat-button class="tidy" (click)="beautifyHtml()">
|
||||
|
||||
@ -23,6 +23,7 @@ $previewSize: 96px !default;
|
||||
|
||||
.tb-container {
|
||||
margin-top: 0;
|
||||
padding: 0;
|
||||
label.tb-title {
|
||||
display: block;
|
||||
padding-bottom: 8px;
|
||||
|
||||
@ -18,7 +18,7 @@
|
||||
<div class="tb-js-func" style="background: #fff;" [ngClass]="{'tb-disabled': disabled, 'fill-height': fillHeight, 'tb-js-func-title': functionTitle}"
|
||||
tb-fullscreen
|
||||
[fullscreen]="fullscreen" fxLayout="column">
|
||||
<div fxLayout="row" fxLayoutAlign="start center" style="height: 40px;" class="tb-js-func-toolbar">
|
||||
<div fxLayout="row" fxLayoutAlign="start center" style="min-height: 40px;" class="tb-js-func-toolbar">
|
||||
<label *ngIf="functionTitle" class="tb-title no-padding"
|
||||
[ngClass]="{'tb-error': !disabled && (hasErrors || !functionValid || required && !modelValue), 'tb-required': !disabled && required}">{{functionTitle + ': f(' + functionArgsString + ')' }}</label>
|
||||
<label *ngIf="!functionTitle" class="tb-title no-padding"
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
/// limitations under the License.
|
||||
///
|
||||
|
||||
import { Component, forwardRef, Input, OnInit } from '@angular/core';
|
||||
import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core';
|
||||
import {
|
||||
AbstractControl,
|
||||
ControlValueAccessor,
|
||||
@ -30,8 +30,9 @@ import {
|
||||
import { PageComponent } from '@shared/components/page.component';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { AppState } from '@core/core.state';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { SubscriptSizing } from '@angular/material/form-field';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'tb-key-val-map',
|
||||
@ -50,7 +51,7 @@ import { SubscriptSizing } from '@angular/material/form-field';
|
||||
}
|
||||
]
|
||||
})
|
||||
export class KeyValMapComponent extends PageComponent implements ControlValueAccessor, OnInit, Validator {
|
||||
export class KeyValMapComponent extends PageComponent implements ControlValueAccessor, OnInit, OnDestroy, Validator {
|
||||
|
||||
@Input() disabled: boolean;
|
||||
|
||||
@ -67,19 +68,27 @@ export class KeyValMapComponent extends PageComponent implements ControlValueAcc
|
||||
|
||||
kvListFormGroup: UntypedFormGroup;
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
private propagateChange = null;
|
||||
|
||||
private valueChangeSubscription: Subscription = null;
|
||||
|
||||
constructor(protected store: Store<AppState>,
|
||||
private fb: UntypedFormBuilder) {
|
||||
super(store);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.kvListFormGroup = this.fb.group({});
|
||||
this.kvListFormGroup.addControl('keyVals',
|
||||
this.fb.array([]));
|
||||
this.kvListFormGroup = this.fb.group({
|
||||
keyVals: this.fb.array([])
|
||||
});
|
||||
|
||||
this.kvListFormGroup.valueChanges.pipe(
|
||||
takeUntil(this.destroy$)
|
||||
).subscribe(() => this.updateModel());
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
keyValsFormArray(): UntypedFormArray {
|
||||
@ -103,9 +112,6 @@ export class KeyValMapComponent extends PageComponent implements ControlValueAcc
|
||||
}
|
||||
|
||||
writeValue(keyValMap: {[key: string]: string}): void {
|
||||
if (this.valueChangeSubscription) {
|
||||
this.valueChangeSubscription.unsubscribe();
|
||||
}
|
||||
const keyValsControls: Array<AbstractControl> = [];
|
||||
if (keyValMap) {
|
||||
for (const property of Object.keys(keyValMap)) {
|
||||
@ -117,10 +123,7 @@ export class KeyValMapComponent extends PageComponent implements ControlValueAcc
|
||||
}
|
||||
}
|
||||
}
|
||||
this.kvListFormGroup.setControl('keyVals', this.fb.array(keyValsControls));
|
||||
this.valueChangeSubscription = this.kvListFormGroup.valueChanges.subscribe(() => {
|
||||
this.updateModel();
|
||||
});
|
||||
this.kvListFormGroup.setControl('keyVals', this.fb.array(keyValsControls), {emitEvent: false});
|
||||
if (this.disabled) {
|
||||
this.kvListFormGroup.disable({emitEvent: false});
|
||||
} else {
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
-->
|
||||
<div class="markdown-content" [ngClass]="{'tb-edit-mode': !readonly}"
|
||||
tb-fullscreen [fullscreen]="fullscreen" (fullscreenChanged)="onFullscreen()">
|
||||
<div fxLayout="row" fxLayoutAlign="start center" style="height: 40px;" class="markdown-editor-toolbar">
|
||||
<div fxLayout="row" fxLayoutAlign="start center" style="min-height: 40px;" class="markdown-editor-toolbar">
|
||||
<label class="tb-title no-padding" [ngClass]="{'tb-error': !disabled && required && !markdownValue, 'tb-required': !disabled && required}">{{ label }}</label>
|
||||
<span fxFlex></span>
|
||||
<button [fxShow]="!editorMode"
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
.markdown-content {
|
||||
min-width: 400px;
|
||||
min-width: 300px;
|
||||
&.tb-edit-mode {
|
||||
.tb-markdown-view-container {
|
||||
border: 1px solid #c0c0c0;
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
|
||||
-->
|
||||
<section [formGroup]="stringItemsForm">
|
||||
<mat-form-field fxFlex class="mat-block" [floatLabel]="floatLabel" [appearance]="appearance">
|
||||
<mat-form-field fxFlex class="mat-block" [floatLabel]="floatLabel" [appearance]="appearance" [subscriptSizing]="subscriptSizing">
|
||||
<mat-label *ngIf="label">{{ label }}</mat-label>
|
||||
<mat-chip-grid #itemsChipList formControlName="items" [required]="required">
|
||||
<mat-chip-row *ngFor="let item of stringItemsList"
|
||||
|
||||
@ -18,7 +18,7 @@ import { Component, forwardRef, Input } from '@angular/core';
|
||||
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
|
||||
import { MatChipInputEvent } from '@angular/material/chips';
|
||||
import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes';
|
||||
import { FloatLabelType, MatFormFieldAppearance } from '@angular/material/form-field';
|
||||
import { FloatLabelType, MatFormFieldAppearance, SubscriptSizing } from '@angular/material/form-field';
|
||||
import { coerceBoolean } from '@shared/decorators/coerce-boolean';
|
||||
|
||||
@Component({
|
||||
@ -79,6 +79,9 @@ export class StringItemsListComponent implements ControlValueAccessor{
|
||||
@coerceBoolean()
|
||||
editable = false;
|
||||
|
||||
@Input()
|
||||
subscriptSizing: SubscriptSizing = 'fixed'
|
||||
|
||||
private propagateChange = (v: any) => { };
|
||||
|
||||
constructor(private fb: FormBuilder) {
|
||||
|
||||
@ -4241,7 +4241,6 @@
|
||||
"maximum-ota-packages-sum-data-size": "Suma màxima de la mida dels fitxers del paquet ota en bytes (0 - il·limitat)",
|
||||
"maximum-ota-package-sum-data-size-required": "Cal suma màxima de la mida dels fitxers del paquet ota.",
|
||||
"maximum-ota-package-sum-data-size-range": "Suma màxima de la mida dels fitxers del paquet ota no pot ser negatiu",
|
||||
"transport-tenant-msg-rate-limit": "Taxa de missatges de transport per propietari.",
|
||||
"transport-tenant-telemetry-msg-rate-limit": "Taxa de missatges de telemetria per propietari.",
|
||||
"transport-tenant-telemetry-data-points-rate-limit": "Taxa de punts de dades per propietari.",
|
||||
"transport-device-msg-rate-limit": "Taxa de missatges de dispositiu.",
|
||||
|
||||
@ -2612,7 +2612,6 @@
|
||||
"maximum-ota-packages-sum-data-size": "Maximální součet velikosti souborů ota balíčků v bajtech (0 - neomezeno)",
|
||||
"maximum-ota-package-sum-data-size-required": "Maximální součet velikosti souborů ota balíčků je povinný.",
|
||||
"maximum-ota-package-sum-data-size-range": "Maximální součet velikosti souborů ota balíčků nemůže být záporný",
|
||||
"transport-tenant-msg-rate-limit": "Limit přenosu zpráv tenanta.",
|
||||
"transport-tenant-telemetry-msg-rate-limit": "Limit přenosu zpráv telemetrie tenanta.",
|
||||
"transport-tenant-telemetry-data-points-rate-limit": "Limit přenosu datových bodů telemetrie tenanta.",
|
||||
"transport-device-msg-rate-limit": "Limit přenosu zpráv zařízení.",
|
||||
|
||||
@ -3130,7 +3130,6 @@
|
||||
"maximum-scheduler-events": "Maks. antal planlægningsbegivenheder (0 – ubegrænset)",
|
||||
"maximum-scheduler-events-required": "Maks. antal planlægningsbegivenheder er påkrævet.",
|
||||
"maximum-scheduler-events-range": "Maks. antal planlægningsbegivenheder kan ikke være negativt",
|
||||
"transport-tenant-msg-rate-limit": "Hastighedsgrænse for transport af lejermeddelelser.",
|
||||
"transport-tenant-telemetry-msg-rate-limit": "Hastighedsgrænse for transport af lejertelemetrimeddelelser.",
|
||||
"transport-tenant-telemetry-data-points-rate-limit": "Hastighedsgrænse for transport af lejertelemetridatapunkter.",
|
||||
"transport-device-msg-rate-limit": "Hastighedsgrænse for transport af enhedsmeddelelser.",
|
||||
|
||||
@ -341,6 +341,7 @@
|
||||
"authentication-settings": "Authentication settings",
|
||||
"auth-method": "Authentication method",
|
||||
"auth-method-username-password": "Password / access token",
|
||||
"auth-method-username-password-hint": "GitHub users <b>must</b> use access <a href='https://github.com/settings/tokens' target='_blank'>tokens</a> with write permissions to the repository.",
|
||||
"auth-method-private-key": "Private key",
|
||||
"password-access-token": "Password / access token",
|
||||
"change-password-access-token": "Change password / access token",
|
||||
@ -2398,6 +2399,7 @@
|
||||
"ignore-case": "ignore case",
|
||||
"value": "Value",
|
||||
"remove-filter": "Remove filter",
|
||||
"duplicate-filter": "Duplicate filter",
|
||||
"preview": "Filter preview",
|
||||
"no-filters": "No filters configured",
|
||||
"add-filter": "Add filter",
|
||||
@ -3535,7 +3537,7 @@
|
||||
"maximum-ota-packages-sum-data-size": "OTA package files sum size",
|
||||
"maximum-ota-package-sum-data-size-required": "OTA package files sum size is required.",
|
||||
"maximum-ota-package-sum-data-size-range": "OTA package files sum size can`t be negative",
|
||||
"transport-tenant-msg-rate-limit": "Transport tenant messages",
|
||||
"rest-requests-for-tenant": "REST requests for tenant",
|
||||
"transport-tenant-telemetry-msg-rate-limit": "Transport tenant telemetry messages",
|
||||
"transport-tenant-telemetry-data-points-rate-limit": "Transport tenant telemetry data points",
|
||||
"transport-device-msg-rate-limit": "Transport device messages",
|
||||
@ -3610,7 +3612,7 @@
|
||||
"edit-transport-device-msg-title": "Edit transport device messages rate limits",
|
||||
"edit-transport-device-telemetry-msg-title": "Edit transport device telemetry messages rate limits",
|
||||
"edit-transport-device-telemetry-data-points-title": "Edit transport device telemetry data points rate limits",
|
||||
"edit-transport-tenant-msg-rate-limit-title": "Edit transport tenant messages rate limits",
|
||||
"edit-tenant-rest-limits-title": "Edit REST requests for tenant rate limits",
|
||||
"edit-customer-rest-limits-title": "Edit REST requests for customer rate limits",
|
||||
"edit-ws-limit-updates-per-session-title": "Edit WS updates per session rate limits",
|
||||
"edit-cassandra-tenant-limits-configuration-title": "Edit Cassandra query for tenant rate limits",
|
||||
|
||||
@ -3092,7 +3092,6 @@
|
||||
"maximum-ota-packages-sum-data-size": "Tamaño máximo de paquetes OTA en bytes (0 - sin límite)",
|
||||
"maximum-ota-package-sum-data-size-required": "Tamaño máximo de paquetes OTA requerido.",
|
||||
"maximum-ota-package-sum-data-size-range": "Tamaño máximo de paquetes OTA no puede ser negativo",
|
||||
"transport-tenant-msg-rate-limit": "Tasa de mensajes de transporte por propietario.",
|
||||
"transport-tenant-telemetry-msg-rate-limit": "Tasa de mensajes de telemetría por propietario.",
|
||||
"transport-tenant-telemetry-data-points-rate-limit": "Tasa de datapoints por propietario.",
|
||||
"transport-device-msg-rate-limit": "Tasa de mensajes de dispositivo.",
|
||||
|
||||
@ -2047,7 +2047,6 @@
|
||||
"maximum-rule-chains": "Maximum number of rule chains (0 - unlimited)",
|
||||
"maximum-rule-chains-required": "Maximum number of rule chains is required.",
|
||||
"maximum-rule-chains-range": "Maximum number of rule chains can't be negative",
|
||||
"transport-tenant-msg-rate-limit": "Transport tenant messages rate limit.",
|
||||
"transport-tenant-telemetry-msg-rate-limit": "Transport tenant telemetry messages rate limit.",
|
||||
"transport-tenant-telemetry-data-points-rate-limit": "Transport tenant telemetry data points rate limit.",
|
||||
"transport-device-msg-rate-limit": "Transport device messages rate limit.",
|
||||
|
||||
@ -2047,7 +2047,6 @@
|
||||
"maximum-rule-chains": "Maximum number of rule chains (0 - unlimited)",
|
||||
"maximum-rule-chains-required": "Maximum number of rule chains is required.",
|
||||
"maximum-rule-chains-range": "Maximum number of rule chains can't be negative",
|
||||
"transport-tenant-msg-rate-limit": "Transport tenant messages rate limit.",
|
||||
"transport-tenant-telemetry-msg-rate-limit": "Transport tenant telemetry messages rate limit.",
|
||||
"transport-tenant-telemetry-data-points-rate-limit": "Transport tenant telemetry data points rate limit.",
|
||||
"transport-device-msg-rate-limit": "Transport device messages rate limit.",
|
||||
|
||||
@ -2631,7 +2631,6 @@
|
||||
"maximum-ota-packages-sum-data-size": "Ota paketi dosyalarının bayt cinsinden maksimum toplamı (0 - sınırsız)",
|
||||
"maximum-ota-package-sum-data-size-required": "Ota paketi dosyalarının maksimum toplamı gerekli.",
|
||||
"maximum-ota-package-sum-data-size-range": "Ota paketi dosyalarının maksimum toplamı negatif olamaz",
|
||||
"transport-tenant-msg-rate-limit": "Taşıma tenant mesajları hız sınırı.",
|
||||
"transport-tenant-telemetry-msg-rate-limit": "Taşıma tenant telemetri iletileri hız sınırı.",
|
||||
"transport-tenant-telemetry-data-points-rate-limit": "Taşıma tenant telemetri veri noktaları hız sınırı.",
|
||||
"transport-device-msg-rate-limit": "Taşıma cihazı mesajları hız sınırı.",
|
||||
|
||||
@ -3213,7 +3213,6 @@
|
||||
"maximum-ota-packages-sum-data-size": "OTA包文件总大小",
|
||||
"maximum-ota-package-sum-data-size-required": "OTA包文件总大小必填。",
|
||||
"maximum-ota-package-sum-data-size-range": "OTA包文件总大小不能为负数",
|
||||
"transport-tenant-msg-rate-limit": "租户消息",
|
||||
"transport-tenant-telemetry-msg-rate-limit": "租户遥测消息",
|
||||
"transport-tenant-telemetry-data-points-rate-limit": "租户遥测数据点",
|
||||
"transport-device-msg-rate-limit": "设备消息",
|
||||
@ -3287,7 +3286,6 @@
|
||||
"edit-transport-device-msg-title": "编辑传输设备消息速率限制",
|
||||
"edit-transport-device-telemetry-msg-title": "编辑传输设备遥测消息速率限制",
|
||||
"edit-transport-device-telemetry-data-points-title": "编辑传输设备遥测数据点速率限制",
|
||||
"edit-transport-tenant-msg-rate-limit-title": "编辑传输租户消息速率限制",
|
||||
"edit-customer-rest-limits-title": "编辑客户REST请求速率限制",
|
||||
"edit-ws-limit-updates-per-session-title": "编辑会话WS更新速率限制",
|
||||
"edit-cassandra-tenant-limits-configuration-title": "编辑租户Cassandra查询速率限制",
|
||||
|
||||
@ -3063,7 +3063,6 @@
|
||||
"maximum-ota-packages-sum-data-size": "OTA套件檔尺寸總計",
|
||||
"maximum-ota-package-sum-data-size-required": "需要OTA套件檔總和大小。",
|
||||
"maximum-ota-package-sum-data-size-range": "OTA套件檔尺寸總計不可為否",
|
||||
"transport-tenant-msg-rate-limit": "傳輸租戶訊息",
|
||||
"transport-tenant-telemetry-msg-rate-limit": "傳輸租戶遙測訊息",
|
||||
"transport-tenant-telemetry-data-points-rate-limit": "傳輸租戶遙測資料端",
|
||||
"transport-device-msg-rate-limit": "傳輸設備訊息",
|
||||
@ -3137,7 +3136,6 @@
|
||||
"edit-transport-device-msg-title": "編輯傳輸設備訊息速率限制",
|
||||
"edit-transport-device-telemetry-msg-title": "編輯傳輸設備遙測訊息速率限制",
|
||||
"edit-transport-device-telemetry-data-points-title": "編輯傳輸設備遙測資料端速率限制",
|
||||
"edit-transport-tenant-msg-rate-limit-title": "編輯傳輸租戶訊息速率限制",
|
||||
"edit-customer-rest-limits-title": "編輯剩餘顧客速率限制",
|
||||
"edit-ws-limit-updates-per-session-title": "編輯每個對談的 WS更新速率限制",
|
||||
"edit-cassandra-tenant-limits-configuration-title": "編輯租戶速率限制的 Cassandra 查詢",
|
||||
|
||||
@ -113,6 +113,10 @@ fieldset {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.fields-group {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
section.tb-header-buttons {
|
||||
position: absolute;
|
||||
top: 86px;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user