Merge branch 'develop/3.5.2' into feature/widget-bundles

This commit is contained in:
Igor Kulikov 2023-08-28 18:55:00 +03:00
commit 9a0b0fbefa
24 changed files with 130 additions and 93 deletions

View File

@ -42,6 +42,7 @@ import {
EntityCountCmd,
EntityDataCmd,
IndexedSubscriptionData,
NOT_SUPPORTED,
SubscriptionData,
TelemetrySubscriber
} from '@shared/models/telemetry/telemetry.models';
@ -786,7 +787,7 @@ export class EntityDataSubscription {
private reportNotSupported(keys: AggKey[], isUpdate: boolean) {
const indexedData: IndexedSubscriptionData = [];
for (const key of keys) {
indexedData[key.id] = [[0, 'Not supported!']];
indexedData[key.id] = [[0, NOT_SUPPORTED]];
}
for (let dataIndex = 0; dataIndex < this.pageData.data.length; dataIndex++) {
this.onIndexedData(indexedData, dataIndex, true,

View File

@ -81,6 +81,7 @@ import { distinct, filter, map, switchMap, takeUntil } from 'rxjs/operators';
import { AlarmDataListener } from '@core/api/alarm-data.service';
import { RpcStatus } from '@shared/models/rpc.models';
import { EventEmitter } from '@angular/core';
import { NOT_SUPPORTED } from '@shared/models/telemetry/telemetry.models';
const moment = moment_;
@ -1541,7 +1542,7 @@ export class WidgetSubscription implements IWidgetSubscription {
} else if (prevData && prevData[0] && prevData[0].length > 1 && data.data.length > 0) {
const prevTs = prevData[0][0];
const prevValue = prevData[0][1];
if (prevTs === data.data[0][0] && prevValue === data.data[0][1]) {
if (prevTs === data.data[0][0] && prevValue === data.data[0][1] && data.data[0][1] !== NOT_SUPPORTED) {
update = false;
}
}

View File

@ -161,12 +161,14 @@ import { HasDirtyFlag } from '@core/guards/confirm-on-exit.guard';
})
export class DashboardPageComponent extends PageComponent implements IDashboardController, HasDirtyFlag, OnInit, AfterViewInit, OnDestroy {
private forcePristine = false;
get isDirty(): boolean {
return this.isEdit;
return this.isEdit && !this.forcePristine;
}
set isDirty(value: boolean) {
this.forcePristine = !value;
}
authState: AuthState = getCurrentAuthState(this.store);

View File

@ -43,6 +43,11 @@
(click)="editAssetProfile($event)">
<mat-icon class="material-icons">edit</mat-icon>
</button>
<button mat-button color="primary" matSuffix
(click)="createAssetProfile($event, '')"
*ngIf="!selectAssetProfileFormGroup.get('assetProfile').value && !disabled && addNewProfile">
<span style="white-space: nowrap">{{ 'notification.create-new' | translate }}</span>
</button>
<mat-autocomplete
class="tb-autocomplete"
(closed)="onPanelClosed()"

View File

@ -19,3 +19,9 @@
color: inherit;
}
}
:host ::ng-deep {
.mat-mdc-form-field-icon-suffix {
display: flex;
}
}

View File

@ -46,6 +46,7 @@ import { AssetProfile, AssetProfileInfo } from '@shared/models/asset.models';
import { AssetProfileService } from '@core/http/asset-profile.service';
import { AssetProfileDialogComponent, AssetProfileDialogData } from './asset-profile-dialog.component';
import { SubscriptSizing } from '@angular/material/form-field';
import { coerceBoolean } from '@shared/decorators/coercion';
@Component({
selector: 'tb-asset-profile-autocomplete',
@ -84,14 +85,9 @@ export class AssetProfileAutocompleteComponent implements ControlValueAccessor,
@Input()
showDetailsPageLink = false;
private requiredValue: boolean;
get required(): boolean {
return this.requiredValue;
}
@Input()
set required(value: boolean) {
this.requiredValue = coerceBooleanProperty(value);
}
@coerceBoolean()
required = false;
@Input()
disabled: boolean;

View File

@ -45,7 +45,7 @@
</button>
<button mat-button color="primary" matSuffix
(click)="createDeviceProfile($event, '')"
*ngIf="!selectDeviceProfileFormGroup.get('deviceProfile').value && !disabled && addNewProfile && showCreateNewButton">
*ngIf="!selectDeviceProfileFormGroup.get('deviceProfile').value && !disabled && addNewProfile">
<span style="white-space: nowrap">{{ 'notification.create-new' | translate }}</span>
</button>
<mat-autocomplete

View File

@ -88,10 +88,6 @@ export class DeviceProfileAutocompleteComponent implements ControlValueAccessor,
@coerceBoolean()
addNewProfile = true;
@Input()
@coerceBoolean()
showCreateNewButton = false;
@Input()
showDetailsPageLink = false;

View File

@ -70,7 +70,7 @@
[indeterminate]="alarmsDatasource.selection.hasValue() && !(alarmsDatasource.isAllSelected() | async)">
</mat-checkbox>
</mat-header-cell>
<mat-cell *matCellDef="let alarm">
<mat-cell *matCellDef="let alarm; let row = index" [style]="rowStyle(alarm, row)">
<mat-checkbox (click)="$event.stopPropagation();"
(change)="$event ? alarmsDatasource.toggleSelection(alarm) : null"
[checked]="alarmsDatasource.isSelected(alarm)">
@ -122,7 +122,7 @@
maxWidth: (alarmsDatasource.countCellButtonAction * 48) + 'px',
width: (alarmsDatasource.countCellButtonAction * 48) + 'px' }">
</mat-header-cell>
<mat-cell *matCellDef="let alarm" [ngStyle.gt-md]="{ minWidth: (alarmsDatasource.countCellButtonAction * 48) + 'px',
<mat-cell *matCellDef="let alarm; let row = index" [style]="rowStyle(alarm, row)" [ngStyle.gt-md]="{ minWidth: (alarmsDatasource.countCellButtonAction * 48) + 'px',
maxWidth: (alarmsDatasource.countCellButtonAction * 48) + 'px',
width: (alarmsDatasource.countCellButtonAction * 48) + 'px' }">
<div [fxHide]="showCellActionsMenu" fxShow.gt-md fxFlex fxLayout="row" fxLayoutAlign="end">

View File

@ -52,7 +52,7 @@
maxWidth: (entityDatasource.countCellButtonAction * 48) + 'px',
width: (entityDatasource.countCellButtonAction * 48) + 'px' }">
</mat-header-cell>
<mat-cell *matCellDef="let entity" [ngStyle.gt-md]="{ minWidth: (entityDatasource.countCellButtonAction * 48) + 'px',
<mat-cell *matCellDef="let entity; let row = index" [style]="rowStyle(entity, row)" [ngStyle.gt-md]="{ minWidth: (entityDatasource.countCellButtonAction * 48) + 'px',
maxWidth: (entityDatasource.countCellButtonAction * 48) + 'px',
width: (entityDatasource.countCellButtonAction * 48) + 'px' }">
<div [fxHide]="showCellActionsMenu && entityDatasource.countCellButtonAction !== 1" fxShow.gt-md fxFlex fxLayout="row" fxLayoutAlign="end">

View File

@ -452,7 +452,7 @@ export function constructTableCssString(widgetConfig: WidgetConfig): string {
'.mat-mdc-table .mat-mdc-row .mat-mdc-cell.mat-mdc-table-sticky, .mat-mdc-table .mat-mdc-header-cell.mat-mdc-table-sticky {\n' +
'background-color: ' + origBackgroundColor + ';\n' +
'}\n' +
'.mat-mdc-table .mat-mdc-cell {\n' +
'.mat-mdc-table .mat-mdc-row {\n' +
'color: ' + mdDark + ';\n' +
'background-color: rgba(0, 0, 0, 0);\n' +
'}\n' +

View File

@ -64,7 +64,7 @@
maxWidth: (source.timeseriesDatasource.countCellButtonAction * 48) + 'px',
width: (source.timeseriesDatasource.countCellButtonAction * 48) + 'px' }">
</mat-header-cell>
<mat-cell *matCellDef="let row" [ngStyle.gt-md]="{ minWidth: (source.timeseriesDatasource.countCellButtonAction * 48) + 'px',
<mat-cell *matCellDef="let entity; let row = index" [style]="rowStyle(source, entity, row)" [ngStyle.gt-md]="{ minWidth: (source.timeseriesDatasource.countCellButtonAction * 48) + 'px',
maxWidth: (source.timeseriesDatasource.countCellButtonAction * 48) + 'px',
width: (source.timeseriesDatasource.countCellButtonAction * 48) + 'px' }">
<div [fxHide]="showCellActionsMenu && source.timeseriesDatasource.countCellButtonAction !== 1" fxShow.gt-md fxFlex fxLayout="row" fxLayoutAlign="end">
@ -74,22 +74,22 @@
mat-icon-button [disabled]="isLoading$ | async"
matTooltip="{{ actionDescriptor.displayName }}"
matTooltipPosition="above"
(click)="onActionButtonClick($event, row, actionDescriptor)">
(click)="onActionButtonClick($event, entity, actionDescriptor)">
<mat-icon>{{actionDescriptor.icon}}</mat-icon>
</button>
</ng-container>
</div>
<div fxHide [fxShow.lt-lg]="showCellActionsMenu && source.timeseriesDatasource.countCellButtonAction !== 1" *ngIf="row.hasActions">
<div fxHide [fxShow.lt-lg]="showCellActionsMenu && source.timeseriesDatasource.countCellButtonAction !== 1" *ngIf="entity.hasActions">
<button mat-icon-button
(click)="$event.stopPropagation(); ctx.detectChanges();"
[matMenuTriggerFor]="cellActionsMenu">
<mat-icon class="material-icons">more_vert</mat-icon>
</button>
<mat-menu #cellActionsMenu="matMenu" xPosition="before">
<ng-container *ngFor="let actionDescriptor of row.actionCellButtons; trackBy: trackByActionCellDescriptionId">
<ng-container *ngFor="let actionDescriptor of entity.actionCellButtons; trackBy: trackByActionCellDescriptionId">
<button mat-menu-item *ngIf="actionDescriptor.icon"
[disabled]="isLoading$ | async"
(click)="onActionButtonClick($event, row, actionDescriptor)">
(click)="onActionButtonClick($event, entity, actionDescriptor)">
<mat-icon>{{actionDescriptor.icon}}</mat-icon>
<span>{{ actionDescriptor.displayName }}</span>
</button>

View File

@ -146,11 +146,8 @@ export class DeviceWizardDialogComponent extends DialogComponent<DeviceWizardDia
overwriteActivityTime: this.deviceWizardFormGroup.get('overwriteActivityTime').value,
description: this.deviceWizardFormGroup.get('description').value
},
customerId: null
customerId: this.deviceWizardFormGroup.get('customerId').value
};
if (this.deviceWizardFormGroup.get('customerId').value) {
device.customerId = new CustomerId(this.deviceWizardFormGroup.get('customerId').value);
}
if (this.addDeviceWizardStepper.steps.last.completed || this.addDeviceWizardStepper.selectedIndex > 0) {
return this.deviceService.saveDeviceWithCredentials(deepTrim(device), deepTrim(this.credentialsFormGroup.value.credential)).pipe(
catchError((e: HttpErrorResponse) => {

View File

@ -86,13 +86,6 @@
{{ 'asset.name-max-length' | translate }}
</mat-error>
</mat-form-field>
<tb-asset-profile-autocomplete
[selectDefaultProfile]="isAdd"
required
formControlName="assetProfileId"
[showDetailsPageLink]="true"
(assetProfileUpdated)="onAssetProfileUpdated()">
</tb-asset-profile-autocomplete>
<mat-form-field class="mat-block">
<mat-label translate>asset.label</mat-label>
<input matInput formControlName="label">
@ -100,6 +93,20 @@
{{ 'asset.label-max-length' | translate }}
</mat-error>
</mat-form-field>
<tb-asset-profile-autocomplete
[selectDefaultProfile]="isAdd"
required
formControlName="assetProfileId"
[showDetailsPageLink]="true"
(assetProfileUpdated)="onAssetProfileUpdated()">
</tb-asset-profile-autocomplete>
<tb-entity-autocomplete
*ngIf="isAdd"
useFullEntityId
formControlName="customerId"
labelText="asset.assign-to-customer"
[entityType]="entityType.CUSTOMER">
</tb-entity-autocomplete>
<div formGroupName="additionalInfo">
<mat-form-field class="mat-block">
<mat-label translate>asset.description</mat-label>

View File

@ -69,6 +69,7 @@ export class AssetComponent extends EntityComponent<AssetInfo> {
name: [entity ? entity.name : '', [Validators.required, Validators.maxLength(255)]],
assetProfileId: [entity ? entity.assetProfileId : null, [Validators.required]],
label: [entity ? entity.label : '', Validators.maxLength(255)],
customerId: [entity ? entity.customerId : ''],
additionalInfo: this.fb.group(
{
description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''],
@ -82,6 +83,7 @@ export class AssetComponent extends EntityComponent<AssetInfo> {
this.entityForm.patchValue({name: entity.name});
this.entityForm.patchValue({assetProfileId: entity.assetProfileId});
this.entityForm.patchValue({label: entity.label});
this.entityForm.patchValue({customerId: entity.customerId});
this.entityForm.patchValue({additionalInfo: {description: entity.additionalInfo ? entity.additionalInfo.description : ''}});
}

View File

@ -29,16 +29,12 @@
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
</mat-progress-bar>
<div mat-dialog-content>
<fieldset [disabled]="isLoading$ | async">
<tb-rule-node #tbRuleNode
[ruleNode]="ruleNode"
[ruleChainId]="ruleChainId"
[ruleChainType]="ruleChainType"
[isEdit]="true"
[isAdd]="true"
[isReadOnly]="false">
</tb-rule-node>
</fieldset>
<tb-rule-node #tbRuleNode
[ruleNode]="ruleNode"
[ruleChainId]="ruleChainId"
[ruleChainType]="ruleChainType"
isAdd>
</tb-rule-node>
</div>
<div mat-dialog-actions fxLayoutAlign="end center">
<button mat-button color="primary"

View File

@ -15,7 +15,7 @@
*/
:host {
display: block;
margin-bottom: 16px;
tb-json-object-edit.tb-rule-node-configuration-json {
display: block;
height: 300px;

View File

@ -165,6 +165,9 @@ export class RuleNodeConfigComponent implements ControlValueAccessor, OnInit, On
} else {
this.ruleNodeConfigFormGroup.enable({emitEvent: false});
}
if (this.definedConfigComponent) {
this.definedConfigComponent.disabled = this.disabled;
}
}
writeValue(value: RuleNodeConfiguration): void {
@ -222,6 +225,7 @@ export class RuleNodeConfigComponent implements ControlValueAccessor, OnInit, On
this.definedConfigComponent.ruleChainId = this.ruleChainId;
this.definedConfigComponent.ruleChainType = this.ruleChainType;
this.definedConfigComponent.configuration = this.configuration;
this.definedConfigComponent.disabled = this.disabled;
this.changeSubscription = this.definedConfigComponent.configurationChanged.subscribe((configuration) => {
this.updateModel(configuration);
});

View File

@ -22,44 +22,40 @@
</button>
</div>
<form [formGroup]="ruleNodeFormGroup" class="mat-padding">
<fieldset [disabled]="(isLoading$ | async) || !isEdit || isReadOnly">
<section>
<section class="title-row">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>rulenode.name</mat-label>
<input matInput formControlName="name" required>
<mat-error *ngIf="ruleNodeFormGroup.get('name').hasError('required')
<section class="title-row">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>rulenode.name</mat-label>
<input matInput formControlName="name" required>
<mat-error *ngIf="ruleNodeFormGroup.get('name').hasError('required')
|| ruleNodeFormGroup.get('name').hasError('pattern')">
{{ 'rulenode.name-required' | translate }}
</mat-error>
<mat-error *ngIf="ruleNodeFormGroup.get('name').hasError('maxlength')">
{{ 'rulenode.name-max-length' | translate }}
</mat-error>
</mat-form-field>
<section class="node-setting">
<mat-slide-toggle formControlName="debugMode">
{{ 'rulenode.debug-mode' | translate }}
</mat-slide-toggle>
<mat-slide-toggle *ngIf="isSingletonEditAllowed()" formControlName="singletonMode">
{{ 'rulenode.singleton-mode' | translate }}
</mat-slide-toggle >
</section>
</section>
<tb-rule-node-config #ruleNodeConfigComponent
formControlName="configuration"
[ruleNodeId]="ruleNode.ruleNodeId?.id"
[ruleChainId]="ruleChainId"
[ruleChainType]="ruleChainType"
[nodeDefinition]="ruleNode.component.configurationDescriptor.nodeDefinition"
(initRuleNode)="initRuleNode.emit($event)"
(changeScript)="changeScript.emit($event)">
</tb-rule-node-config>
<div formGroupName="additionalInfo" fxLayout="column" class="description-block">
<mat-form-field class="mat-block">
<mat-label translate>rulenode.rule-node-description</mat-label>
<textarea matInput formControlName="description" rows="1"></textarea>
</mat-form-field>
</div>
{{ 'rulenode.name-required' | translate }}
</mat-error>
<mat-error *ngIf="ruleNodeFormGroup.get('name').hasError('maxlength')">
{{ 'rulenode.name-max-length' | translate }}
</mat-error>
</mat-form-field>
<section class="node-setting">
<mat-slide-toggle formControlName="debugMode">
{{ 'rulenode.debug-mode' | translate }}
</mat-slide-toggle>
<mat-slide-toggle *ngIf="isSingletonEditAllowed()" formControlName="singletonMode">
{{ 'rulenode.singleton-mode' | translate }}
</mat-slide-toggle>
</section>
</fieldset>
</section>
<tb-rule-node-config #ruleNodeConfigComponent
formControlName="configuration"
[ruleNodeId]="ruleNode.ruleNodeId?.id"
[ruleChainId]="ruleChainId"
[ruleChainType]="ruleChainType"
[nodeDefinition]="ruleNode.component.configurationDescriptor.nodeDefinition"
(initRuleNode)="initRuleNode.emit($event)"
(changeScript)="changeScript.emit($event)">
</tb-rule-node-config>
<div formGroupName="additionalInfo" fxLayout="column" class="description-block">
<mat-form-field class="mat-block">
<mat-label translate>rulenode.rule-node-description</mat-label>
<textarea matInput formControlName="description" rows="1"></textarea>
</mat-form-field>
</div>
</form>

View File

@ -22,11 +22,11 @@ import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms
import { FcRuleNode, RuleNodeType } from '@shared/models/rule-node.models';
import { EntityType } from '@shared/models/entity-type.models';
import { Subscription } from 'rxjs';
import { RuleChainService } from '@core/http/rule-chain.service';
import { RuleNodeConfigComponent } from './rule-node-config.component';
import { Router } from '@angular/router';
import { RuleChainType } from '@app/shared/models/rule-chain.models';
import { ComponentClusteringMode } from '@shared/models/component-descriptor.models';
import { coerceBoolean } from '@shared/decorators/coercion';
@Component({
selector: 'tb-rule-node',
@ -47,12 +47,11 @@ export class RuleNodeDetailsComponent extends PageComponent implements OnInit, O
ruleChainType: RuleChainType;
@Input()
isEdit: boolean;
@Input()
isReadOnly: boolean;
@coerceBoolean()
disabled = false;
@Input()
@coerceBoolean()
isAdd = false;
@Output()
@ -70,7 +69,6 @@ export class RuleNodeDetailsComponent extends PageComponent implements OnInit, O
constructor(protected store: Store<AppState>,
private fb: UntypedFormBuilder,
private ruleChainService: RuleChainService,
private router: Router) {
super(store);
this.ruleNodeFormGroup = this.fb.group({});
@ -99,6 +97,9 @@ export class RuleNodeDetailsComponent extends PageComponent implements OnInit, O
} else {
this.ruleNodeFormGroup = this.fb.group({});
}
if (this.disabled) {
this.ruleNodeFormGroup.disable({emitEvent: false});
}
}
private updateRuleNode() {
@ -108,6 +109,9 @@ export class RuleNodeDetailsComponent extends PageComponent implements OnInit, O
}
ngOnInit(): void {
if (this.disabled) {
this.ruleNodeFormGroup.disable({emitEvent: false});
}
}
ngOnChanges(changes: SimpleChanges): void {

View File

@ -110,8 +110,6 @@
[ruleNode]="editingRuleNode"
[ruleChainId]="ruleChain.id?.id"
[ruleChainType]="ruleChainType"
[isEdit]="true"
[isReadOnly]="false"
(initRuleNode)="onRuleNodeInit()"
(changeScript)="switchToFirstTab()">
</tb-rule-node>

View File

@ -74,6 +74,7 @@ export interface IRuleNodeConfigurationComponent {
ruleNodeId: string;
ruleChainId: string;
hasScript: boolean;
disabled: boolean;
testScriptLabel?: string;
changeScript?: EventEmitter<void>;
ruleChainType: RuleChainType;
@ -101,6 +102,14 @@ export abstract class RuleNodeConfigurationComponent extends PageComponent imple
private configurationSet = false;
set disabled(value: boolean) {
if (value) {
this.configForm().disable({emitEvent: false});
} else {
this.configForm().enable({emitEvent: false});
}
};
set configuration(value: RuleNodeConfiguration) {
this.configurationValue = value;
if (!this.configurationSet) {

View File

@ -37,6 +37,8 @@ import { isUndefined } from '@core/utils';
import { CmdWrapper, WsSubscriber } from '@shared/models/websocket/websocket.models';
import { TelemetryWebsocketService } from '@core/ws/telemetry-websocket.service';
export const NOT_SUPPORTED = 'Not supported!';
export enum DataKeyType {
timeseries = 'timeseries',
attribute = 'attribute',

View File

@ -692,6 +692,21 @@ mat-label {
margin: 0;
}
.tb-table-widget {
.mat-mdc-table {
.mat-mdc-cell {
background: inherit;
color: inherit;
font-size: inherit;
font-family: inherit;
font-weight: inherit;
line-height: inherit;
letter-spacing: inherit;
text-transform: inherit;
}
}
}
.mat-mdc-footer-row::after, .mat-mdc-header-row::after, .mat-mdc-row::after {
content: none;
}