diff --git a/ui-ngx/src/app/shared/components/dashboard-state-autocomplete.component.html b/ui-ngx/src/app/shared/components/dashboard-state-autocomplete.component.html
index 5ce3ff51ca..82b6f44e3c 100644
--- a/ui-ngx/src/app/shared/components/dashboard-state-autocomplete.component.html
+++ b/ui-ngx/src/app/shared/components/dashboard-state-autocomplete.component.html
@@ -15,11 +15,13 @@
limitations under the License.
-->
-
-
+ {{ label }}
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/ui-ngx/src/app/shared/components/dashboard-state-autocomplete.component.ts b/ui-ngx/src/app/shared/components/dashboard-state-autocomplete.component.ts
index f6e93d8aea..1f68ff5ef4 100644
--- a/ui-ngx/src/app/shared/components/dashboard-state-autocomplete.component.ts
+++ b/ui-ngx/src/app/shared/components/dashboard-state-autocomplete.component.ts
@@ -47,12 +47,11 @@ export class DashboardStateAutocompleteComponent implements ControlValueAccessor
private propagateChange = (v: any) => { };
-
@Input()
- placeholder: string;
+ label: string = this.translate.instant('widget-action.target-dashboard-state');
@Input()
- floatLabel: FloatLabelType = 'auto';
+ placeholder: string;
private requiredValue: boolean;
get required(): boolean {
@@ -66,17 +65,20 @@ export class DashboardStateAutocompleteComponent implements ControlValueAccessor
@Input()
disabled: boolean;
- private dashboardIdValue: string;
+ private dashboardIdValue: string = null;
get dashboardId(): string {
return this.dashboardIdValue;
}
+ @Input()
set dashboardId(value: string) {
- this.dashboardIdValue = value;
- this.clearDashboardStateCache();
- this.searchText = '';
- this.selectDashboardStateFormGroup.get('dashboardStateId').patchValue('', {emitEvent: false});
- this.dirty = true;
+ if (this.dashboardIdValue !== value) {
+ this.dashboardIdValue = value;
+ this.clearDashboardStateCache();
+ this.searchText = '';
+ this.selectDashboardStateFormGroup.get('dashboardStateId').patchValue('', {emitEvent: false});
+ this.dirty = true;
+ }
}
@ViewChild('dashboardStateInput', {static: true}) dashboardStateInput: ElementRef;
@@ -113,7 +115,7 @@ export class DashboardStateAutocompleteComponent implements ControlValueAccessor
debounceTime(150),
tap(value => {
let modelValue;
- if (!value || !this.latestDashboardStates.includes(value)) {
+ if (!value || !this.latestDashboardStates?.includes(value)) {
modelValue = null;
} else {
modelValue = value;
@@ -160,7 +162,7 @@ export class DashboardStateAutocompleteComponent implements ControlValueAccessor
onFocus() {
if (this.dirty) {
- this.selectDashboardStateFormGroup.get('dashboard').updateValueAndValidity({onlySelf: true});
+ this.selectDashboardStateFormGroup.get('dashboardStateId').updateValueAndValidity({onlySelf: true});
this.dirty = false;
}
}
diff --git a/ui-ngx/src/app/shared/components/notification/notification.component.ts b/ui-ngx/src/app/shared/components/notification/notification.component.ts
index 22a514aa08..6732c8d956 100644
--- a/ui-ngx/src/app/shared/components/notification/notification.component.ts
+++ b/ui-ngx/src/app/shared/components/notification/notification.component.ts
@@ -16,6 +16,7 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import {
+ ActionButtonLinkType,
AlarmSeverityNotificationColors,
Notification,
NotificationType,
@@ -26,6 +27,8 @@ import { Router } from '@angular/router';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { alarmSeverityTranslations } from '@shared/models/alarm.models';
import * as tinycolor_ from 'tinycolor2';
+import { StateObject } from '@core/api/widget-api.models';
+import { objToBase64URI } from '@core/utils';
@Component({
selector: 'tb-notification',
@@ -91,11 +94,27 @@ export class NotificationComponent implements OnInit {
$event.stopPropagation();
}
if (!this.preview) {
- if (this.notification.additionalConfig.actionButtonConfig.link.startsWith('/')) {
- this.router.navigateByUrl(this.router.parseUrl(this.notification.additionalConfig.actionButtonConfig.link)).then(() => {
+ let link: string;
+ if (this.notification.additionalConfig.actionButtonConfig.linkType === ActionButtonLinkType.DASHBOARD) {
+ let state = null;
+ if (this.notification.additionalConfig.actionButtonConfig.dashboardState) {
+ const stateObject: StateObject = {};
+ stateObject.params = {};
+ stateObject.id = this.notification.additionalConfig.actionButtonConfig.dashboardState;
+ state = objToBase64URI([ stateObject ]);
+ }
+ link = `/dashboards/${this.notification.additionalConfig.actionButtonConfig.dashboardId}`
+ if (state) {
+ link += `?state=${state}`;
+ }
+ } else {
+ link = this.notification.additionalConfig.actionButtonConfig.link;
+ }
+ if (link.startsWith('/')) {
+ this.router.navigateByUrl(this.router.parseUrl(link)).then(() => {
});
} else {
- window.open(this.notification.additionalConfig.actionButtonConfig.link, '_blank');
+ window.open(link, '_blank');
}
if (this.onClose) {
this.onClose();
diff --git a/ui-ngx/src/app/shared/components/string-items-list.component.html b/ui-ngx/src/app/shared/components/string-items-list.component.html
index 08e06b0f60..63e44d73b3 100644
--- a/ui-ngx/src/app/shared/components/string-items-list.component.html
+++ b/ui-ngx/src/app/shared/components/string-items-list.component.html
@@ -22,6 +22,7 @@
{{item}}
close
diff --git a/ui-ngx/src/app/shared/components/string-items-list.component.ts b/ui-ngx/src/app/shared/components/string-items-list.component.ts
index 422006fbbc..e7f50180c9 100644
--- a/ui-ngx/src/app/shared/components/string-items-list.component.ts
+++ b/ui-ngx/src/app/shared/components/string-items-list.component.ts
@@ -16,10 +16,10 @@
import { Component, forwardRef, Input } from '@angular/core';
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
-import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { MatChipInputEvent } from '@angular/material/chips';
import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes';
import { FloatLabelType, MatFormFieldAppearance } from '@angular/material/form-field';
+import { coerceBoolean } from '@shared/decorators/coerce-boolean';
@Component({
selector: 'tb-string-items-list',
@@ -45,15 +45,16 @@ export class StringItemsListComponent implements ControlValueAccessor{
return this.requiredValue;
}
@Input()
+ @coerceBoolean()
set required(value: boolean) {
- const newVal = coerceBooleanProperty(value);
- if (this.requiredValue !== newVal) {
- this.requiredValue = newVal;
+ if (this.requiredValue !== value) {
+ this.requiredValue = value;
this.updateValidators();
}
}
@Input()
+ @coerceBoolean()
disabled: boolean;
@Input()
@@ -74,6 +75,10 @@ export class StringItemsListComponent implements ControlValueAccessor{
@Input()
appearance: MatFormFieldAppearance = 'standard';
+ @Input()
+ @coerceBoolean()
+ editable: boolean = false
+
private propagateChange = (v: any) => { };
constructor(private fb: FormBuilder) {
diff --git a/ui-ngx/src/app/shared/decorators/coerce-boolean.ts b/ui-ngx/src/app/shared/decorators/coerce-boolean.ts
new file mode 100644
index 0000000000..226135a42a
--- /dev/null
+++ b/ui-ngx/src/app/shared/decorators/coerce-boolean.ts
@@ -0,0 +1,35 @@
+///
+/// Copyright © 2016-2023 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 { coerceBooleanProperty } from '@angular/cdk/coercion';
+
+export function coerceBoolean() {
+ return function (target: any, key: string): void {
+ const getter = function () {
+ return this['__' + key];
+ };
+
+ const setter = function (next: any) {
+ this['__' + key] = coerceBooleanProperty(next);
+ };
+
+ Object.defineProperty(target, key, {
+ get: getter,
+ set: setter,
+ enumerable: true,
+ configurable: true,
+ });
+ };
+}
diff --git a/ui-ngx/src/app/shared/models/notification.models.ts b/ui-ngx/src/app/shared/models/notification.models.ts
index f9f5eec2fd..1c9cc0e472 100644
--- a/ui-ngx/src/app/shared/models/notification.models.ts
+++ b/ui-ngx/src/app/shared/models/notification.models.ts
@@ -206,7 +206,10 @@ interface PushDeliveryMethodAdditionalConfig {
enabled: boolean;
text: string;
color: string;
- link: string;
+ linkType: ActionButtonLinkType,
+ link?: string;
+ dashboardId?: string;
+ dashboardState?: string;
};
}
@@ -301,6 +304,16 @@ export const AlarmSeverityNotificationColors = new Map(
]
);
+export enum ActionButtonLinkType {
+ LINK = 'LINK',
+ DASHBOARD = 'DASHBOARD'
+}
+
+export const ActionButtonLinkTypeTranslateMap = new Map([
+ [ActionButtonLinkType.LINK, 'notification.link-type.link'],
+ [ActionButtonLinkType.DASHBOARD, 'notification.link-type.dashboard']
+]);
+
interface NotificationTemplateTypeTranslate {
name: string;
hint?: string;
diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json
index 14c1c3d57a..95393d7aa1 100644
--- a/ui-ngx/src/assets/locale/locale.constant-en_US.json
+++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json
@@ -2690,6 +2690,7 @@
},
"notification": {
"action-button": "Action button",
+ "action-type": "Action type",
"add-notification-target": "Add notification recipient",
"add-notification-template": "Add notification template",
"add-rule": "Add rule",
@@ -2756,6 +2757,10 @@
"inbox": "Inbox",
"link": "Link",
"link-required": "Link is required",
+ "link-type": {
+ "link": "Open URL link",
+ "dashboard": "Open dashboard"
+ },
"management": "Notification management",
"mark-all-as-read": "Mark all as read",
"mark-as-read": "Mark as read",