Browse Source

UI: Alarm rule details page

pull/14692/head
ArtemDzhereleiko 5 months ago
committed by Vladyslav Prykhodko
parent
commit
96f2c34058
  1. 5
      ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule.module.ts
  2. 59
      ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules-table-config.ts
  3. 4
      ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules-table.component.ts
  4. 157
      ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules.component.html
  5. 199
      ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules.component.ts
  6. 87
      ui-ngx/src/app/modules/home/pages/alarm/alarm-routing.module.ts
  7. 30
      ui-ngx/src/app/modules/home/pages/alarm/alarm-rules-tabs.component.html
  8. 49
      ui-ngx/src/app/modules/home/pages/alarm/alarm-rules-tabs.component.ts
  9. 5
      ui-ngx/src/app/modules/home/pages/alarm/alarm.module.ts
  10. 1
      ui-ngx/src/assets/locale/locale.constant-en_US.json

5
ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule.module.ts

@ -53,6 +53,7 @@ import { AlarmRuleTableHeaderComponent } from "@home/components/alarm-rules/alar
import {
AlarmRuleFilterPredicateNoDataValueComponent
} from "@home/components/alarm-rules/filter/alarm-rule-filter-predicate-no-data-value.component";
import { AlarmRulesComponent } from '@home/components/alarm-rules/alarm-rules.component';
@NgModule({
declarations: [
@ -73,7 +74,8 @@ import {
AlarmRuleDetailsDialogComponent,
AlarmRuleFilterConfigComponent,
AlarmRuleTableHeaderComponent,
AlarmRuleFilterPredicateNoDataValueComponent
AlarmRuleFilterPredicateNoDataValueComponent,
AlarmRulesComponent
],
imports: [
CommonModule,
@ -83,6 +85,7 @@ import {
],
exports: [
AlarmRuleDialogComponent,
AlarmRulesComponent
]
})
export class AlarmRuleModule { }

59
ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules-table-config.ts

@ -21,7 +21,7 @@ import {
EntityTableColumn,
EntityTableConfig
} from '@home/models/entity/entities-table-config.models';
import { EntityType, entityTypeTranslations } from '@shared/models/entity-type.models';
import { EntityType, entityTypeResources, entityTypeTranslations } from '@shared/models/entity-type.models';
import { TranslateService } from '@ngx-translate/core';
import { Direction } from '@shared/models/page/sort-order';
import { MatDialog } from '@angular/material/dialog';
@ -67,6 +67,10 @@ import {
CalculatedFieldScriptTestDialogComponent,
CalculatedFieldTestScriptDialogData
} from "@home/components/calculated-fields/components/test-dialog/calculated-field-script-test-dialog.component";
import { AlarmRulesTabsComponent } from '@home/pages/alarm/alarm-rules-tabs.component';
import { Router } from '@angular/router';
import { EntityAction } from '@home/models/entity/entity-component.models';
import { AlarmRulesComponent } from '@home/components/alarm-rules/alarm-rules.component';
type AlarmRuleTableEntity = CalculatedField | CalculatedFieldInfo;
@ -93,27 +97,26 @@ export class AlarmRulesTableConfig extends EntityTableConfig<AlarmRuleTableEntit
private importExportService: ImportExportService,
private entityDebugSettingsService: EntityDebugSettingsService,
private utilsService: UtilsService,
private router: Router,
public pageMode: boolean = false,
) {
super();
if (this.pageMode) {
this.headerComponent = AlarmRuleTableHeaderComponent;
this.entityComponent = AlarmRulesComponent;
this.entityTabsComponent = AlarmRulesTabsComponent;
this.rowPointer = true;
this.handleRowClick = ($event, model) => {
this.editCalculatedField($event, model);
return true;
};
}
this.tableTitle = this.pageMode ? '' : this.translate.instant('alarm-rule.alarm-rules');
this.detailsPanelEnabled = false;
this.detailsPanelEnabled = this.pageMode;
this.entityResources = entityTypeResources.get(EntityType.CALCULATED_FIELD);
this.entityType = EntityType.CALCULATED_FIELD;
this.entityTranslations = {
type: 'alarm-rule.alarm-rule',
typePlural: 'alarm-rule.alarm-rules',
list: 'alarm-rule.list',
add: 'action.add',
details: 'alarm-rule.details',
noEntities: 'alarm-rule.no-found',
search: 'action.search',
selectedEntities: 'alarm-rule.selected-fields'
@ -121,11 +124,17 @@ export class AlarmRulesTableConfig extends EntityTableConfig<AlarmRuleTableEntit
this.entitiesFetchFunction = (pageLink: PageLink) => this.fetchCalculatedFields(pageLink);
this.addEntity = this.getCalculatedAlarmDialog.bind(this);
this.loadEntity = id => this.calculatedFieldsService.getCalculatedFieldById(id.id);
this.saveEntity = (alarmRule) => this.calculatedFieldsService.saveCalculatedField(alarmRule);
this.deleteEntityTitle = (field) => this.translate.instant('alarm-rule.delete-title', {title: field.name});
this.deleteEntityContent = () => this.translate.instant('alarm-rule.delete-text');
this.deleteEntitiesTitle = count => this.translate.instant('alarm-rule.delete-multiple-title', {count});
this.deleteEntitiesContent = () => this.translate.instant('alarm-rule.delete-multiple-text');
this.deleteEntity = id => this.calculatedFieldsService.deleteCalculatedField(id.id);
this.onEntityAction = action => this.onCFAction(action);
this.addActionDescriptors = [
{
name: this.translate.instant('alarm-rule.create'),
@ -186,14 +195,18 @@ export class AlarmRulesTableConfig extends EntityTableConfig<AlarmRuleTableEntit
isEnabled: () => true,
iconFunction: ({ debugSettings }) => this.entityDebugSettingsService.isDebugActive(debugSettings?.allEnabledUntil) || debugSettings?.failuresEnabled ? 'mdi:bug' : 'mdi:bug-outline',
onAction: ($event, entity) => this.onOpenDebugConfig($event, entity),
},
{
name: this.translate.instant('action.edit'),
icon: 'edit',
isEnabled: () => true,
onAction: ($event, entity) => this.editCalculatedField($event, entity),
}
);
if (!this.pageMode) {
this.cellActionDescriptors.push(
{
name: this.translate.instant('action.edit'),
icon: 'edit',
isEnabled: () => true,
onAction: ($event, entity) => this.editCalculatedField($event, entity),
}
)
}
}
fetchCalculatedFields(pageLink: PageLink): Observable<PageData<AlarmRuleTableEntity>> {
@ -384,4 +397,22 @@ export class AlarmRulesTableConfig extends EntityTableConfig<AlarmRuleTableEntit
return of(null);
}
}
private openCalculatedField($event: Event, entity: AlarmRuleTableEntity) {
$event?.stopPropagation();
const url = this.router.createUrlTree(['alarms', 'alarm-rules', entity.id.id]);
this.router.navigateByUrl(url);
}
private onCFAction(action: EntityAction<AlarmRuleTableEntity>): boolean {
switch (action.action) {
case 'open':
this.openCalculatedField(action.event, action.entity);
return true;
case 'export':
this.exportAlarmRule(action.event, action.entity);
return true;
}
return false;
}
}

4
ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules-table.component.ts

@ -36,7 +36,7 @@ import { EntityDebugSettingsService } from '@home/components/entity/debug/entity
import { DatePipe } from '@angular/common';
import { AlarmRulesTableConfig } from "@home/components/alarm-rules/alarm-rules-table-config";
import { UtilsService } from "@core/services/utils.service";
import { ActivatedRoute } from "@angular/router";
import { ActivatedRoute, Router } from "@angular/router";
@Component({
selector: 'tb-alarm-rules-table',
@ -70,6 +70,7 @@ export class AlarmRulesTableComponent {
private utilsService: UtilsService,
private destroyRef: DestroyRef,
private route: ActivatedRoute,
private router: Router
) {
this.pageMode = !!this.route.snapshot.data.isPage;
effect(() => {
@ -88,6 +89,7 @@ export class AlarmRulesTableComponent {
this.importExportService,
this.entityDebugSettingsService,
this.utilsService,
this.router,
this.pageMode,
);
this.cd.markForCheck();

157
ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules.component.html

@ -0,0 +1,157 @@
<!--
Copyright © 2016-2025 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<div class="tb-details-buttons xs:flex xs:flex-col" *ngIf="!standalone">
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'open')"
[class.!hidden]="isEdit || isDetailsPage">
{{ 'common.open-details-page' | translate }}
</button>
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'export')"
[class.!hidden]="isEdit">
{{ 'action.export' | translate }}
</button>
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'delete')"
[class.!hidden]="hideDelete() || isEdit">
{{ 'action.delete' | translate }}
</button>
</div>
<div class="mat-padding flex flex-1 flex-col" [formGroup]="entityForm">
<div class="tb-form-panel no-border no-padding">
<div class="tb-form-panel no-gap">
<div class="tb-form-panel-title mb-4">{{ 'common.general' | translate }}</div>
<div class="flex items-center gap-2">
<mat-form-field class="flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-label>{{ 'entity-field.title' | translate }}</mat-label>
<input matInput maxlength="255" formControlName="name" required>
@if (entityForm.get('name').errors && entityForm.get('name').touched) {
<mat-error>
@if (entityForm.get('name').hasError('required')) {
{{ 'common.hint.title-required' | translate }}
} @else if (entityForm.get('name').hasError('pattern')) {
{{ 'common.hint.title-pattern' | translate }}
} @else if (entityForm.get('name').hasError('maxlength')) {
{{ 'common.hint.title-max-length' | translate }}
}
</mat-error>
}
<mat-hint></mat-hint>
</mat-form-field>
<tb-entity-debug-settings-button
formControlName="debugSettings"
style="margin-bottom: 22px"
[entityType]="EntityType.CALCULATED_FIELD"
[additionalActionConfig]="additionalDebugActionConfig"
/>
</div>
<tb-entity-select formControlName="entityId"
appearance="outline"
[filterAllowedEntityTypes]="false"
[allowedEntityTypes]="calculatedFieldsEntityTypeList"
(entityChanged)="changeEntity($event)">
</tb-entity-select>
</div>
<ng-container formGroupName="configuration">
<div class="tb-form-panel" [class.disabled]="!isEditValue">
<div class="tb-form-panel-title tb-required">{{ 'calculated-fields.arguments' | translate }}</div>
<tb-calculated-field-arguments-table formControlName="arguments"
[entityId]="entityId"
[tenantId]="tenantId"
[ownerId]="ownerId"
[watchKeyChange]="true"
[entityName]="entityName"/>
</div>
<div class="tb-form-panel" [class.disabled]="!isEditValue">
<div class="tb-form-panel-title">{{ 'alarm-rule.create-conditions' | translate }}</div>
<div class="flex flex-1 flex-col">
<tb-create-cf-alarm-rules formControlName="createRules" [arguments]="arguments" [testScript]="onTestScript.bind(this)">
</tb-create-cf-alarm-rules>
</div>
</div>
<div class="tb-form-panel" [class.disabled]="!isEditValue">
<div class="tb-form-panel-title">{{ 'alarm-rule.clear-condition' | translate }}</div>
<div class="flex flex-row items-center justify-start gap-2 pb-2"
[class.!hidden]="!configFormGroup.get('clearRule').value">
<div class="clear-alarm-rule flex flex-1 flex-row">
<tb-cf-alarm-rule formControlName="clearRule" class="flex-1" [arguments]="arguments" [testScript]="onTestScript.bind(this)" isClearCondition>
</tb-cf-alarm-rule>
</div>
<button mat-icon-button
class="button-icon"
type="button"
(click)="removeClearAlarmRule()"
matTooltip="{{ 'action.remove' | translate }}"
matTooltipPosition="above">
<mat-icon>delete</mat-icon>
</button>
</div>
<div *ngIf="!configFormGroup.get('clearRule').value">
<span translate class="tb-prompt flex items-center justify-center text-base">alarm-rule.no-clear-alarm-rule</span>
</div>
<div [class.!hidden]="configFormGroup.get('clearRule').value">
<button mat-stroked-button color="primary"
[disabled]="!isEditValue"
type="button"
(click)="addClearAlarmRule()">
{{ 'alarm-rule.add-clear-alarm-rule' | translate }}
</button>
</div>
</div>
<div class="tb-form-panel no-gap">
<mat-expansion-panel class="tb-settings" [expanded]="false">
<mat-expansion-panel-header [class.tb-disabled]="!isEditValue">
{{ 'alarm-rule.advanced-settings' | translate }}
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<div class="tb-form-panel stroked no-padding no-gap">
<div class="tb-form-row no-border">
<mat-slide-toggle class="mat-slide margin" formControlName="propagate">
{{ 'alarm-rule.propagate-alarm' | translate }}
</mat-slide-toggle>
</div>
@if (configFormGroup.get('propagate').value) {
<tb-string-items-list appearance="outline"
class="flex flex-1 px-4"
label="{{ 'alarm-rule.alarm-rule-relation-types-list' | translate }}"
placeholder="{{ 'alarm-rule.alarm-rule-relation-types-list' | translate }}"
hint="{{ 'alarm-rule.alarm-rule-relation-types-list-hint' | translate }}"
[predefinedValues]="predefinedTypeValues"
formControlName="propagateRelationTypes">
</tb-string-items-list>
}
</div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide margin" formControlName="propagateToOwner">
{{ 'alarm-rule.propagate-alarm-to-owner' | translate }}
</mat-slide-toggle>
</div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide margin" formControlName="propagateToTenant">
{{ 'alarm-rule.propagate-alarm-to-tenant' | translate }}
</mat-slide-toggle>
</div>
</ng-template>
</mat-expansion-panel>
</div>
</ng-container>
</div>
</div>

199
ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules.component.ts

@ -0,0 +1,199 @@
///
/// Copyright © 2016-2025 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { ChangeDetectorRef, Component, DestroyRef, Inject, Input } from '@angular/core';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { EntityComponent } from '../../components/entity/entity.component';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { EntityType } from '@shared/models/entity-type.models';
import { TranslateService } from '@ngx-translate/core';
import {
CalculatedFieldArgument,
CalculatedFieldConfiguration,
CalculatedFieldInfo,
calculatedFieldsEntityTypeList,
CalculatedFieldType
} from '@shared/models/calculated-field.models';
import { oneSpaceInsideRegex } from '@shared/models/regex.constants';
import { EntityId } from '@shared/models/id/entity-id';
import { switchMap } from 'rxjs/operators';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { BaseData } from '@shared/models/base-data';
import { Observable } from 'rxjs';
import { CalculatedFieldsService } from '@core/http/calculated-fields.service';
import { getCurrentAuthUser } from '@core/auth/auth.selectors';
import {
CalculatedFieldsTableConfig,
CalculatedFieldsTableEntity
} from '@home/components/calculated-fields/calculated-fields-table-config';
import { TenantId } from '@shared/models/id/tenant-id';
import { StringItemsOption } from '@shared/components/string-items-list.component';
import { RelationTypes } from '@shared/models/relation.models';
import { AlarmRule, AlarmRuleConditionType, AlarmRuleExpressionType } from '@shared/models/alarm-rule.models';
@Component({
selector: 'tb-alarm-rules',
templateUrl: './alarm-rules.component.html',
styleUrls: []
})
export class AlarmRulesComponent extends EntityComponent<CalculatedFieldsTableEntity> {
@Input()
standalone = false;
@Input()
entityName: string;
readonly tenantId = getCurrentAuthUser(this.store).tenantId;
readonly ownerId = new TenantId(getCurrentAuthUser(this.store).tenantId);
readonly EntityType = EntityType;
readonly calculatedFieldsEntityTypeList = calculatedFieldsEntityTypeList;
readonly CalculatedFieldType = CalculatedFieldType;
constructor(protected store: Store<AppState>,
protected translate: TranslateService,
@Inject('entity') protected entityValue: CalculatedFieldInfo,
@Inject('entitiesTableConfig') protected entitiesTableConfigValue: CalculatedFieldsTableConfig,
protected fb: FormBuilder,
protected cd: ChangeDetectorRef,
private destroyRef: DestroyRef,
private calculatedFieldsService: CalculatedFieldsService) {
super(store, fb, entityValue, entitiesTableConfigValue, cd);
}
hideDelete() {
if (this.entitiesTableConfig) {
return !this.entitiesTableConfig.deleteEnabled(this.entity);
} else {
return false;
}
}
additionalDebugActionConfig = {
...this.entitiesTableConfig.additionalDebugActionConfig,
action: () => this.entitiesTableConfig.additionalDebugActionConfig.action(
{ id: this.entity.id, ...this.entityFormValue() }, false,
(expression) => {
if (expression) {
this.entityForm.get('configuration').setValue({...this.entityFormValue().configuration, expression});
this.entityForm.get('configuration').markAsDirty();
}
}),
};
get entityId(): EntityId {
return this.entityForm.get('entityId').value;
}
get entitiesTableConfig(): CalculatedFieldsTableConfig {
return this.entitiesTableConfigValue;
}
changeEntity(entity: BaseData<EntityId>): void {
this.entityName = entity?.name;
}
buildForm(entity?: CalculatedFieldInfo): FormGroup {
const form = this.fb.group({
name: ['', [Validators.required, Validators.pattern(oneSpaceInsideRegex), Validators.maxLength(255)]],
entityId: [null],
type: [CalculatedFieldType.ALARM],
debugSettings: [],
configuration: this.fb.group({
type: [CalculatedFieldType.ALARM],
arguments: this.fb.control({}, Validators.required),
propagate: [false],
propagateToOwner: [false],
propagateToTenant: [false],
propagateRelationTypes: [null],
createRules: [null, Validators.required],
clearRule: [null],
}),
});
return form;
}
updateForm(entity: CalculatedFieldInfo) {
const { configuration = {} as CalculatedFieldConfiguration, type = CalculatedFieldType.ALARM, debugSettings = { failuresEnabled: true, allEnabled: true }, entityId = this.entityId, ...value } = entity ?? {};
setTimeout(() => {
this.entityForm.patchValue({ configuration, debugSettings, entityId, ...value }, {emitEvent: false});
});
if (!entityId) {
this.entityForm.get('configuration').disable({emitEvent: false});
}
}
onTestScript(expression?: string): Observable<string> {
const calculatedFieldId = this.entity?.id?.id;
if (calculatedFieldId) {
return this.calculatedFieldsService.getLatestCalculatedFieldDebugEvent(calculatedFieldId, {ignoreLoading: true})
.pipe(
switchMap(event => {
const args = event?.arguments ? JSON.parse(event.arguments) : null;
return this.entitiesTableConfig.getTestScriptDialog(this.entityFormValue(), args, false, expression);
}),
takeUntilDestroyed(this.destroyRef)
)
}
return this.entitiesTableConfig.getTestScriptDialog(this.entityFormValue(), null, false, expression);
}
updateFormState() {
if (this.entityForm) {
if (this.isEditValue) {
this.entityForm.enable({emitEvent: false});
this.entityForm.get('entityId').disable({emitEvent: false});
} else {
this.entityForm.disable({emitEvent: false});
}
}
}
get arguments(): Record<string, CalculatedFieldArgument> {
return this.entityForm.get('configuration.arguments').value;
}
get predefinedTypeValues(): StringItemsOption[] {
return RelationTypes.map(type => ({
name: type,
value: type
}));
}
get configFormGroup(): FormGroup {
return this.entityForm.get('configuration') as FormGroup;
}
public removeClearAlarmRule() {
this.configFormGroup.patchValue({clearRule: null});
this.entityForm.markAsDirty();
}
public addClearAlarmRule() {
const clearAlarmRule: AlarmRule = {
condition: {
type: AlarmRuleConditionType.SIMPLE,
expression: {
type: AlarmRuleExpressionType.SIMPLE
}
}
};
this.configFormGroup.patchValue({clearRule: clearAlarmRule});
}
}

87
ui-ngx/src/app/modules/home/pages/alarm/alarm-routing.module.ts

@ -14,14 +14,64 @@
/// limitations under the License.
///
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { DestroyRef, inject, NgModule } from '@angular/core';
import { ActivatedRouteSnapshot, ResolveFn, Router, RouterModule, RouterStateSnapshot, Routes } from '@angular/router';
import { Authority } from '@shared/models/authority.enum';
import { AlarmTableComponent } from '@home/components/alarm/alarm-table.component';
import { AlarmsMode } from '@shared/models/alarm.models';
import { MenuId } from '@core/services/menu.models';
import { RouterTabsComponent } from "@home/components/router-tabs.component";
import { AlarmRulesTableComponent } from "@home/components/alarm-rules/alarm-rules-table.component";
import { CalculatedFieldsTableComponent } from '@home/components/calculated-fields/calculated-fields-table.component';
import { EntityDetailsPageComponent } from '@home/components/entity/entity-details-page.component';
import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard';
import { entityDetailsPageBreadcrumbLabelFunction } from '@home/pages/home-pages.models';
import { BreadCrumbConfig } from '@shared/components/breadcrumb';
import { CalculatedFieldsTableConfigResolver } from '@home/pages/calculated-fields/calculated-fields-routing.module';
import { CalculatedFieldsTableConfig } from '@home/components/calculated-fields/calculated-fields-table-config';
import { CalculatedFieldsService } from '@core/http/calculated-fields.service';
import { TranslateService } from '@ngx-translate/core';
import { MatDialog } from '@angular/material/dialog';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { DatePipe } from '@angular/common';
import { ImportExportService } from '@shared/import-export/import-export.service';
import { EntityDebugSettingsService } from '@home/components/entity/debug/entity-debug-settings.service';
import { UtilsService } from '@core/services/utils.service';
import { AlarmRulesTableConfig } from '@home/components/alarm-rules/alarm-rules-table-config';
export const AlarmRulesTableConfigResolver: ResolveFn<AlarmRulesTableConfig> =
(_route: ActivatedRouteSnapshot,
_state: RouterStateSnapshot,
calculatedFieldsService = inject(CalculatedFieldsService),
translate = inject(TranslateService),
dialog = inject(MatDialog),
store = inject(Store<AppState>),
datePipe = inject(DatePipe),
destroyRef = inject(DestroyRef),
importExportService = inject(ImportExportService),
entityDebugSettingsService = inject(EntityDebugSettingsService),
utilsService = inject(UtilsService),
router = inject(Router),
) => {
return new AlarmRulesTableConfig(
calculatedFieldsService,
translate,
dialog,
datePipe,
null,
store,
destroyRef,
null,
null,
null,
importExportService,
entityDebugSettingsService,
utilsService,
router,
true,
);
};
const routes: Routes = [
{
@ -57,15 +107,38 @@ const routes: Routes = [
},
{
path: 'alarm-rules',
component: AlarmRulesTableComponent,
data: {
auth: [Authority.TENANT_ADMIN],
title: 'alarm-rule.alarm-rules',
breadcrumb: {
menuId: MenuId.alarm_rules
},
isPage: true,
}
},
children: [
{
path: '',
component: AlarmRulesTableComponent,
data: {
auth: [Authority.TENANT_ADMIN],
title: 'alarm-rule.alarm-rules',
isPage: true,
}
},
{
path: ':entityId',
component: EntityDetailsPageComponent,
canDeactivate: [ConfirmOnExitGuard],
data: {
breadcrumb: {
labelFunction: entityDetailsPageBreadcrumbLabelFunction,
icon: 'tune'
} as BreadCrumbConfig<EntityDetailsPageComponent>,
auth: [Authority.TENANT_ADMIN],
title: 'entity.type-calculated-fields',
},
resolve: {
entitiesTableConfig: AlarmRulesTableConfigResolver
}
}
]
}
]
}

30
ui-ngx/src/app/modules/home/pages/alarm/alarm-rules-tabs.component.html

@ -0,0 +1,30 @@
<!--
Copyright © 2016-2025 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
@if (entity) {
<mat-tab label="{{ 'calculated-fields.events' | translate }}" #eventsTab="matTab">
<tb-event-table [defaultEventType]="DebugEventType.DEBUG_CALCULATED_FIELD"
[disabledEventTypes]="[EventType.LC_EVENT, EventType.ERROR, EventType.STATS]"
[debugEventTypes]="[DebugEventType.DEBUG_CALCULATED_FIELD]"
[active]="eventsTab.isActive"
[tenantId]="entity.tenantId.id"
[entityId]="entity.id"
[disableDebugEventAction]="debugActionDisabled"
(debugEventSelected)="onDebugEventSelected($event)"
></tb-event-table>
</mat-tab>
}

49
ui-ngx/src/app/modules/home/pages/alarm/alarm-rules-tabs.component.ts

@ -0,0 +1,49 @@
///
/// Copyright © 2016-2025 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component } from '@angular/core';
import { EntityTabsComponent } from '../../components/entity/entity-tabs.component';
import { CalculatedFieldEventBody, DebugEventType, EventType } from '@shared/models/event.models';
import type {
CalculatedFieldsTableConfig,
CalculatedFieldsTableEntity
} from '@home/components/calculated-fields/calculated-fields-table-config';
import { debugCfActionEnabled } from '@shared/models/calculated-field.models';
@Component({
selector: 'tb-alarm-rules-tabs',
templateUrl: './alarm-rules-tabs.component.html',
styleUrls: []
})
export class AlarmRulesTabsComponent extends EntityTabsComponent<CalculatedFieldsTableEntity> {
readonly DebugEventType = DebugEventType;
readonly EventType = EventType;
constructor() {
super();
}
get debugActionDisabled(): boolean {
return !debugCfActionEnabled(this.entity);
};
onDebugEventSelected(event: CalculatedFieldEventBody) {
(this.entitiesTableConfig as CalculatedFieldsTableConfig).getTestScriptDialog(this.entity, JSON.parse(event.arguments))
.subscribe((expression) => {
});
};
}

5
ui-ngx/src/app/modules/home/pages/alarm/alarm.module.ts

@ -20,9 +20,12 @@ import { SharedModule } from '@shared/shared.module';
import { HomeDialogsModule } from '../../dialogs/home-dialogs.module';
import { HomeComponentsModule } from '@modules/home/components/home-components.module';
import { AlarmRoutingModule } from '@home/pages/alarm/alarm-routing.module';
import { AlarmRulesTabsComponent } from '@home/pages/alarm/alarm-rules-tabs.component';
@NgModule({
declarations: [],
declarations: [
AlarmRulesTabsComponent
],
imports: [
CommonModule,
SharedModule,

1
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -1394,6 +1394,7 @@
"create": "Create new alarm rule",
"add": "Add alarm rule",
"copy": "Copy alarm rule configuration",
"details": "Alarm rule details",
"no-found": "No alarm rules found",
"list": "{ count, plural, =1 {One alarm rule} other {List of # alarm rules} }",
"selected-fields": "{ count, plural, =1 {1 alarm rule} other {# alarm rules} } selected",

Loading…
Cancel
Save