diff --git a/application/src/main/data/json/system/scada_symbols/dynamic-horizontal-scale-hp.svg b/application/src/main/data/json/system/scada_symbols/dynamic-horizontal-scale-hp.svg index 6a060c4e63..00c7fa9ca2 100644 --- a/application/src/main/data/json/system/scada_symbols/dynamic-horizontal-scale-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/dynamic-horizontal-scale-hp.svg @@ -548,7 +548,7 @@ "id": "units", "name": "{i18n:scada.symbol.units}", "type": "units", - "default": "°C", + "default": "°C", "required": null, "subLabel": null, "divider": null, diff --git a/application/src/main/data/json/system/scada_symbols/dynamic-vertical-scale-hp.svg b/application/src/main/data/json/system/scada_symbols/dynamic-vertical-scale-hp.svg index 1310ee78b6..fe5fd56564 100644 --- a/application/src/main/data/json/system/scada_symbols/dynamic-vertical-scale-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/dynamic-vertical-scale-hp.svg @@ -548,7 +548,7 @@ "id": "units", "name": "{i18n:scada.symbol.units}", "type": "units", - "default": "°C", + "default": "°C", "required": null, "subLabel": null, "divider": null, diff --git a/application/src/main/data/json/system/scada_symbols/simple-horizontal-scale-hp.svg b/application/src/main/data/json/system/scada_symbols/simple-horizontal-scale-hp.svg index 1052d1361c..88850cee1d 100644 --- a/application/src/main/data/json/system/scada_symbols/simple-horizontal-scale-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/simple-horizontal-scale-hp.svg @@ -540,7 +540,7 @@ "id": "units", "name": "{i18n:scada.symbol.units}", "type": "units", - "default": "°C", + "default": "°C", "required": null, "subLabel": null, "divider": null, diff --git a/application/src/main/data/json/system/scada_symbols/vertical-scale-hp.svg b/application/src/main/data/json/system/scada_symbols/simple-vertical-scale-hp.svg similarity index 99% rename from application/src/main/data/json/system/scada_symbols/vertical-scale-hp.svg rename to application/src/main/data/json/system/scada_symbols/simple-vertical-scale-hp.svg index f1e1f2f178..30b36e6e76 100644 --- a/application/src/main/data/json/system/scada_symbols/vertical-scale-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/simple-vertical-scale-hp.svg @@ -540,7 +540,7 @@ "id": "units", "name": "{i18n:scada.symbol.units}", "type": "units", - "default": "°C", + "default": "°C", "required": null, "subLabel": null, "divider": null, diff --git a/application/src/main/data/json/system/scada_symbols/vertical-tank-hp.svg b/application/src/main/data/json/system/scada_symbols/vertical-tank-hp.svg index 33148d66a5..f7b77082f2 100644 --- a/application/src/main/data/json/system/scada_symbols/vertical-tank-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/vertical-tank-hp.svg @@ -1,5 +1,4 @@ - - { +<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="600" height="1e3" fill="none" version="1.1" viewBox="0 0 600 1e3"><tb:metadata xmlns=""><![CDATA[{ "title": "HP Vertical tank", "description": "Vertical tank", "searchTags": [ @@ -33,12 +32,12 @@ }, { "tag": "fluid-background", - "stateRenderFunction": " var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*970; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n element.attr({height: height});\n }", + "stateRenderFunction": " var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*994; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n element.attr({height: height});\n }", "actions": null }, { "tag": "scale", - "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 15;\n var majorIntervalLength = 970 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(160, y, 192, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 150, y: y + 2, 'text-anchor': 'end', class: 'majorTickText'});\n majorTickText.first().attr({'dominant-baseline': 'middle'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(172, minorY, 192, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", + "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 3;\n var majorIntervalLength = 994 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(160, y, 192, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n if (i === 0) {\n majorTickText.attr({x: 150, y: y + 10, 'text-anchor': 'end', class: 'majorTickText'});\n } else if (i === majorIntervals) {\n majorTickText.attr({x: 150, y: y - 5, 'text-anchor': 'end', class: 'majorTickText'});\n } else {\n majorTickText.attr({x: 150, y: y + 2, 'text-anchor': 'end', class: 'majorTickText'});\n }\n majorTickText.first().attr({'dominant-baseline': 'middle'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(172, minorY, 192, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", "actions": null }, { @@ -455,16 +454,12 @@ } ] } - - - + - - + - - + @@ -527,18 +522,15 @@ - - - + - - + - + \ No newline at end of file diff --git a/application/src/main/data/upgrade/3.8.1/schema_update.sql b/application/src/main/data/upgrade/3.8.1/schema_update.sql index 8783ccf3e3..e4faca82a5 100644 --- a/application/src/main/data/upgrade/3.8.1/schema_update.sql +++ b/application/src/main/data/upgrade/3.8.1/schema_update.sql @@ -59,10 +59,6 @@ $$ ALTER TABLE mobile_app_oauth2_client RENAME TO mobile_app_bundle_oauth2_client; ALTER TABLE mobile_app_bundle_oauth2_client DROP CONSTRAINT IF EXISTS fk_domain; ALTER TABLE mobile_app_bundle_oauth2_client RENAME COLUMN mobile_app_id TO mobile_app_bundle_id; - IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'fk_mobile_app_bundle_oauth2_client_bundle_id') THEN - ALTER TABLE mobile_app_bundle_oauth2_client ADD CONSTRAINT fk_mobile_app_bundle_oauth2_client_bundle_id - FOREIGN KEY (mobile_app_bundle_id) REFERENCES mobile_app_bundle(id) ON DELETE CASCADE; - END IF; END IF; END; $$; @@ -98,6 +94,10 @@ $$ UPDATE mobile_app_bundle_oauth2_client SET mobile_app_bundle_id = generatedBundleId WHERE mobile_app_bundle_id = mobileAppRecord.id; END LOOP; END IF; + IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'fk_mobile_app_bundle_oauth2_client_bundle_id') THEN + ALTER TABLE mobile_app_bundle_oauth2_client ADD CONSTRAINT fk_mobile_app_bundle_oauth2_client_bundle_id + FOREIGN KEY (mobile_app_bundle_id) REFERENCES mobile_app_bundle(id) ON DELETE CASCADE; + END IF; ALTER TABLE mobile_app DROP COLUMN IF EXISTS oauth2_enabled; IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'mobile_app_pkg_name_platform_unq_key') THEN ALTER TABLE mobile_app ADD CONSTRAINT mobile_app_pkg_name_platform_unq_key UNIQUE (pkg_name, platform_type); @@ -155,7 +155,7 @@ $$ iosAppId := uuid_generate_v4(); INSERT INTO mobile_app(id, created_time, tenant_id, pkg_name, platform_type, status, store_info) VALUES (iosAppId, (extract(epoch from now()) * 1000), qrCodeRecord.tenant_id, - iosPkgName, 'IOS', 'DRAFT', qrCodeRecord.ios_config); + iosPkgName, 'IOS', 'DRAFT', qrCodeRecord.ios_config::jsonb - 'enabled'); IF generatedBundleId IS NULL THEN generatedBundleId := uuid_generate_v4(); INSERT INTO mobile_app_bundle(id, created_time, tenant_id, title, ios_app_id) diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmStatusSubCtx.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmStatusSubCtx.java index fb330085a7..29e7d99688 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmStatusSubCtx.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmStatusSubCtx.java @@ -15,21 +15,16 @@ */ package org.thingsboard.server.service.subscription; -import lombok.Getter; -import lombok.Setter; import lombok.ToString; import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.alarm.AlarmInfo; -import org.thingsboard.server.common.data.query.AlarmCountQuery; import org.thingsboard.server.common.data.query.OriginatorAlarmFilter; import org.thingsboard.server.dao.alarm.AlarmService; -import org.thingsboard.server.dao.attributes.AttributesService; -import org.thingsboard.server.dao.entity.EntityService; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.ws.WebSocketService; import org.thingsboard.server.service.ws.WebSocketSessionRef; -import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmCountUpdate; import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmStatusCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmStatusUpdate; import org.thingsboard.server.service.ws.telemetry.sub.AlarmSubscriptionUpdate; import java.util.List; @@ -82,7 +77,10 @@ public class TbAlarmStatusSubCtx extends TbAbstractSubCtx { } public void sendUpdate() { - sendWsMsg(subscription.createUpdate()); + sendWsMsg(AlarmStatusUpdate.builder() + .cmdId(cmdId) + .active(subscription.hasAlarms()) + .build()); } public void fetchActiveAlarms() { diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmStatusSubscription.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmStatusSubscription.java index 6e80d817d7..f153178fa0 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmStatusSubscription.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmStatusSubscription.java @@ -22,7 +22,6 @@ import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmStatusUpdate; import org.thingsboard.server.service.ws.telemetry.sub.AlarmSubscriptionUpdate; import java.util.HashSet; @@ -53,15 +52,12 @@ public class TbAlarmStatusSubscription extends TbSubscription cmds; diff --git a/ui-ngx/src/app/core/ws/telemetry-websocket.service.ts b/ui-ngx/src/app/core/ws/telemetry-websocket.service.ts index 5c2c383bd9..4f0d8472dc 100644 --- a/ui-ngx/src/app/core/ws/telemetry-websocket.service.ts +++ b/ui-ngx/src/app/core/ws/telemetry-websocket.service.ts @@ -22,6 +22,9 @@ import { AlarmDataCmd, AlarmDataUnsubscribeCmd, AlarmDataUpdate, + AlarmStatusCmd, + AlarmStatusUnsubscribeCmd, + AlarmStatusUpdate, EntityCountCmd, EntityCountUnsubscribeCmd, EntityCountUpdate, @@ -30,6 +33,7 @@ import { EntityDataUpdate, isAlarmCountUpdateMsg, isAlarmDataUpdateMsg, + isAlarmStatusUpdateMsg, isEntityCountUpdateMsg, isEntityDataUpdateMsg, isNotificationCountUpdateMsg, @@ -121,6 +125,10 @@ export class TelemetryWebsocketService extends WebsocketService 0) { const lines = content.split('\n'); - newHeight = 16 * lines.length + 16; + newHeight = 17 * lines.length + 16; let maxLineLength = 0; lines.forEach((row) => { const line = row.replace(/\t/g, ' ').replace(/\n/g, ''); const lineLength = line.length; maxLineLength = Math.max(maxLineLength, lineLength); }); - newWidth = 8 * maxLineLength + 16; + newWidth = 9 * maxLineLength + 16; } // newHeight = Math.min(400, newHeight); this.renderer.setStyle(editorElement, 'minHeight', newHeight.toString() + 'px'); diff --git a/ui-ngx/src/app/modules/home/components/event/event-content-dialog.component.ts b/ui-ngx/src/app/modules/home/components/event/event-content-dialog.component.ts index 65b5be9b75..40cd3b6f3b 100644 --- a/ui-ngx/src/app/modules/home/components/event/event-content-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/event/event-content-dialog.component.ts @@ -139,14 +139,14 @@ export class EventContentDialogComponent extends DialogComponent 0) { const lines = content.split('\n'); - newHeight = 16 * lines.length + 16; + newHeight = 17 * lines.length + 16; let maxLineLength = 0; lines.forEach((row) => { const line = row.replace(/\t/g, ' ').replace(/\n/g, ''); const lineLength = line.length; maxLineLength = Math.max(maxLineLength, lineLength); }); - newWidth = 8 * maxLineLength + 16; + newWidth = 9 * maxLineLength + 16; } // newHeight = Math.min(400, newHeight); this.renderer.setStyle(editorElement, 'minHeight', newHeight.toString() + 'px'); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/action/action-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/action/action-widget.models.ts index 20613b3d4f..f4d9cf8d58 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/action/action-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/action/action-widget.models.ts @@ -242,6 +242,8 @@ export abstract class ValueGetter extends ValueAction { return new AttributeValueGetter(ctx, settings, valueType, valueObserver, simulated); case GetValueAction.GET_TIME_SERIES: return new TimeSeriesValueGetter(ctx, settings, valueType, valueObserver, simulated); + case GetValueAction.GET_ALARM_STATUS: + return new AlarmStatusValueGetter(ctx, settings, valueType, valueObserver, simulated); case GetValueAction.GET_DASHBOARD_STATE: return new DashboardStateGetter(ctx, settings, valueType, valueObserver, simulated); } @@ -257,7 +259,7 @@ export abstract class ValueGetter extends ValueAction { protected valueObserver: Partial>, protected simulated: boolean) { super(ctx, settings); - if (this.settings.action !== GetValueAction.DO_NOTHING) { + if (this.settings.action !== GetValueAction.DO_NOTHING && this.settings.action !== GetValueAction.GET_ALARM_STATUS) { this.dataConverter = new DataToValueConverter(settings.dataToValue, valueType); } } @@ -537,6 +539,58 @@ export class TimeSeriesValueGetter extends TelemetryValueGetter extends ValueGetter { + + protected targetEntityId: EntityId; + private telemetrySubscriber: SharedTelemetrySubscriber; + + constructor(protected ctx: WidgetContext, + protected settings: GetValueSettings, + protected valueType: ValueType, + protected valueObserver: Partial>, + protected simulated: boolean) { + super(ctx, settings, valueType, valueObserver, simulated); + const entityInfo = this.ctx.defaultSubscription.getFirstEntityInfo(); + this.targetEntityId = entityInfo?.entityId; + } + + protected doGetValue(): Observable { + if (this.simulated) { + return of(false).pipe(delay(100)); + } else { + if (!this.targetEntityId && !this.ctx.defaultSubscription.rpcEnabled) { + return throwError(() => new Error(this.ctx.translate.instant('widgets.value-action.error.target-entity-is-not-set'))); + } + if (this.targetEntityId) { + return this.subscribeForTelemetryValue(); + } else { + return of(null); + } + } + } + + private subscribeForTelemetryValue(): Observable { + this.telemetrySubscriber = + SharedTelemetrySubscriber.createAlarmStatusSubscription(this.ctx.telemetryWsService, this.targetEntityId, + this.ctx.ngZone, this.settings.getAlarmStatus.severityList, this.settings.getAlarmStatus.typeList); + this.telemetrySubscriber.subscribe(); + return this.telemetrySubscriber.alarmStatus$.pipe( + map((data) => { + return data.active; + }) + ); + } + + + destroy() { + if (this.telemetrySubscriber) { + this.telemetrySubscriber.unsubscribe(); + this.telemetrySubscriber = null; + } + super.destroy(); + } +} + export class DashboardStateGetter extends ValueGetter { constructor(protected ctx: WidgetContext, protected settings: GetValueSettings, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/button/action-button-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/button/action-button-widget.models.ts index 4b3fafa55c..fd2c0bdcdc 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/button/action-button-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/button/action-button-widget.models.ts @@ -38,6 +38,10 @@ export const actionButtonDefaultSettings: ActionButtonWidgetSettings = { getTimeSeries: { key: 'state' }, + getAlarmStatus: { + severityList: null, + typeList: null + }, dataToValue: { type: DataToValueType.NONE, compareToValue: true, @@ -54,6 +58,10 @@ export const actionButtonDefaultSettings: ActionButtonWidgetSettings = { getTimeSeries: { key: 'state' }, + getAlarmStatus: { + severityList: null, + typeList: null + }, dataToValue: { type: DataToValueType.NONE, compareToValue: true, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/button/command-button-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/button/command-button-widget.models.ts index a365e6929c..93484b5574 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/button/command-button-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/button/command-button-widget.models.ts @@ -62,6 +62,10 @@ export const commandButtonDefaultSettings: CommandButtonWidgetSettings = { getTimeSeries: { key: 'state' }, + getAlarmStatus: { + severityList: null, + typeList: null + }, dataToValue: { type: DataToValueType.NONE, compareToValue: true, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/button/toggle-button-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/button/toggle-button-widget.models.ts index 12e115792b..3ece34a82f 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/button/toggle-button-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/button/toggle-button-widget.models.ts @@ -61,6 +61,10 @@ export const toggleButtonDefaultSettings: ToggleButtonWidgetSettings = { getTimeSeries: { key: 'state' }, + getAlarmStatus: { + severityList: null, + typeList: null + }, dataToValue: { type: DataToValueType.NONE, compareToValue: true, @@ -77,6 +81,10 @@ export const toggleButtonDefaultSettings: ToggleButtonWidgetSettings = { getTimeSeries: { key: 'state' }, + getAlarmStatus: { + severityList: null, + typeList: null + }, dataToValue: { type: DataToValueType.NONE, compareToValue: true, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/indicator/signal-strength-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/indicator/signal-strength-widget.component.ts index acb60af335..f4531371ef 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/indicator/signal-strength-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/indicator/signal-strength-widget.component.ts @@ -145,6 +145,7 @@ export class SignalStrengthWidgetComponent implements OnInit, OnDestroy, AfterVi private rssi = -100; private noSignal = false; private noData = false; + private noSignalRssiValue = -100; constructor(public widgetComponent: WidgetComponent, private imagePipe: ImagePipe, @@ -166,6 +167,8 @@ export class SignalStrengthWidgetComponent implements OnInit, OnDestroy, AfterVi this.dateStyle.color = this.settings.dateColor; } + this.noSignalRssiValue = this.settings.noSignalRssiValue ?? -100; + this.activeBarsColor = ColorProcessor.fromSettings(this.settings.activeBarsColor); const inactiveBarsColor = tinycolor(this.settings.inactiveBarsColor); this.inactiveBarsColorHex = inactiveBarsColor.toHexString(); @@ -262,7 +265,7 @@ export class SignalStrengthWidgetComponent implements OnInit, OnDestroy, AfterVi } } - this.noSignal = this.rssi <= this.settings.noSignalRssiValue; + this.noSignal = this.rssi <= this.noSignalRssiValue; this.activeBarsColor.update(this.rssi); @@ -342,7 +345,7 @@ export class SignalStrengthWidgetComponent implements OnInit, OnDestroy, AfterVi const activeBarsOpacity = activeBarsColor.getAlpha(); for (let index = 0; index < this.bars.length; index++) { const bar = this.bars[index]; - const active = signalBarActive(this.rssi, index); + const active = signalBarActive(this.rssi, index, this.noSignalRssiValue); const newFill = active ? activeBarsColorHex : this.inactiveBarsColorHex; const newOpacity = active ? activeBarsOpacity : this.inactiveBarsOpacity; if (newFill !== bar.fill() || newOpacity !== bar.opacity()) { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/indicator/signal-strength-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/indicator/signal-strength-widget.models.ts index 1f43ef8367..ad5b0f7efc 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/indicator/signal-strength-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/indicator/signal-strength-widget.models.ts @@ -133,10 +133,10 @@ export const signalStrengthDefaultSettings: SignalStrengthWidgetSettings = { padding: '12px' }; -export const signalBarActive = (rssi: number, index: number): boolean => { +export const signalBarActive = (rssi: number, index: number, minSignal: number): boolean => { switch (index) { case 0: - return rssi > -100; + return rssi > minSignal; case 1: return rssi >= -85; case 2: diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/indicator/status-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/indicator/status-widget.models.ts index 4dbea591f9..1097842b4b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/indicator/status-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/indicator/status-widget.models.ts @@ -85,6 +85,10 @@ export const statusWidgetDefaultSettings: StatusWidgetSettings = { getTimeSeries: { key: 'state' }, + getAlarmStatus: { + severityList: null, + typeList: null + }, dataToValue: { type: DataToValueType.NONE, compareToValue: true, @@ -101,6 +105,10 @@ export const statusWidgetDefaultSettings: StatusWidgetSettings = { getTimeSeries: { key: 'state' }, + getAlarmStatus: { + severityList: null, + typeList: null + }, dataToValue: { type: DataToValueType.NONE, compareToValue: true, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.models.ts index 12e2a1065b..503347939d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.models.ts @@ -104,6 +104,10 @@ export const powerButtonDefaultSettings: PowerButtonWidgetSettings = { getTimeSeries: { key: 'state' }, + getAlarmStatus: { + severityList: null, + typeList: null + }, dataToValue: { type: DataToValueType.NONE, compareToValue: true, @@ -120,6 +124,10 @@ export const powerButtonDefaultSettings: PowerButtonWidgetSettings = { getTimeSeries: { key: 'state' }, + getAlarmStatus: { + severityList: null, + typeList: null + }, dataToValue: { type: DataToValueType.NONE, compareToValue: true, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/single-switch-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/single-switch-widget.models.ts index 62aae25816..f186d4d58c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/single-switch-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/single-switch-widget.models.ts @@ -100,6 +100,10 @@ export const singleSwitchDefaultSettings: SingleSwitchWidgetSettings = { getTimeSeries: { key: 'state' }, + getAlarmStatus: { + severityList: null, + typeList: null + }, dataToValue: { type: DataToValueType.NONE, compareToValue: true, @@ -116,6 +120,10 @@ export const singleSwitchDefaultSettings: SingleSwitchWidgetSettings = { getTimeSeries: { key: 'state' }, + getAlarmStatus: { + severityList: null, + typeList: null + }, dataToValue: { type: DataToValueType.NONE, compareToValue: true, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/slider-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/slider-widget.models.ts index 3df4359dce..980aff2f0f 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/slider-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/slider-widget.models.ts @@ -101,6 +101,10 @@ export const sliderWidgetDefaultSettings: SliderWidgetSettings = { getTimeSeries: { key: 'state' }, + getAlarmStatus: { + severityList: null, + typeList: null + }, dataToValue: { type: DataToValueType.NONE, compareToValue: true, @@ -117,6 +121,10 @@ export const sliderWidgetDefaultSettings: SliderWidgetSettings = { getTimeSeries: { key: 'state' }, + getAlarmStatus: { + severityList: null, + typeList: null + }, dataToValue: { type: DataToValueType.NONE, compareToValue: true, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol.models.ts index 6d3e9fde82..8a782a14a2 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol.models.ts @@ -415,6 +415,10 @@ export const defaultGetValueSettings = (valueType: ValueType): GetValueSettings< getTimeSeries: { key: 'state' }, + getAlarmStatus: { + severityList: null, + typeList: null + }, dataToValue: { type: DataToValueType.NONE, compareToValue: true, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/get-value-action-settings-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/get-value-action-settings-panel.component.html index 4e5e6709a6..87a81db0eb 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/get-value-action-settings-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/get-value-action-settings-panel.component.html @@ -103,8 +103,31 @@ + + +
+
alarm.alarm-severity
+ + + {{ alarmSeverityTranslationMap.get(alarmSeverity) | translate }} + + +
+
+
alarm.alarm-types
+ + +
+
+
-
widgets.value-action.action-result-converter
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/get-value-action-settings-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/get-value-action-settings-panel.component.ts index bf19d3e37a..6abc51b003 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/get-value-action-settings-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/get-value-action-settings-panel.component.ts @@ -33,6 +33,8 @@ import { TargetDevice, widgetType } from '@shared/models/widget.models'; import { AttributeScope, DataKeyType, telemetryTypeTranslationsShort } from '@shared/models/telemetry/telemetry.models'; import { IAliasController } from '@core/api/widget-api.models'; import { WidgetService } from '@core/http/widget.service'; +import { AlarmSeverity, alarmSeverityTranslations } from '@shared/models/alarm.models'; +import { EntityType } from '@shared/models/entity-type.models'; @Component({ selector: 'tb-get-value-action-settings-panel', @@ -96,6 +98,9 @@ export class GetValueActionSettingsPanelComponent extends PageComponent implemen getValueSettingsFormGroup: UntypedFormGroup; + alarmSeverities = Object.keys(AlarmSeverity) as AlarmSeverity[]; + alarmSeverityTranslationMap = alarmSeverityTranslations; + constructor(private fb: UntypedFormBuilder, private widgetService: WidgetService, protected store: Store) { @@ -122,6 +127,10 @@ export class GetValueActionSettingsPanelComponent extends PageComponent implemen getTimeSeries: this.fb.group({ key: [this.getValueSettings?.getTimeSeries?.key, [Validators.required]] }), + getAlarmStatus: this.fb.group({ + severityList: [this.getValueSettings?.getAlarmStatus?.severityList], + typeList: [this.getValueSettings?.getAlarmStatus?.typeList] + }), dataToValue: this.fb.group({ type: [this.getValueSettings?.dataToValue?.type, [Validators.required]], dataToValueFunction: [this.getValueSettings?.dataToValue?.dataToValueFunction, [Validators.required]], @@ -159,6 +168,7 @@ export class GetValueActionSettingsPanelComponent extends PageComponent implemen this.getValueSettingsFormGroup.get('executeRpc').disable({emitEvent: false}); this.getValueSettingsFormGroup.get('getAttribute').disable({emitEvent: false}); this.getValueSettingsFormGroup.get('getTimeSeries').disable({emitEvent: false}); + this.getValueSettingsFormGroup.get('getAlarmStatus').disable({emitEvent: false}); switch (action) { case GetValueAction.DO_NOTHING: this.getValueSettingsFormGroup.get('defaultValue').enable({emitEvent: false}); @@ -178,8 +188,11 @@ export class GetValueActionSettingsPanelComponent extends PageComponent implemen case GetValueAction.GET_TIME_SERIES: this.getValueSettingsFormGroup.get('getTimeSeries').enable({emitEvent: false}); break; + case GetValueAction.GET_ALARM_STATUS: + this.getValueSettingsFormGroup.get('getAlarmStatus').enable({emitEvent: false}); + break; } - if (action === GetValueAction.DO_NOTHING) { + if (action === GetValueAction.DO_NOTHING || action === GetValueAction.GET_ALARM_STATUS) { this.getValueSettingsFormGroup.get('dataToValue').disable({emitEvent: false}); } else { this.getValueSettingsFormGroup.get('dataToValue').enable({emitEvent: false}); @@ -190,4 +203,6 @@ export class GetValueActionSettingsPanelComponent extends PageComponent implemen } } } + + protected readonly entityType = EntityType; } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/get-value-action-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/get-value-action-settings.component.ts index 3a9f079db8..871923e5b7 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/get-value-action-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/get-value-action-settings.component.ts @@ -176,6 +176,9 @@ export class GetValueActionSettingsComponent implements OnInit, ControlValueAcce case GetValueAction.GET_TIME_SERIES: this.displayValue = this.translate.instant('widgets.value-action.get-time-series-text', {key: this.modelValue.getTimeSeries.key}); break; + case GetValueAction.GET_ALARM_STATUS: + this.displayValue = this.translate.instant('widgets.value-action.get-alarm-status-text'); + break; case GetValueAction.GET_DASHBOARD_STATE: if (this.valueType === ValueType.BOOLEAN) { const state = this.modelValue.dataToValue?.compareToValue; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/signal-strength-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/signal-strength-widget-settings.component.ts index 8e0dad0daa..9e6aa0ccdf 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/signal-strength-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/signal-strength-widget-settings.component.ts @@ -16,7 +16,7 @@ import { Component, Injector } from '@angular/core'; import { WidgetSettings, WidgetSettingsComponent } from '@shared/models/widget.models'; -import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { formatValue } from '@core/utils'; @@ -88,7 +88,7 @@ export class SignalStrengthWidgetSettingsComponent extends WidgetSettingsCompone background: [settings.background, []], padding: [settings.padding, []], - noSignalRssiValue: [settings.noSignalRssiValue, []] + noSignalRssiValue: [settings.noSignalRssiValue, [Validators.max(-86)]] }); } diff --git a/ui-ngx/src/app/shared/models/action-widget-settings.models.ts b/ui-ngx/src/app/shared/models/action-widget-settings.models.ts index e7b962b34f..fc03ef2eb9 100644 --- a/ui-ngx/src/app/shared/models/action-widget-settings.models.ts +++ b/ui-ngx/src/app/shared/models/action-widget-settings.models.ts @@ -16,12 +16,14 @@ import { AttributeScope } from '@shared/models/telemetry/telemetry.models'; import { widgetType } from '@shared/models/widget.models'; +import { AlarmSeverity } from '@shared/models/alarm.models'; export enum GetValueAction { DO_NOTHING = 'DO_NOTHING', EXECUTE_RPC = 'EXECUTE_RPC', GET_ATTRIBUTE = 'GET_ATTRIBUTE', GET_TIME_SERIES = 'GET_TIME_SERIES', + GET_ALARM_STATUS = 'GET_ALARM_STATUS', GET_DASHBOARD_STATE = 'GET_DASHBOARD_STATE' } @@ -41,6 +43,7 @@ export const getValueActionTranslations = new Map( [GetValueAction.EXECUTE_RPC, 'widgets.value-action.execute-rpc'], [GetValueAction.GET_ATTRIBUTE, 'widgets.value-action.get-attribute'], [GetValueAction.GET_TIME_SERIES, 'widgets.value-action.get-time-series'], + [GetValueAction.GET_ALARM_STATUS, 'widgets.value-action.get-alarm-status'], [GetValueAction.GET_DASHBOARD_STATE, 'widgets.value-action.get-dashboard-state'] ] ); @@ -52,6 +55,11 @@ export interface RpcSettings { persistentPollingInterval: number; } +export interface AlarmStatusSettings { + severityList: Array; + typeList: Array; +} + export interface TelemetryValueSettings { key: string; } @@ -85,6 +93,7 @@ export interface GetValueSettings extends ValueActionSettings { executeRpc?: RpcSettings; getAttribute: GetAttributeValueSettings; getTimeSeries: TelemetryValueSettings; + getAlarmStatus: AlarmStatusSettings; dataToValue: DataToValueSettings; } diff --git a/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts b/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts index 2db1983b0b..827708351e 100644 --- a/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts +++ b/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts @@ -33,9 +33,9 @@ import { TsValue } from '@shared/models/query/query.models'; import { PageData } from '@shared/models/page/page-data'; -import { alarmFields } from '@shared/models/alarm.models'; +import { alarmFields, AlarmSeverity } from '@shared/models/alarm.models'; import { entityFields } from '@shared/models/entity.models'; -import { isDefinedAndNotNull, isUndefined } from '@core/utils'; +import { deepClone, isDefinedAndNotNull, isUndefined } from '@core/utils'; import { CmdWrapper, WsService, WsSubscriber } from '@shared/models/websocket/websocket.models'; import { TelemetryWebsocketService } from '@core/ws/telemetry-websocket.service'; import { Notification, NotificationType } from '@shared/models/notification.models'; @@ -141,6 +141,7 @@ export enum WsCmdType { ENTITY_COUNT = 'ENTITY_COUNT', ALARM_DATA = 'ALARM_DATA', ALARM_COUNT = 'ALARM_COUNT', + ALARM_STATUS = 'ALARM_STATUS', NOTIFICATIONS = 'NOTIFICATIONS', NOTIFICATIONS_COUNT = 'NOTIFICATIONS_COUNT', @@ -149,6 +150,7 @@ export enum WsCmdType { ALARM_DATA_UNSUBSCRIBE = 'ALARM_DATA_UNSUBSCRIBE', ALARM_COUNT_UNSUBSCRIBE = 'ALARM_COUNT_UNSUBSCRIBE', + ALARM_STATUS_UNSUBSCRIBE = 'ALARM_STATUS_UNSUBSCRIBE', ENTITY_DATA_UNSUBSCRIBE = 'ENTITY_DATA_UNSUBSCRIBE', ENTITY_COUNT_UNSUBSCRIBE = 'ENTITY_COUNT_UNSUBSCRIBE', NOTIFICATIONS_UNSUBSCRIBE = 'NOTIFICATIONS_UNSUBSCRIBE' @@ -298,6 +300,14 @@ export class AlarmCountCmd implements WebsocketCmd { type = WsCmdType.ALARM_COUNT; } +export class AlarmStatusCmd implements WebsocketCmd { + cmdId: number; + originatorId: EntityId; + severityList?: Array; + typeList?: Array; + type = WsCmdType.ALARM_STATUS; +} + export class UnreadCountSubCmd implements WebsocketCmd { cmdId: number; type = WsCmdType.NOTIFICATIONS_COUNT; @@ -352,6 +362,11 @@ export class AlarmCountUnsubscribeCmd implements WebsocketCmd { type = WsCmdType.ALARM_COUNT_UNSUBSCRIBE; } +export class AlarmStatusUnsubscribeCmd implements WebsocketCmd { + cmdId: number; + type = WsCmdType.ALARM_STATUS_UNSUBSCRIBE; +} + export class UnsubscribeCmd implements WebsocketCmd { cmdId: number; type = WsCmdType.NOTIFICATIONS_UNSUBSCRIBE; @@ -432,6 +447,7 @@ export enum CmdUpdateType { ENTITY_DATA = 'ENTITY_DATA', ALARM_DATA = 'ALARM_DATA', ALARM_COUNT_DATA = 'ALARM_COUNT_DATA', + ALARM_STATUS = 'ALARM_STATUS', COUNT_DATA = 'COUNT_DATA', NOTIFICATIONS_COUNT = 'NOTIFICATIONS_COUNT', NOTIFICATIONS = 'NOTIFICATIONS' @@ -469,6 +485,11 @@ export interface AlarmCountUpdateMsg extends CmdUpdateMsg { count: number; } +export interface AlarmStatusUpdateMsg extends CmdUpdateMsg { + cmdUpdateType: CmdUpdateType.ALARM_STATUS; + active: boolean; +} + export interface NotificationCountUpdateMsg extends CmdUpdateMsg { cmdUpdateType: CmdUpdateType.NOTIFICATIONS_COUNT; totalUnreadCount: number; @@ -506,6 +527,11 @@ export const isAlarmCountUpdateMsg = (message: WebsocketDataMsg): message is Ala return updateMsg.cmdId !== undefined && updateMsg.cmdUpdateType === CmdUpdateType.ALARM_COUNT_DATA; }; +export const isAlarmStatusUpdateMsg = (message: WebsocketDataMsg): message is AlarmCountUpdateMsg => { + const updateMsg = (message as CmdUpdateMsg); + return updateMsg.cmdId !== undefined && updateMsg.cmdUpdateType === CmdUpdateType.ALARM_STATUS; +}; + export const isNotificationCountUpdateMsg = (message: WebsocketDataMsg): message is NotificationCountUpdateMsg => { const updateMsg = (message as CmdUpdateMsg); return updateMsg.cmdId !== undefined && updateMsg.cmdUpdateType === CmdUpdateType.NOTIFICATIONS_COUNT; @@ -705,6 +731,15 @@ export class AlarmCountUpdate extends CmdUpdate { } } +export class AlarmStatusUpdate extends CmdUpdate { + active: boolean; + + constructor(msg: AlarmStatusUpdateMsg) { + super(msg); + this.active = msg.active; + } +} + export class NotificationCountUpdate extends CmdUpdate { totalUnreadCount: number; sequenceNumber: number; @@ -750,14 +785,29 @@ export class SharedTelemetrySubscriber { return key; } + private static createAlarmStatusSubscriberKey (entityId: EntityId, severityList: AlarmSeverity[] = null, typeList: string[] = null): string { + let key = entityId.entityType + '_' + entityId.id; + if (severityList) { + key += '_' + severityList.sort().join('_'); + } + if (typeList) { + key += '_' + typeList.sort().join('_'); + } + return key; + } + private subscribed = false; private attributeDataSubject = connectable(this.sharedSubscriptionInfo.subscriber.attributeData$(), { connector: () => new ReplaySubject>(1)}); + private alarmStatusSubject = connectable(this.sharedSubscriptionInfo.subscriber.alarmStatus$, + { connector: () => new ReplaySubject()}); + private subscriptions = new Array(); public attributeData$: Observable> = this.attributeDataSubject; //this.attributeDataSubject.asObservable(); + public alarmStatus$: Observable = this.alarmStatusSubject; public static createEntityAttributesSubscription(telemetryService: TelemetryWebsocketService, entityId: EntityId, attributeScope: TelemetryType, @@ -781,6 +831,28 @@ export class SharedTelemetrySubscriber { return sharedSubscriber; } + public static createAlarmStatusSubscription(telemetryService: TelemetryWebsocketService, + entityId: EntityId, zone: NgZone, severityList: AlarmSeverity[] = null, + typeList: string[] = null): SharedTelemetrySubscriber { + const key = SharedTelemetrySubscriber.createAlarmStatusSubscriberKey(entityId, severityList, typeList); + let info = SharedTelemetrySubscriber.subscribersCache[key]; + if (!info) { + const subscriber = TelemetrySubscriber.createAlarmStatusSubscription( + telemetryService, entityId, zone, severityList, typeList + ); + info = { + key, + subscriber, + subscribed: false, + sharedSubscribers: new Set() + }; + SharedTelemetrySubscriber.subscribersCache[key] = info; + } + const sharedSubscriber = new SharedTelemetrySubscriber(info); + info.sharedSubscribers.add(sharedSubscriber); + return sharedSubscriber; + } + private constructor(private sharedSubscriptionInfo: SharedSubscriptionInfo) { } @@ -788,6 +860,7 @@ export class SharedTelemetrySubscriber { if (!this.subscribed) { this.subscribed = true; this.subscriptions.push(this.attributeDataSubject.connect()); + this.subscriptions.push(this.alarmStatusSubject.connect()); if (!this.sharedSubscriptionInfo.subscribed) { this.sharedSubscriptionInfo.subscriber.subscribe(); this.sharedSubscriptionInfo.subscribed = true; @@ -823,6 +896,7 @@ export class TelemetrySubscriber extends WsSubscriber { private alarmDataSubject = new ReplaySubject(1); private entityCountSubject = new ReplaySubject(1); private alarmCountSubject = new ReplaySubject(1); + private alarmStatusSubject = new ReplaySubject(1); private tsOffset = undefined; public data$ = this.dataSubject.asObservable(); @@ -830,6 +904,7 @@ export class TelemetrySubscriber extends WsSubscriber { public alarmData$ = this.alarmDataSubject.asObservable(); public entityCount$ = this.entityCountSubject.asObservable(); public alarmCount$ = this.alarmCountSubject.asObservable(); + public alarmStatus$ = this.alarmStatusSubject.asObservable(); public static createEntityAttributesSubscription(telemetryService: TelemetryWebsocketService, entityId: EntityId, attributeScope: TelemetryType, @@ -851,6 +926,17 @@ export class TelemetrySubscriber extends WsSubscriber { return subscriber; } + public static createAlarmStatusSubscription(telemetryService: TelemetryWebsocketService, entityId: EntityId, + zone: NgZone, severityList: AlarmSeverity[] = null, typeList: string[] = null): TelemetrySubscriber { + const subscriptionCommand = new AlarmStatusCmd(); + subscriptionCommand.originatorId = deepClone(entityId); + subscriptionCommand.severityList = severityList; + subscriptionCommand.typeList = typeList; + const subscriber = new TelemetrySubscriber(telemetryService, zone); + subscriber.subscriptionCommands.push(subscriptionCommand); + return subscriber; + } + public static createEntityFilterLatestSubscription(telemetryService: TelemetryWebsocketService, entityFilter: EntityFilter, zone: NgZone, latestKeys: EntityKey[] = null): TelemetrySubscriber { @@ -882,6 +968,7 @@ export class TelemetrySubscriber extends WsSubscriber { this.alarmDataSubject.complete(); this.entityCountSubject.complete(); this.alarmCountSubject.complete(); + this.alarmStatusSubject.complete(); super.complete(); } @@ -971,6 +1058,18 @@ export class TelemetrySubscriber extends WsSubscriber { } } + public onAlarmStatus(message: AlarmStatusUpdate) { + if (this.zone) { + this.zone.run( + () => { + this.alarmStatusSubject.next(message); + } + ); + } else { + this.alarmStatusSubject.next(message); + } + } + public attributeData$(): Observable> { const attributeData = new Array(); return this.data$.pipe( diff --git a/ui-ngx/src/assets/help/en_US/widget/lib/gateway/address-filter_fn.md b/ui-ngx/src/assets/help/en_US/widget/lib/gateway/address-filter_fn.md index 9ead0a6afd..0ae95ab9b2 100644 --- a/ui-ngx/src/assets/help/en_US/widget/lib/gateway/address-filter_fn.md +++ b/ui-ngx/src/assets/help/en_US/widget/lib/gateway/address-filter_fn.md @@ -21,4 +21,4 @@ For example, we have a device that has the following IP address: 192.168.0.120:5 4. Allow any devices: - **Address filter:** *:* + **Address filter:** `*:*` diff --git a/ui-ngx/src/assets/help/en_US/widget/lib/gateway/report-strategy_fn.md b/ui-ngx/src/assets/help/en_US/widget/lib/gateway/report-strategy_fn.md new file mode 100644 index 0000000000..cf52b3a679 --- /dev/null +++ b/ui-ngx/src/assets/help/en_US/widget/lib/gateway/report-strategy_fn.md @@ -0,0 +1,11 @@ +# Report Strategy for Data Transmission + +This section allows you to configure the strategy for sending data. You can select one of the following behaviors: + +1. **On Report Period** - Data is collected and sent at regular intervals, defined in milliseconds. + +2. **On Value Change** - Data is transmitted immediately whenever a change is detected. + +3. **On Value Change or Report Period** - Data is sent either when a change occurs or after a specified time period has elapsed since the last transmission. + +4. **On Received** - Data is sent instantly upon being received. 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 5addc4b6c2..405f4ae9d3 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -596,6 +596,7 @@ "ack-time": "Acknowledged time", "clear-time": "Cleared time", "duration": "Duration", + "alarm-severity": "Alarm severity", "alarm-severity-list": "Alarm severity list", "any-severity": "Any severity", "severity-critical": "Critical", @@ -634,6 +635,7 @@ "fetch-size": "Fetch size", "fetch-size-required": "Fetch size is required.", "fetch-size-error-min": "Minimum value is 10.", + "alarm-types": "Alarm types", "alarm-type-list": "Alarm type list", "any-type": "Any type", "assigned-to-current-user": "Assigned to current user", @@ -7352,11 +7354,12 @@ "get-attribute": "Get attribute", "set-attribute": "Set attribute", "get-time-series": "Get time series", + "get-alarm-status": "Get alarm status", "get-dashboard-state": "Get dashboard state", "add-time-series": "Add time series", "execute-rpc-text": "Execute RPC method '{{methodName}}'", "get-attribute-text": "Use attribute '{{key}}'", - "get-time-series-text": "Use time series '{{key}}'", + "get-alarm-status-text": "Use alarm status", "get-dashboard-state-text": "Use dashboard state", "when-dashboard-state-is-text": "When dashboard state is '{{state}}'", "when-dashboard-state-function-is-text": "When f(dashboard state) is '{{state}}'", @@ -7660,31 +7663,31 @@ "language": { "language": "Language", "locales": { - "ar_AE": "اَلْعَرَبِيَّةُ", - "ca_ES": "Catalan", - "cs_CZ": "Česky", - "da_DK": "Dansk", - "de_DE": "Deutsch", - "el_GR": "Ελληνικά", - "en_US": "English", - "es_ES": "Español", - "fa_IR": "فارسي", - "fr_FR": "Français", - "it_IT": "Italiano", - "ja_JP": "日本語", - "ka_GE": "ქართული", - "ko_KR": "한국어", - "lt_LT": "Lietuvių", - "lv_LV": "Latviešu", - "nl_BE": "Koninkrijk België", - "pl_PL": "Polski", - "pt_BR": "Português do Brasil", - "ro_RO": "Română", - "sl_SI": "Slovenščina", - "tr_TR": "Türkçe", - "uk_UA": "Українська", - "zh_CN": "简体中文", - "zh_TW": "繁體中文" + "ar_AE": "العربية (الإمارات العربية المتحدة)", + "ca_ES": "català (Espanya)", + "cs_CZ": "čeština (Česko)", + "da_DK": "dansk (Danmark)", + "de_DE": "Deutsch (Deutschland)", + "el_GR": "Ελληνικά (Ελλάδα)", + "en_US": "English (United States)", + "es_ES": "español (España)", + "fa_IR": "فارسی (ایران)", + "fr_FR": "français (France)", + "it_IT": "italiano (Italia)", + "ja_JP": "日本語 (日本)", + "ka_GE": "ქართული (საქართველო)", + "ko_KR": "한국어 (대한민국)", + "lt_LT": "lietuvių (Lietuva)", + "lv_LV": "latviešu (Latvija)", + "nl_BE": "Nederlands (België)", + "pl_PL": "polski (Polska)", + "pt_BR": "português (Brasil)", + "ro_RO": "română (România)", + "sl_SI": "slovenščina (Slovenija)", + "tr_TR": "Türkçe (Türkiye)", + "uk_UA": "українська (Україна)", + "zh_CN": "中文 (中国)", + "zh_TW": "中文 (台灣)" } } }