Merge branch 'master' into fix-edge-zombie-consumer-cleanup
This commit is contained in:
commit
63966c5771
@ -11,7 +11,7 @@
|
||||
"resources": [],
|
||||
"templateHtml": "<div style=\"height: 100%; overflow-y: auto;\" id=\"device-terminal\"></div>",
|
||||
"templateCss": ".cmd .cursor.blink {\n -webkit-animation-name: terminal-underline;\n -moz-animation-name: terminal-underline;\n -ms-animation-name: terminal-underline;\n animation-name: terminal-underline;\n}\n.terminal .inverted, .cmd .inverted {\n border-bottom-color: #aaa;\n}\n\n",
|
||||
"controllerScript": "var requestTimeout = 500;\nvar requestPersistent = false;\nvar persistentPollingInterval = 5000;\n\nself.onInit = function() {\n var subscription = self.ctx.defaultSubscription;\n var rpcEnabled = subscription.rpcEnabled;\n var deviceName = 'Simulated';\n var prompt;\n if (subscription.targetEntityName && subscription.targetEntityName.length) {\n deviceName = subscription.targetEntityName;\n }\n if (self.ctx.settings.requestTimeout) {\n requestTimeout = self.ctx.settings.requestTimeout;\n }\n if (self.ctx.settings.requestPersistent) {\n requestPersistent = self.ctx.settings.requestPersistent;\n }\n if (self.ctx.settings.persistentPollingInterval) {\n persistentPollingInterval = self.ctx.settings.persistentPollingInterval;\n }\n var greetings = 'Welcome to ThingsBoard RPC debug terminal.\\n\\n';\n if (!rpcEnabled) {\n greetings += 'Target device is not set!\\n\\n';\n prompt = '';\n } else {\n greetings += 'Current target device for RPC commands: [[b;#fff;]' + deviceName + ']\\n\\n';\n greetings += 'Please type [[b;#fff;]\\'help\\'] to see usage.\\n';\n prompt = '[[b;#8bc34a;]' + deviceName +']> ';\n }\n \n var terminal = $('#device-terminal', self.ctx.$container).terminal(\n function(command) {\n if (command !== '') {\n try {\n var localCommand = command.trim();\n var requestUUID = uuidv4();\n if (localCommand === 'help') {\n printUsage(this);\n } else {\n var spaceIndex = localCommand.indexOf(' ');\n if (spaceIndex === -1 && !localCommand.length) {\n this.error(\"Wrong number of arguments!\");\n this.echo(' ');\n } else {\n var params;\n if (spaceIndex === -1) {\n spaceIndex = localCommand.length;\n }\n var name = localCommand.substr(0, spaceIndex);\n var args = localCommand.substr(spaceIndex + 1);\n if (args.length) {\n try {\n params = JSON.parse(args);\n } catch (e) {\n params = args;\n }\n }\n performRpc(this, name, params, requestUUID);\n }\n }\n } catch(e) {\n this.error(new String(e));\n }\n } else {\n this.echo('');\n }\n }, {\n greetings: greetings,\n prompt: prompt,\n enabled: rpcEnabled\n });\n \n if (!rpcEnabled) {\n terminal.error('No RPC target detected!').pause();\n }\n}\n\n\nfunction printUsage(terminal) {\n var commandsListText = '\\n[[b;#fff;]Usage:]\\n';\n commandsListText += ' <method> [params body]]\\n\\n';\n commandsListText += '[[b;#fff;]Example 1:]\\n'; \n commandsListText += ' myRemoteMethod1 myText\\n\\n'; \n commandsListText += '[[b;#fff;]Example 2:]\\n'; \n commandsListText += ' myOtherRemoteMethod \"{\\\\\"key1\\\\\": 2, \\\\\"key2\\\\\": \\\\\"myVal\\\\\"}\"\\n'; \n terminal.echo(new String(commandsListText));\n}\n\n\nfunction performRpc(terminal, method, params, requestUUID) {\n terminal.pause();\n self.ctx.controlApi.sendTwoWayCommand(method, params, requestTimeout, requestPersistent, persistentPollingInterval, requestUUID).subscribe(\n function success(responseBody) {\n terminal.echo(JSON.stringify(responseBody));\n terminal.echo(' ');\n terminal.resume();\n },\n function fail() {\n var errorText = self.ctx.defaultSubscription.rpcErrorText;\n terminal.error(errorText);\n terminal.echo(' ');\n terminal.resume();\n }\n );\n}\n\n\nfunction uuidv4() {\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {\n var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);\n return v.toString(16);\n });\n}\n\n \nself.onDestroy = function() {\n self.ctx.controlApi.completedCommand();\n}",
|
||||
"controllerScript": "var requestTimeout = 500;\nvar requestPersistent = false;\nvar persistentPollingInterval = 5000;\n\nself.onInit = function() {\n var subscription = self.ctx.defaultSubscription;\n var rpcEnabled = subscription.rpcEnabled;\n var deviceName = 'Simulated';\n var prompt;\n if (subscription.targetEntityName && subscription.targetEntityName.length) {\n deviceName = subscription.targetEntityName;\n }\n if (self.ctx.settings.requestTimeout) {\n requestTimeout = self.ctx.settings.requestTimeout;\n }\n if (self.ctx.settings.requestPersistent) {\n requestPersistent = self.ctx.settings.requestPersistent;\n }\n if (self.ctx.settings.persistentPollingInterval) {\n persistentPollingInterval = self.ctx.settings.persistentPollingInterval;\n }\n var greetings = 'Welcome to ThingsBoard RPC debug terminal.\\n\\n';\n if (!rpcEnabled) {\n greetings += 'Target device is not set!\\n\\n';\n prompt = '';\n } else {\n greetings += 'Current target device for RPC commands: [[b;#fff;]' + deviceName + ']\\n\\n';\n greetings += 'Please type [[b;#fff;]\\'help\\'] to see usage.\\n';\n prompt = '[[b;#8bc34a;]' + deviceName +']> ';\n }\n \n var terminal = $('#device-terminal', self.ctx.$container).terminal(\n function(command) {\n if (command !== '') {\n try {\n var localCommand = command.trim();\n var requestUUID = uuidv4();\n if (localCommand === 'help') {\n printUsage(this);\n } else {\n var spaceIndex = localCommand.indexOf(' ');\n if (spaceIndex === -1 && !localCommand.length) {\n this.error(\"Wrong number of arguments!\");\n this.echo(' ');\n } else {\n var params;\n if (spaceIndex === -1) {\n spaceIndex = localCommand.length;\n }\n var name = localCommand.substr(0, spaceIndex);\n var args = localCommand.substr(spaceIndex + 1);\n if (args.length) {\n try {\n params = JSON.parse(args);\n } catch (e) {\n params = args;\n }\n }\n performRpc(this, name, params, requestUUID);\n }\n }\n } catch(e) {\n this.error(new String(e));\n }\n } else {\n this.echo('');\n }\n }, {\n greetings: greetings,\n prompt: prompt,\n enabled: rpcEnabled\n });\n \n if (!rpcEnabled) {\n terminal.error('No RPC target detected!').pause();\n }\n}\n\n\nfunction printUsage(terminal) {\n var commandsListText = '\\n[[b;#fff;]Usage:]\\n';\n commandsListText += ' <method> [params body]\\n\\n';\n commandsListText += '[[b;#fff;]Example 1:]\\n'; \n commandsListText += ' myRemoteMethod1 myText\\n\\n'; \n commandsListText += '[[b;#fff;]Example 2:]\\n'; \n commandsListText += ' myOtherRemoteMethod \"{\\\\\"key1\\\\\": 2, \\\\\"key2\\\\\": \\\\\"myVal\\\\\"}\"\\n'; \n terminal.echo(new String(commandsListText));\n}\n\n\nfunction performRpc(terminal, method, params, requestUUID) {\n terminal.pause();\n self.ctx.controlApi.sendTwoWayCommand(method, params, requestTimeout, requestPersistent, persistentPollingInterval, requestUUID).subscribe(\n function success(responseBody) {\n terminal.echo(JSON.stringify(responseBody));\n terminal.echo(' ');\n terminal.resume();\n },\n function fail() {\n var errorText = self.ctx.defaultSubscription.rpcErrorText;\n terminal.error(errorText);\n terminal.echo(' ');\n terminal.resume();\n }\n );\n}\n\n\nfunction uuidv4() {\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {\n var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);\n return v.toString(16);\n });\n}\n\n \nself.onDestroy = function() {\n self.ctx.controlApi.completedCommand();\n}",
|
||||
"settingsSchema": "",
|
||||
"dataKeySettingsSchema": "{}\n",
|
||||
"settingsDirective": "tb-rpc-terminal-widget-settings",
|
||||
@ -43,4 +43,4 @@
|
||||
"public": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,6 +18,7 @@ package org.thingsboard.server.dao.sql.cf;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.thingsboard.server.common.data.id.CalculatedFieldId;
|
||||
import org.thingsboard.server.dao.model.sql.CalculatedFieldEntity;
|
||||
|
||||
@ -36,7 +37,10 @@ public interface CalculatedFieldRepository extends JpaRepository<CalculatedField
|
||||
|
||||
Page<CalculatedFieldEntity> findAllByTenantId(UUID tenantId, Pageable pageable);
|
||||
|
||||
Page<CalculatedFieldEntity> findAllByTenantIdAndEntityId(UUID tenantId, UUID entityId, Pageable pageable);
|
||||
@Query("SELECT cf FROM CalculatedFieldEntity cf WHERE cf.tenantId = :tenantId " +
|
||||
"AND cf.entityId = :entityId " +
|
||||
"AND (:textSearch IS NULL OR ilike(cf.name, CONCAT('%', :textSearch, '%')) = true)")
|
||||
Page<CalculatedFieldEntity> findAllByTenantIdAndEntityId(UUID tenantId, UUID entityId, String textSearch, Pageable pageable);
|
||||
|
||||
List<CalculatedFieldEntity> findAllByTenantId(UUID tenantId);
|
||||
|
||||
|
||||
@ -85,7 +85,7 @@ public class JpaCalculatedFieldDao extends JpaAbstractDao<CalculatedFieldEntity,
|
||||
@Override
|
||||
public PageData<CalculatedField> findAllByEntityId(TenantId tenantId, EntityId entityId, PageLink pageLink) {
|
||||
log.debug("Try to find calculated fields by entityId[{}] and pageLink [{}]", entityId, pageLink);
|
||||
return DaoUtil.toPageData(calculatedFieldRepository.findAllByTenantIdAndEntityId(tenantId.getId(), entityId.getId(), DaoUtil.toPageable(pageLink)));
|
||||
return DaoUtil.toPageData(calculatedFieldRepository.findAllByTenantIdAndEntityId(tenantId.getId(), entityId.getId(), pageLink.getTextSearch(), DaoUtil.toPageable(pageLink)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
FROM thingsboard/node:20.18.0-bookworm-slim
|
||||
FROM thingsboard/node:22.18.0-bookworm-slim
|
||||
|
||||
ENV NODE_ENV production
|
||||
ENV DOCKER_MODE true
|
||||
|
||||
@ -6,20 +6,20 @@
|
||||
"main": "server.ts",
|
||||
"bin": "server.js",
|
||||
"scripts": {
|
||||
"pkg": "tsc && pkg -t node18-linux-x64,node18-win-x64 --out-path ./target ./target/src && node install.js",
|
||||
"pkg": "tsc && pkg -t node22-linux-x64,node22-win-x64 --out-path ./target ./target/src && node install.js",
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"start": "nodemon --watch '.' --ext 'ts' --exec 'ts-node server.ts'",
|
||||
"start-prod": "nodemon --watch '.' --ext 'ts' --exec 'NODE_ENV=production ts-node server.ts'",
|
||||
"build": "tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"config": "^3.3.12",
|
||||
"express": "^4.21.1",
|
||||
"config": "^4.1.1",
|
||||
"express": "^5.1.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"kafkajs": "^2.2.4",
|
||||
"long": "^5.2.3",
|
||||
"long": "^5.3.2",
|
||||
"uuid-parse": "^1.1.0",
|
||||
"winston": "^3.16.0",
|
||||
"winston": "^3.17.0",
|
||||
"winston-daily-rotate-file": "^5.0.0"
|
||||
},
|
||||
"nyc": {
|
||||
@ -32,14 +32,14 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/config": "^3.3.5",
|
||||
"@types/express": "~4.17.21",
|
||||
"@types/node": "~20.17.6",
|
||||
"@types/express": "~5.0.3",
|
||||
"@types/node": "~22.17.2",
|
||||
"@types/uuid-parse": "^1.0.2",
|
||||
"fs-extra": "^11.2.0",
|
||||
"nodemon": "^3.1.7",
|
||||
"pkg": "^5.8.1",
|
||||
"@yao-pkg/pkg": "^6.6.0",
|
||||
"fs-extra": "^11.3.1",
|
||||
"nodemon": "^3.1.10",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "5.5.4"
|
||||
"typescript": "5.9.2"
|
||||
},
|
||||
"pkg": {
|
||||
"assets": [
|
||||
|
||||
@ -71,7 +71,7 @@
|
||||
<goal>install-node-and-yarn</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<nodeVersion>v20.18.0</nodeVersion>
|
||||
<nodeVersion>v22.18.0</nodeVersion>
|
||||
<yarnVersion>v1.22.22</yarnVersion>
|
||||
</configuration>
|
||||
</execution>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -14,7 +14,7 @@
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
FROM thingsboard/node:20.18.0-bookworm-slim
|
||||
FROM thingsboard/node:22.18.0-bookworm-slim
|
||||
|
||||
ENV NODE_ENV production
|
||||
ENV DOCKER_MODE true
|
||||
|
||||
@ -6,21 +6,21 @@
|
||||
"main": "server.ts",
|
||||
"bin": "server.js",
|
||||
"scripts": {
|
||||
"pkg": "tsc && pkg -t node18-linux-x64,node18-win-x64 --out-path ./target ./target/src && node install.js",
|
||||
"pkg": "tsc && pkg -t node22-linux-x64,node22-win-x64 --out-path ./target ./target/src && node install.js",
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"start": "nodemon --watch '.' --ext 'ts' --exec 'WEB_FOLDER=./target/web ts-node server.ts'",
|
||||
"start-prod": "nodemon --watch '.' --ext 'ts' --exec 'WEB_FOLDER=./target/web NODE_ENV=production ts-node server.ts'",
|
||||
"build": "tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"compression": "^1.7.5",
|
||||
"compression": "^1.8.1",
|
||||
"config": "^3.3.12",
|
||||
"connect-history-api-fallback": "^1.6.0",
|
||||
"express": "^4.21.1",
|
||||
"connect-history-api-fallback": "1.6.0",
|
||||
"express": "^5.1.0",
|
||||
"http": "0.0.0",
|
||||
"http-proxy": "^1.18.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
"winston": "^3.16.0",
|
||||
"winston": "^3.17.0",
|
||||
"winston-daily-rotate-file": "^5.0.0"
|
||||
},
|
||||
"nyc": {
|
||||
@ -32,17 +32,17 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/compression": "^1.7.5",
|
||||
"@types/compression": "^1.8.1",
|
||||
"@types/config": "^3.3.5",
|
||||
"@types/connect-history-api-fallback": "^1.5.4",
|
||||
"@types/express": "~4.17.21",
|
||||
"@types/http-proxy": "^1.17.15",
|
||||
"@types/node": "~20.17.6",
|
||||
"fs-extra": "^11.2.0",
|
||||
"nodemon": "^3.1.7",
|
||||
"pkg": "^5.8.1",
|
||||
"@types/connect-history-api-fallback": "1.5.4",
|
||||
"@types/express": "~5.0.3",
|
||||
"@types/http-proxy": "^1.17.16",
|
||||
"@types/node": "~22.17.2",
|
||||
"@yao-pkg/pkg": "^6.6.0",
|
||||
"fs-extra": "^11.3.1",
|
||||
"nodemon": "^3.1.10",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "5.5.4"
|
||||
"typescript": "5.9.2"
|
||||
},
|
||||
"pkg": {
|
||||
"assets": [
|
||||
|
||||
@ -80,7 +80,7 @@
|
||||
<goal>install-node-and-yarn</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<nodeVersion>v20.18.0</nodeVersion>
|
||||
<nodeVersion>v22.18.0</nodeVersion>
|
||||
<yarnVersion>v1.22.22</yarnVersion>
|
||||
</configuration>
|
||||
</execution>
|
||||
|
||||
1307
msa/web-ui/yarn.lock
1307
msa/web-ui/yarn.lock
File diff suppressed because it is too large
Load Diff
@ -56,7 +56,7 @@
|
||||
<goal>install-node-and-yarn</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<nodeVersion>v20.18.0</nodeVersion>
|
||||
<nodeVersion>v22.18.0</nodeVersion>
|
||||
<yarnVersion>v1.22.22</yarnVersion>
|
||||
</configuration>
|
||||
</execution>
|
||||
|
||||
@ -38,6 +38,7 @@ import {
|
||||
import { deepClone, isDefined, isDefinedAndNotNull, isNotEmptyStr, isString, isUndefined } from '@core/utils';
|
||||
import {
|
||||
Datasource,
|
||||
datasourcesHasAggregation,
|
||||
datasourcesHasOnlyComparisonAggregation,
|
||||
DatasourceType,
|
||||
defaultLegendConfig,
|
||||
@ -49,7 +50,8 @@ import {
|
||||
WidgetConfigMode,
|
||||
WidgetSize,
|
||||
widgetType,
|
||||
WidgetTypeDescriptor
|
||||
WidgetTypeDescriptor,
|
||||
widgetTypeHasTimewindow
|
||||
} from '@app/shared/models/widget.models';
|
||||
import { EntityType } from '@shared/models/entity-type.models';
|
||||
import { AliasFilterType, EntityAlias, EntityAliasFilter } from '@app/shared/models/alias.models';
|
||||
@ -234,6 +236,7 @@ export class DashboardUtilsService {
|
||||
public validateAndUpdateWidget(widget: Widget): Widget {
|
||||
widget.config = this.validateAndUpdateWidgetConfig(widget.config, widget.type);
|
||||
widget = this.validateAndUpdateWidgetTypeFqn(widget);
|
||||
this.removeTimewindowConfigIfUnused(widget);
|
||||
if (isDefined((widget as any).title)) {
|
||||
delete (widget as any).title;
|
||||
}
|
||||
@ -294,8 +297,11 @@ export class DashboardUtilsService {
|
||||
}
|
||||
widgetConfig.datasources = this.validateAndUpdateDatasources(widgetConfig.datasources);
|
||||
if (type === widgetType.latest) {
|
||||
const onlyHistoryTimewindow = datasourcesHasOnlyComparisonAggregation(widgetConfig.datasources);
|
||||
widgetConfig.timewindow = initModelFromDefaultTimewindow(widgetConfig.timewindow, true, onlyHistoryTimewindow, this.timeService);
|
||||
if (datasourcesHasAggregation(widgetConfig.datasources)) {
|
||||
const onlyHistoryTimewindow = datasourcesHasOnlyComparisonAggregation(widgetConfig.datasources);
|
||||
widgetConfig.timewindow = initModelFromDefaultTimewindow(widgetConfig.timewindow, true,
|
||||
onlyHistoryTimewindow, this.timeService, false);
|
||||
}
|
||||
} else if (type === widgetType.rpc) {
|
||||
if (widgetConfig.targetDeviceAliasIds && widgetConfig.targetDeviceAliasIds.length) {
|
||||
widgetConfig.targetDevice = {
|
||||
@ -348,6 +354,33 @@ export class DashboardUtilsService {
|
||||
return widgetConfig;
|
||||
}
|
||||
|
||||
private removeTimewindowConfigIfUnused(widget: Widget) {
|
||||
const widgetHasTimewindow = this.widgetHasTimewindow(widget);
|
||||
if (!widgetHasTimewindow || widget.config.useDashboardTimewindow) {
|
||||
delete widget.config.displayTimewindow;
|
||||
delete widget.config.timewindow;
|
||||
delete widget.config.timewindowStyle;
|
||||
|
||||
if (!widgetHasTimewindow) {
|
||||
delete widget.config.useDashboardTimewindow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private widgetHasTimewindow(widget: Widget): boolean {
|
||||
const widgetDefinition = findWidgetModelDefinition(widget);
|
||||
if (widgetDefinition) {
|
||||
return widgetDefinition.hasTimewindow(widget);
|
||||
}
|
||||
return widgetTypeHasTimewindow(widget.type)
|
||||
|| (widget.type === widgetType.latest && datasourcesHasAggregation(widget.config.datasources));
|
||||
}
|
||||
|
||||
public prepareWidgetForSaving(widget: Widget): Widget {
|
||||
this.removeTimewindowConfigIfUnused(widget);
|
||||
return widget;
|
||||
}
|
||||
|
||||
public prepareWidgetForScadaLayout(widget: Widget, isScada: boolean): Widget {
|
||||
const config = widget.config;
|
||||
config.showTitle = false;
|
||||
|
||||
@ -27,7 +27,8 @@ import { serverErrorCodesTranslations } from '@shared/models/constants';
|
||||
import { SubscriptionEntityInfo } from '@core/api/widget-api.models';
|
||||
import {
|
||||
CompiledTbFunction,
|
||||
compileTbFunction, GenericFunction,
|
||||
compileTbFunction,
|
||||
GenericFunction,
|
||||
isNotEmptyTbFunction,
|
||||
TbFunction
|
||||
} from '@shared/models/js-function.models';
|
||||
@ -196,6 +197,23 @@ export function deleteNullProperties(obj: any) {
|
||||
});
|
||||
}
|
||||
|
||||
export function deleteFalseProperties(obj: Record<string, any>): void {
|
||||
if (isUndefinedOrNull(obj)) {
|
||||
return;
|
||||
}
|
||||
Object.keys(obj).forEach((propName) => {
|
||||
if (obj[propName] === false || isUndefinedOrNull(obj[propName])) {
|
||||
delete obj[propName];
|
||||
} else if (isObject(obj[propName])) {
|
||||
deleteFalseProperties(obj[propName]);
|
||||
} else if (Array.isArray(obj[propName])) {
|
||||
(obj[propName] as any[]).forEach((elem) => {
|
||||
deleteFalseProperties(elem);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function objToBase64(obj: any): string {
|
||||
const json = JSON.stringify(obj);
|
||||
return btoa(encodeURIComponent(json).replace(/%([0-9A-F]{2})/g,
|
||||
@ -773,6 +791,33 @@ export function deepTrim<T>(obj: T): T {
|
||||
}, (Array.isArray(obj) ? [] : {}) as T);
|
||||
}
|
||||
|
||||
export function deepClean<T extends Record<string, any> | any[]>(obj: T, {
|
||||
cleanKeys = []
|
||||
} = {}): T {
|
||||
return _.transform(obj, (result, value, key) => {
|
||||
if (cleanKeys.includes(key)) {
|
||||
return;
|
||||
}
|
||||
if (Array.isArray(value) || isLiteralObject(value)) {
|
||||
value = deepClean(value, {cleanKeys});
|
||||
}
|
||||
if(isLiteralObject(value) && isEmpty(value)) {
|
||||
return;
|
||||
}
|
||||
if (Array.isArray(value) && !value.length) {
|
||||
return;
|
||||
}
|
||||
if (value === undefined || value === null || value === '' || Number.isNaN(value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Array.isArray(result)) {
|
||||
return result.push(value);
|
||||
}
|
||||
result[key] = value;
|
||||
});
|
||||
}
|
||||
|
||||
export function generateSecret(length?: number): string {
|
||||
if (isUndefined(length) || length == null) {
|
||||
length = 1;
|
||||
|
||||
@ -41,6 +41,7 @@ import { deepTrim } from '@core/utils';
|
||||
export interface AIModelDialogData {
|
||||
AIModel?: AiModel;
|
||||
isAdd?: boolean;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
@ -111,6 +112,10 @@ export class AIModelDialogComponent extends DialogComponent<AIModelDialogCompone
|
||||
})
|
||||
});
|
||||
|
||||
if (this.data.name) {
|
||||
this.aiModelForms.get('name').patchValue(this.data.name, {emitEvent: false});
|
||||
}
|
||||
|
||||
this.aiModelForms.get('configuration.provider').valueChanges.pipe(
|
||||
takeUntilDestroyed()
|
||||
).subscribe((provider: AiProvider) => {
|
||||
|
||||
@ -1323,6 +1323,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
|
||||
}
|
||||
|
||||
private addWidgetToDashboard(widget: Widget) {
|
||||
this.dashboardUtils.prepareWidgetForSaving(widget);
|
||||
if (this.addingLayoutCtx) {
|
||||
this.addWidgetToLayout(widget, this.addingLayoutCtx.id);
|
||||
this.addingLayoutCtx = null;
|
||||
@ -1410,7 +1411,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
|
||||
|
||||
saveWidget() {
|
||||
this.editWidgetComponent.widgetFormGroup.markAsPristine();
|
||||
const widget = deepClone(this.editingWidget);
|
||||
const widget = this.dashboardUtils.prepareWidgetForSaving(deepClone(this.editingWidget));
|
||||
const widgetLayout = deepClone(this.editingWidgetLayout);
|
||||
const id = this.editingWidgetOriginal.id;
|
||||
this.dashboardConfiguration.widgets[id] = widget;
|
||||
|
||||
@ -29,7 +29,7 @@
|
||||
labelText="rule-node-config.ai.model"
|
||||
(entityChanged)="onEntityChange($event)"
|
||||
[entityType]="entityType.AI_MODEL"
|
||||
(createNew)="createModelAi('modelId')"
|
||||
(createNew)="createModelAi($event, 'modelId')"
|
||||
formControlName="modelId">
|
||||
</tb-entity-autocomplete>
|
||||
</section>
|
||||
|
||||
@ -74,11 +74,12 @@ export class AiConfigComponent extends RuleNodeConfigurationComponent {
|
||||
}
|
||||
}
|
||||
|
||||
protected prepareOutputConfig(configuration: RuleNodeConfiguration): RuleNodeConfiguration {
|
||||
protected prepareOutputConfig(): RuleNodeConfiguration {
|
||||
const config = this.configForm().getRawValue();
|
||||
if (!this.aiConfigForm.get('systemPrompt').value) {
|
||||
delete configuration.systemPrompt;
|
||||
delete config.systemPrompt;
|
||||
}
|
||||
return deepTrim(configuration);
|
||||
return deepTrim(config);
|
||||
}
|
||||
|
||||
onEntityChange($event: AiModel) {
|
||||
@ -98,12 +99,13 @@ export class AiConfigComponent extends RuleNodeConfigurationComponent {
|
||||
return this.translate.instant(`rule-node-config.ai.response-format-hint-${this.aiConfigForm.get('responseFormat.type').value}`);
|
||||
}
|
||||
|
||||
createModelAi(formControl: string) {
|
||||
createModelAi(name: string, formControl: string) {
|
||||
this.dialog.open<AIModelDialogComponent, AIModelDialogData, AiModel>(AIModelDialogComponent, {
|
||||
disableClose: true,
|
||||
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
|
||||
data: {
|
||||
isAdd: true
|
||||
isAdd: true,
|
||||
name
|
||||
}
|
||||
}).afterClosed()
|
||||
.subscribe((model) => {
|
||||
|
||||
@ -23,11 +23,19 @@ import { PageComponent } from '@shared/components/page.component';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { AppState } from '@core/core.state';
|
||||
import { AbstractControl, UntypedFormGroup } from '@angular/forms';
|
||||
import { DataKey, DatasourceType, Widget, WidgetConfigMode, widgetType } from '@shared/models/widget.models';
|
||||
import {
|
||||
DataKey,
|
||||
DatasourceType,
|
||||
Widget,
|
||||
widgetTypeCanHaveTimewindow,
|
||||
WidgetConfigMode,
|
||||
widgetType
|
||||
} from '@shared/models/widget.models';
|
||||
import { WidgetConfigComponent } from '@home/components/widget/widget-config.component';
|
||||
import { isDefinedAndNotNull } from '@core/utils';
|
||||
import { isDefinedAndNotNull, isUndefinedOrNull } from '@core/utils';
|
||||
import { IAliasController } from '@core/api/widget-api.models';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { initModelFromDefaultTimewindow } from '@shared/models/time/time.models';
|
||||
|
||||
export type WidgetConfigCallbacks = DatasourceCallbacks & WidgetActionCallbacks;
|
||||
|
||||
@ -107,6 +115,11 @@ export abstract class BasicWidgetConfigComponent extends PageComponent implement
|
||||
if (this.isAdd) {
|
||||
this.setupDefaults(widgetConfig);
|
||||
}
|
||||
if (widgetTypeCanHaveTimewindow(widgetConfig.widgetType) && isUndefinedOrNull(widgetConfig.config.timewindow)) {
|
||||
widgetConfig.config.timewindow = initModelFromDefaultTimewindow(null,
|
||||
widgetConfig.widgetType === widgetType.latest, false, this.widgetConfigComponent.timeService,
|
||||
widgetConfig.widgetType === widgetType.timeseries);
|
||||
}
|
||||
this.onConfigSet(widgetConfig);
|
||||
this.updateValidators(false);
|
||||
for (const trigger of this.validatorTriggers()) {
|
||||
|
||||
@ -45,14 +45,14 @@
|
||||
</span>
|
||||
<span class="flex-1"></span>
|
||||
<button *ngIf="allowAcknowledgment"
|
||||
mat-icon-button [disabled]="isLoading$ | async"
|
||||
mat-icon-button
|
||||
matTooltip="{{ 'alarm.acknowledge' | translate }}"
|
||||
matTooltipPosition="above"
|
||||
(click)="ackAlarms($event)">
|
||||
<mat-icon>done</mat-icon>
|
||||
</button>
|
||||
<button *ngIf="ctx.settings.allowClear" mat-icon-button
|
||||
[disabled]="isLoading$ | async"
|
||||
<button *ngIf="ctx.settings.allowClear"
|
||||
mat-icon-button
|
||||
matTooltip="{{ 'alarm.clear' | translate }}"
|
||||
matTooltipPosition="above"
|
||||
(click)="clearAlarms($event)">
|
||||
@ -107,7 +107,7 @@
|
||||
</span>
|
||||
</ng-template>
|
||||
<button *ngIf="allowAssign"
|
||||
mat-icon-button [disabled]="isLoading$ | async"
|
||||
mat-icon-button
|
||||
matTooltip="{{ 'alarm.assign' | translate }}"
|
||||
matTooltipPosition="above"
|
||||
(click)="openAlarmAssigneePanel($event, alarm)">
|
||||
|
||||
@ -65,7 +65,7 @@
|
||||
[style.min-width]="(entityDatasource.countCellButtonAction * 48) + 'px'">
|
||||
<ng-container *ngFor="let actionDescriptor of entity.actionCellButtons; trackBy: trackByActionCellDescriptionId">
|
||||
<span *ngIf="!actionDescriptor.icon" style="width: 48px;"></span>
|
||||
<button mat-icon-button [disabled]="isLoading$ | async"
|
||||
<button mat-icon-button
|
||||
*ngIf="actionDescriptor.icon"
|
||||
matTooltip="{{ actionDescriptor.displayName }}"
|
||||
matTooltipPosition="above"
|
||||
@ -83,7 +83,6 @@
|
||||
<mat-menu #cellActionsMenu="matMenu" xPosition="before">
|
||||
<ng-container *ngFor="let actionDescriptor of entity.actionCellButtons; trackBy: trackByActionCellDescriptionId">
|
||||
<button mat-menu-item *ngIf="actionDescriptor.icon"
|
||||
[disabled]="isLoading$ | async"
|
||||
(click)="onActionButtonClick($event, entity, actionDescriptor)">
|
||||
<tb-icon matMenuItemIcon>{{actionDescriptor.icon}}</tb-icon>
|
||||
<span>{{ actionDescriptor.displayName }}</span>
|
||||
|
||||
@ -56,6 +56,9 @@ export const createTooltip = (map: TbMap<any>,
|
||||
}
|
||||
}
|
||||
});
|
||||
layer.on('mousemove', (e) => {
|
||||
tooltip.setLatLng(e.latlng);
|
||||
});
|
||||
layer.on('mouseout', () => {
|
||||
tooltip.close();
|
||||
});
|
||||
|
||||
@ -35,117 +35,73 @@
|
||||
</mat-icon>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<ng-container [formGroup]="mobileActionTypeFormGroup" [ngSwitch]="mobileActionFormGroup.get('type').value">
|
||||
<ng-template [ngSwitchCase]="mobileActionType.deviceProvision">
|
||||
<tb-js-func
|
||||
formControlName="handleProvisionSuccessFunction"
|
||||
functionName="handleProvisionSuccess"
|
||||
withModules
|
||||
[functionArgs]="['deviceName', '$event', 'widgetContext', 'entityId', 'entityName', 'additionalParams', 'entityLabel']"
|
||||
[globalVariables]="functionScopeVariables"
|
||||
[editorCompleter]="customActionEditorCompleter"
|
||||
hideBrackets
|
||||
></tb-js-func>
|
||||
</ng-template>
|
||||
<ng-template [ngSwitchCase]="mobileActionFormGroup.get('type').value === mobileActionType.mapDirection ||
|
||||
mobileActionFormGroup.get('type').value === mobileActionType.mapLocation ?
|
||||
mobileActionFormGroup.get('type').value : ''">
|
||||
<tb-js-func
|
||||
formControlName="getLocationFunction"
|
||||
functionName="getLocation"
|
||||
withModules
|
||||
[functionArgs]="['$event', 'widgetContext', 'entityId', 'entityName', 'additionalParams', 'entityLabel']"
|
||||
[globalVariables]="functionScopeVariables"
|
||||
[editorCompleter]="customActionEditorCompleter"
|
||||
hideBrackets
|
||||
helpId="widget/action/mobile_get_location_fn"
|
||||
></tb-js-func>
|
||||
</ng-template>
|
||||
<ng-template [ngSwitchCase]="mobileActionType.makePhoneCall">
|
||||
<tb-js-func
|
||||
formControlName="getPhoneNumberFunction"
|
||||
functionName="getPhoneNumber"
|
||||
withModules
|
||||
[functionArgs]="['$event', 'widgetContext', 'entityId', 'entityName', 'additionalParams', 'entityLabel']"
|
||||
[globalVariables]="functionScopeVariables"
|
||||
[editorCompleter]="customActionEditorCompleter"
|
||||
hideBrackets
|
||||
helpId="widget/action/mobile_get_phone_number_fn"
|
||||
></tb-js-func>
|
||||
</ng-template>
|
||||
<ng-template [ngSwitchCase]="mobileActionFormGroup.get('type').value === mobileActionType.takePhoto ||
|
||||
mobileActionFormGroup.get('type').value === mobileActionType.takePictureFromGallery ||
|
||||
mobileActionFormGroup.get('type').value === mobileActionType.takeScreenshot ?
|
||||
mobileActionFormGroup.get('type').value : ''">
|
||||
<tb-js-func
|
||||
formControlName="processImageFunction"
|
||||
functionName="processImage"
|
||||
withModules
|
||||
[functionArgs]="['imageUrl', '$event', 'widgetContext', 'entityId', 'entityName', 'additionalParams', 'entityLabel']"
|
||||
[globalVariables]="functionScopeVariables"
|
||||
[editorCompleter]="customActionEditorCompleter"
|
||||
hideBrackets
|
||||
helpId="widget/action/mobile_process_image_fn"
|
||||
></tb-js-func>
|
||||
</ng-template>
|
||||
<ng-template [ngSwitchCase]="mobileActionType.scanQrCode">
|
||||
<tb-js-func
|
||||
formControlName="processQrCodeFunction"
|
||||
functionName="processQrCode"
|
||||
withModules
|
||||
[functionArgs]="['code', 'format', '$event', 'widgetContext', 'entityId', 'entityName', 'additionalParams', 'entityLabel']"
|
||||
[globalVariables]="functionScopeVariables"
|
||||
[editorCompleter]="customActionEditorCompleter"
|
||||
hideBrackets
|
||||
helpId="widget/action/mobile_process_qr_code_fn"
|
||||
></tb-js-func>
|
||||
</ng-template>
|
||||
<ng-template [ngSwitchCase]="mobileActionType.getLocation">
|
||||
<tb-js-func
|
||||
formControlName="processLocationFunction"
|
||||
functionName="processLocation"
|
||||
withModules
|
||||
[functionArgs]="['latitude', 'longitude', '$event', 'widgetContext', 'entityId', 'entityName', 'additionalParams', 'entityLabel']"
|
||||
[globalVariables]="functionScopeVariables"
|
||||
[editorCompleter]="customActionEditorCompleter"
|
||||
hideBrackets
|
||||
helpId="widget/action/mobile_process_location_fn"
|
||||
></tb-js-func>
|
||||
</ng-template>
|
||||
<ng-template [ngSwitchCase]="mobileActionFormGroup.get('type').value === mobileActionType.mapDirection ||
|
||||
mobileActionFormGroup.get('type').value === mobileActionType.mapLocation ||
|
||||
mobileActionFormGroup.get('type').value === mobileActionType.makePhoneCall ?
|
||||
mobileActionFormGroup.get('type').value : ''">
|
||||
<tb-js-func
|
||||
formControlName="processLaunchResultFunction"
|
||||
functionName="processLaunchResult"
|
||||
withModules
|
||||
[functionArgs]="['launched', '$event', 'widgetContext', 'entityId', 'entityName', 'additionalParams', 'entityLabel']"
|
||||
[globalVariables]="functionScopeVariables"
|
||||
[editorCompleter]="customActionEditorCompleter"
|
||||
hideBrackets
|
||||
helpId="widget/action/mobile_process_launch_result_fn"
|
||||
></tb-js-func>
|
||||
</ng-template>
|
||||
<ng-container [formGroup]="mobileActionTypeFormGroup">
|
||||
@if (mobileActionFormGroup.get('type').value === mobileActionType.takePhoto ||
|
||||
mobileActionFormGroup.get('type').value === mobileActionType.takePictureFromGallery ||
|
||||
mobileActionFormGroup.get('type').value === mobileActionType.takeScreenshot) {
|
||||
<div class="tb-form-row">
|
||||
<mat-slide-toggle class="mat-slide" formControlName="saveToGallery">
|
||||
{{ 'widget-action.mobile.save-to-gallery' | translate }}
|
||||
</mat-slide-toggle>
|
||||
</div>
|
||||
}
|
||||
@if (mobileActionFormGroup.get('type').value === mobileActionType.deviceProvision) {
|
||||
<div class="tb-form-row">
|
||||
<div class="fixed-title-width">{{ 'widget-action.mobile.provision-type' | translate }}*</div>
|
||||
<mat-form-field class="flex-1" appearance="outline" subscriptSizing="dynamic">
|
||||
<mat-select formControlName="provisionType">
|
||||
<mat-option *ngFor="let type of provisionTypes" [value]="type">
|
||||
{{ provisionTypeTranslationMap.get(type) | translate }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
}
|
||||
|
||||
@for (config of actionConfig; track config.formControlName) {
|
||||
<div class="tb-form-panel stroked">
|
||||
<mat-expansion-panel class="tb-settings">
|
||||
<mat-expansion-panel-header>
|
||||
<mat-panel-description class="flex items-stretch justify-start">
|
||||
{{ config.title | translate }}
|
||||
</mat-panel-description>
|
||||
</mat-expansion-panel-header>
|
||||
<tb-js-func
|
||||
[formControlName]="config.formControlName"
|
||||
[functionName]="config.functionName"
|
||||
withModules
|
||||
[functionArgs]="config.functionArgs"
|
||||
[globalVariables]="functionScopeVariables"
|
||||
[editorCompleter]="customActionEditorCompleter"
|
||||
hideBrackets
|
||||
[helpId]="config.helpId"
|
||||
></tb-js-func>
|
||||
</mat-expansion-panel>
|
||||
</div>
|
||||
}
|
||||
</ng-container>
|
||||
<tb-js-func *ngIf="mobileActionFormGroup.get('type').value"
|
||||
formControlName="handleEmptyResultFunction"
|
||||
functionName="handleEmptyResult"
|
||||
withModules
|
||||
[functionArgs]="['$event', 'widgetContext', 'entityId', 'entityName', 'additionalParams', 'entityLabel']"
|
||||
[globalVariables]="functionScopeVariables"
|
||||
[editorCompleter]="customActionEditorCompleter"
|
||||
hideBrackets
|
||||
helpId="widget/action/mobile_handle_empty_result_fn"
|
||||
></tb-js-func>
|
||||
<tb-js-func *ngIf="mobileActionFormGroup.get('type').value"
|
||||
formControlName="handleErrorFunction"
|
||||
functionName="handleError"
|
||||
withModules
|
||||
[functionArgs]="['error', '$event', 'widgetContext', 'entityId', 'entityName', 'additionalParams', 'entityLabel']"
|
||||
[globalVariables]="functionScopeVariables"
|
||||
[editorCompleter]="customActionEditorCompleter"
|
||||
hideBrackets
|
||||
helpId="widget/action/mobile_handle_error_fn"
|
||||
></tb-js-func>
|
||||
|
||||
@if(mobileActionFormGroup.get('type').value) {
|
||||
@for (config of commonActionConfig; track config.formControlName) {
|
||||
<div class="tb-form-panel stroked">
|
||||
<mat-expansion-panel class="tb-settings">
|
||||
<mat-expansion-panel-header>
|
||||
<mat-panel-description class="flex items-stretch justify-start">
|
||||
{{ config.title | translate }}
|
||||
</mat-panel-description>
|
||||
</mat-expansion-panel-header>
|
||||
<tb-js-func
|
||||
[formControlName]="config.formControlName"
|
||||
[functionName]="config.functionName"
|
||||
withModules
|
||||
[functionArgs]="config.functionArgs"
|
||||
[globalVariables]="functionScopeVariables"
|
||||
[editorCompleter]="customActionEditorCompleter"
|
||||
hideBrackets
|
||||
[helpId]="config.helpId"
|
||||
></tb-js-func>
|
||||
</mat-expansion-panel>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
|
||||
@ -24,6 +24,9 @@ import {
|
||||
} from '@angular/forms';
|
||||
import { coerceBooleanProperty } from '@angular/cdk/coercion';
|
||||
import {
|
||||
ActionConfig,
|
||||
ProvisionType,
|
||||
provisionTypeTranslationMap,
|
||||
WidgetActionType,
|
||||
WidgetMobileActionDescriptor,
|
||||
WidgetMobileActionType,
|
||||
@ -35,6 +38,7 @@ import {
|
||||
getDefaultGetPhoneNumberFunction,
|
||||
getDefaultHandleEmptyResultFunction,
|
||||
getDefaultHandleErrorFunction,
|
||||
getDefaultHandleNonMobileFallBackFunction,
|
||||
getDefaultProcessImageFunction,
|
||||
getDefaultProcessLaunchResultFunction,
|
||||
getDefaultProcessLocationFunction,
|
||||
@ -68,6 +72,12 @@ export class MobileActionEditorComponent implements ControlValueAccessor, OnInit
|
||||
|
||||
functionScopeVariables: string[];
|
||||
|
||||
actionConfig: ActionConfig[];
|
||||
commonActionConfig: ActionConfig[];
|
||||
|
||||
provisionTypes: string[] = Object.keys(ProvisionType);
|
||||
provisionTypeTranslationMap = provisionTypeTranslationMap;
|
||||
|
||||
private requiredValue: boolean;
|
||||
get required(): boolean {
|
||||
return this.requiredValue;
|
||||
@ -99,8 +109,10 @@ export class MobileActionEditorComponent implements ControlValueAccessor, OnInit
|
||||
this.mobileActionFormGroup = this.fb.group({
|
||||
type: [null, Validators.required],
|
||||
handleEmptyResultFunction: [null],
|
||||
handleErrorFunction: [null]
|
||||
handleErrorFunction: [null],
|
||||
handleNonMobileFallbackFunction: [null]
|
||||
});
|
||||
this.getCommonActionConfigs();
|
||||
this.mobileActionFormGroup.get('type').valueChanges.pipe(
|
||||
takeUntilDestroyed(this.destroyRef)
|
||||
).subscribe((type: WidgetMobileActionType) => {
|
||||
@ -109,6 +121,7 @@ export class MobileActionEditorComponent implements ControlValueAccessor, OnInit
|
||||
action = {...action, ...this.mobileActionTypeFormGroup.value};
|
||||
}
|
||||
this.updateMobileActionType(type, action);
|
||||
this.getActionConfigs();
|
||||
});
|
||||
this.mobileActionFormGroup.valueChanges.pipe(
|
||||
takeUntilDestroyed(this.destroyRef)
|
||||
@ -133,10 +146,14 @@ export class MobileActionEditorComponent implements ControlValueAccessor, OnInit
|
||||
}
|
||||
|
||||
writeValue(value: WidgetMobileActionDescriptor | null): void {
|
||||
this.mobileActionFormGroup.patchValue({type: value?.type,
|
||||
handleEmptyResultFunction: value?.handleEmptyResultFunction,
|
||||
handleErrorFunction: value?.handleErrorFunction}, {emitEvent: false});
|
||||
this.mobileActionFormGroup.patchValue({
|
||||
type: value?.type,
|
||||
handleEmptyResultFunction: value?.handleEmptyResultFunction,
|
||||
handleErrorFunction: value?.handleErrorFunction,
|
||||
handleNonMobileFallbackFunction: value?.handleNonMobileFallbackFunction
|
||||
}, {emitEvent: false});
|
||||
this.updateMobileActionType(value?.type, value);
|
||||
this.getActionConfigs();
|
||||
}
|
||||
|
||||
private updateModel() {
|
||||
@ -164,6 +181,12 @@ export class MobileActionEditorComponent implements ControlValueAccessor, OnInit
|
||||
handleErrorFunction = getDefaultHandleErrorFunction(type);
|
||||
this.mobileActionFormGroup.patchValue({handleErrorFunction}, {emitEvent: false});
|
||||
}
|
||||
let handleNonMobileFallbackFunction = action?.handleNonMobileFallbackFunction;
|
||||
const defaultHandleNonMobileFallbackFunction = getDefaultHandleNonMobileFallBackFunction();
|
||||
if (defaultHandleNonMobileFallbackFunction !== handleNonMobileFallbackFunction) {
|
||||
handleNonMobileFallbackFunction = getDefaultHandleNonMobileFallBackFunction();
|
||||
this.mobileActionFormGroup.patchValue({handleNonMobileFallbackFunction}, {emitEvent: false});
|
||||
}
|
||||
}
|
||||
this.mobileActionTypeFormGroup = this.fb.group({});
|
||||
if (type) {
|
||||
@ -183,6 +206,10 @@ export class MobileActionEditorComponent implements ControlValueAccessor, OnInit
|
||||
'processImageFunction',
|
||||
this.fb.control(processImageFunction, [])
|
||||
);
|
||||
this.mobileActionTypeFormGroup.addControl(
|
||||
'saveToGallery',
|
||||
this.fb.control(action?.saveToGallery || false, [])
|
||||
);
|
||||
break;
|
||||
case WidgetMobileActionType.mapDirection:
|
||||
case WidgetMobileActionType.mapLocation:
|
||||
@ -267,6 +294,10 @@ export class MobileActionEditorComponent implements ControlValueAccessor, OnInit
|
||||
'handleProvisionSuccessFunction',
|
||||
this.fb.control(handleProvisionSuccessFunction, [Validators.required])
|
||||
);
|
||||
this.mobileActionTypeFormGroup.addControl(
|
||||
'provisionType',
|
||||
this.fb.control(action?.provisionType || ProvisionType.auto, [])
|
||||
);
|
||||
}
|
||||
}
|
||||
this.mobileActionTypeFormGroup.valueChanges.pipe(
|
||||
@ -276,5 +307,108 @@ export class MobileActionEditorComponent implements ControlValueAccessor, OnInit
|
||||
});
|
||||
}
|
||||
|
||||
getActionConfigs() {
|
||||
const type = this.mobileActionFormGroup.get('type').value;
|
||||
this.actionConfig = [];
|
||||
switch (type) {
|
||||
case this.mobileActionType.deviceProvision:
|
||||
this.actionConfig.push({
|
||||
title: 'widget-action.mobile.handle-provision-success-function',
|
||||
formControlName: 'handleProvisionSuccessFunction',
|
||||
functionName: 'handleProvisionSuccess',
|
||||
functionArgs: ['deviceName', '$event', 'widgetContext', 'entityId', 'entityName', 'additionalParams', 'entityLabel']
|
||||
});
|
||||
break;
|
||||
case this.mobileActionType.mapDirection:
|
||||
case this.mobileActionType.mapLocation:
|
||||
this.actionConfig.push({
|
||||
title: 'widget-action.mobile.get-location-function',
|
||||
formControlName: 'getLocationFunction',
|
||||
functionName: 'getLocation',
|
||||
functionArgs: ['$event', 'widgetContext', 'entityId', 'entityName', 'additionalParams', 'entityLabel'],
|
||||
helpId: 'widget/action/mobile_get_location_fn'
|
||||
});
|
||||
this.actionConfig.push({
|
||||
title: 'widget-action.mobile.process-launch-result-function',
|
||||
formControlName: 'processLaunchResultFunction',
|
||||
functionName: 'processLaunchResult',
|
||||
functionArgs: ['launched', '$event', 'widgetContext', 'entityId', 'entityName', 'additionalParams', 'entityLabel'],
|
||||
helpId: 'widget/action/mobile_process_launch_result_fn'
|
||||
});
|
||||
break;
|
||||
case this.mobileActionType.makePhoneCall:
|
||||
this.actionConfig.push({
|
||||
title: 'widget-action.mobile.get-phone-number-function',
|
||||
formControlName: 'getPhoneNumberFunction',
|
||||
functionName: 'getPhoneNumber',
|
||||
functionArgs: ['$event', 'widgetContext', 'entityId', 'entityName', 'additionalParams', 'entityLabel'],
|
||||
helpId: 'widget/action/mobile_get_phone_number_fn'
|
||||
});
|
||||
this.actionConfig.push({
|
||||
title: 'widget-action.mobile.process-launch-result-function',
|
||||
formControlName: 'processLaunchResultFunction',
|
||||
functionName: 'processLaunchResult',
|
||||
functionArgs: ['launched', '$event', 'widgetContext', 'entityId', 'entityName', 'additionalParams', 'entityLabel'],
|
||||
helpId: 'widget/action/mobile_process_launch_result_fn'
|
||||
});
|
||||
break;
|
||||
case this.mobileActionType.takePhoto:
|
||||
case this.mobileActionType.takePictureFromGallery:
|
||||
case this.mobileActionType.takeScreenshot:
|
||||
this.actionConfig.push({
|
||||
title: 'widget-action.mobile.process-image-function',
|
||||
formControlName: 'processImageFunction',
|
||||
functionName: 'processImage',
|
||||
functionArgs: ['imageUrl', '$event', 'widgetContext', 'entityId', 'entityName', 'additionalParams', 'entityLabel'],
|
||||
helpId: 'widget/action/mobile_process_image_fn'
|
||||
});
|
||||
break;
|
||||
case this.mobileActionType.scanQrCode:
|
||||
this.actionConfig.push({
|
||||
title: 'widget-action.mobile.process-qr-code-function',
|
||||
formControlName: 'processQrCodeFunction',
|
||||
functionName: 'processQrCode',
|
||||
functionArgs: ['code', 'format', '$event', 'widgetContext', 'entityId', 'entityName', 'additionalParams', 'entityLabel'],
|
||||
helpId: 'widget/action/mobile_process_qr_code_fn'
|
||||
});
|
||||
break;
|
||||
case this.mobileActionType.getLocation:
|
||||
this.actionConfig.push({
|
||||
title: 'widget-action.mobile.process-location-function',
|
||||
formControlName: 'processLocationFunction',
|
||||
functionName: 'processLocation',
|
||||
functionArgs: ['latitude', 'longitude', '$event', 'widgetContext', 'entityId', 'entityName', 'additionalParams', 'entityLabel'],
|
||||
helpId: 'widget/action/mobile_process_location_fn'
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
getCommonActionConfigs() {
|
||||
this.commonActionConfig = [
|
||||
{
|
||||
title: 'widget-action.mobile.handle-empty-result-function',
|
||||
formControlName: 'handleEmptyResultFunction',
|
||||
functionName: 'handleEmptyResult',
|
||||
functionArgs: ['$event', 'widgetContext', 'entityId', 'entityName', 'additionalParams', 'entityLabel'],
|
||||
helpId: 'widget/action/mobile_handle_empty_result_fn'
|
||||
},
|
||||
{
|
||||
title: 'widget-action.mobile.handle-error-function',
|
||||
formControlName: 'handleErrorFunction',
|
||||
functionName: 'handleError',
|
||||
functionArgs: ['error', '$event', 'widgetContext', 'entityId', 'entityName', 'additionalParams', 'entityLabel'],
|
||||
helpId: 'widget/action/mobile_handle_error_fn'
|
||||
},
|
||||
{
|
||||
title: 'widget-action.mobile.handle-non-mobile-fallback-function',
|
||||
formControlName: 'handleNonMobileFallbackFunction',
|
||||
functionName: 'handleNonMobileFallback',
|
||||
functionArgs: ['$event', 'widgetContext'],
|
||||
helpId: 'widget/action/mobile_handle_non_mobile_fallback_fn'
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
protected readonly WidgetActionType = WidgetActionType;
|
||||
}
|
||||
|
||||
@ -172,6 +172,14 @@ const handleErrorFunctionTemplate: TbFunction =
|
||||
' }, 100);\n' +
|
||||
'}\n';
|
||||
|
||||
const handleNonMobileFallbackFunctionTemplate: TbFunction =
|
||||
'// Optional function body to handle non-mobile fallback \n' +
|
||||
'showFallbackToast();\n' +
|
||||
'\n' +
|
||||
'function showFallbackToast(title, error) {\n' +
|
||||
' widgetContext.showWarnToast(\'This action is only available in the mobile application.\');\n' +
|
||||
'}\n';
|
||||
|
||||
const getLocationFunctionTemplate: TbFunction =
|
||||
'// Function body that should return location as array of two numbers (latitude, longitude) for further processing by mobile action.\n' +
|
||||
'// Usually location can be obtained from entity attributes/telemetry. \n\n' +
|
||||
@ -326,3 +334,5 @@ export const getDefaultHandleErrorFunction = (type: WidgetMobileActionType): TbF
|
||||
}
|
||||
return handleErrorFunctionTemplate.replace('--TITLE--', title);
|
||||
};
|
||||
|
||||
export const getDefaultHandleNonMobileFallBackFunction = () => handleNonMobileFallbackFunctionTemplate;
|
||||
|
||||
@ -78,7 +78,7 @@
|
||||
<span *ngIf="!actionDescriptor.icon" style="width: 40px;"></span>
|
||||
<button *ngIf="actionDescriptor.icon"
|
||||
class="tb-mat-40"
|
||||
mat-icon-button [disabled]="isLoading$ | async"
|
||||
mat-icon-button
|
||||
matTooltip="{{ actionDescriptor.displayName }}"
|
||||
matTooltipPosition="above"
|
||||
(click)="onActionButtonClick($event, row, actionDescriptor)">
|
||||
@ -96,7 +96,6 @@
|
||||
<mat-menu #cellActionsMenu="matMenu" xPosition="before">
|
||||
<ng-container *ngFor="let actionDescriptor of row.actionCellButtons; trackBy: trackByActionCellDescriptionId">
|
||||
<button mat-menu-item *ngIf="actionDescriptor.icon"
|
||||
[disabled]="isLoading$ | async"
|
||||
(click)="onActionButtonClick($event, row, actionDescriptor)">
|
||||
<tb-icon matMenuItemIcon>{{actionDescriptor.icon}}</tb-icon>
|
||||
<span>{{ actionDescriptor.displayName }}</span>
|
||||
|
||||
@ -38,6 +38,7 @@ import {
|
||||
TargetDevice,
|
||||
targetDeviceValid,
|
||||
Widget,
|
||||
widgetTypeCanHaveTimewindow,
|
||||
WidgetConfigMode,
|
||||
widgetType
|
||||
} from '@shared/models/widget.models';
|
||||
@ -53,7 +54,7 @@ import {
|
||||
Validators
|
||||
} from '@angular/forms';
|
||||
import { WidgetConfigComponentData } from '@home/models/widget-component.models';
|
||||
import { deepClone, genNextLabel, isDefined, isObject } from '@app/core/utils';
|
||||
import { deepClone, genNextLabel, isDefined, isDefinedAndNotNull, isObject } from '@app/core/utils';
|
||||
import { alarmFields, AlarmSearchStatus } from '@shared/models/alarm.models';
|
||||
import { IAliasController } from '@core/api/widget-api.models';
|
||||
import { EntityAlias } from '@shared/models/alias.models';
|
||||
@ -84,6 +85,8 @@ import { DataKeySettingsFunction } from '@home/components/widget/lib/settings/co
|
||||
import { defaultFormProperties, FormProperty } from '@shared/models/dynamic-form.models';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { WidgetService } from '@core/http/widget.service';
|
||||
import { TimeService } from '@core/services/time.service';
|
||||
import { initModelFromDefaultTimewindow } from '@shared/models/time/time.models';
|
||||
import Timeout = NodeJS.Timeout;
|
||||
|
||||
@Component({
|
||||
@ -201,6 +204,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, OnDe
|
||||
constructor(protected store: Store<AppState>,
|
||||
private utils: UtilsService,
|
||||
private entityService: EntityService,
|
||||
public timeService: TimeService,
|
||||
private dialog: MatDialog,
|
||||
public translate: TranslateService,
|
||||
private fb: UntypedFormBuilder,
|
||||
@ -366,16 +370,16 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, OnDe
|
||||
this.dataSettings = this.fb.group({});
|
||||
this.targetDeviceSettings = this.fb.group({});
|
||||
this.advancedSettings = this.fb.group({});
|
||||
if (this.widgetType === widgetType.timeseries || this.widgetType === widgetType.alarm || this.widgetType === widgetType.latest) {
|
||||
if (widgetTypeCanHaveTimewindow(this.widgetType)) {
|
||||
this.dataSettings.addControl('timewindowConfig', this.fb.control({
|
||||
useDashboardTimewindow: true,
|
||||
displayTimewindow: true,
|
||||
timewindow: null,
|
||||
timewindowStyle: null
|
||||
}));
|
||||
if (this.widgetType === widgetType.alarm) {
|
||||
this.dataSettings.addControl('alarmFilterConfig', this.fb.control(null));
|
||||
}
|
||||
}
|
||||
if (this.widgetType === widgetType.alarm) {
|
||||
this.dataSettings.addControl('alarmFilterConfig', this.fb.control(null));
|
||||
}
|
||||
if (this.modelValue.isDataEnabled) {
|
||||
if (this.widgetType !== widgetType.rpc &&
|
||||
@ -529,14 +533,17 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, OnDe
|
||||
},
|
||||
{emitEvent: false}
|
||||
);
|
||||
if (this.widgetType === widgetType.timeseries || this.widgetType === widgetType.alarm || this.widgetType === widgetType.latest) {
|
||||
if (widgetTypeCanHaveTimewindow(this.widgetType)) {
|
||||
const useDashboardTimewindow = isDefined(config.useDashboardTimewindow) ?
|
||||
config.useDashboardTimewindow : true;
|
||||
this.dataSettings.get('timewindowConfig').patchValue({
|
||||
useDashboardTimewindow,
|
||||
displayTimewindow: isDefined(config.displayTimewindow) ?
|
||||
config.displayTimewindow : true,
|
||||
timewindow: config.timewindow,
|
||||
timewindow: isDefinedAndNotNull(config.timewindow)
|
||||
? config.timewindow
|
||||
: initModelFromDefaultTimewindow(null, this.widgetType === widgetType.latest, this.onlyHistoryTimewindow(),
|
||||
this.timeService, this.widgetType === widgetType.timeseries),
|
||||
timewindowStyle: config.timewindowStyle
|
||||
}, {emitEvent: false});
|
||||
}
|
||||
|
||||
@ -38,6 +38,7 @@ import {
|
||||
} from '@angular/core';
|
||||
import { DashboardWidget } from '@home/models/dashboard-component.models';
|
||||
import {
|
||||
MobileImageResult,
|
||||
Widget,
|
||||
WidgetAction,
|
||||
WidgetActionDescriptor,
|
||||
@ -126,6 +127,7 @@ import { IModulesMap } from '@modules/common/modules-map.models';
|
||||
import { DashboardUtilsService } from '@core/services/dashboard-utils.service';
|
||||
import { CompiledTbFunction, compileTbFunction, isNotEmptyTbFunction } from '@shared/models/js-function.models';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { addDiagnosticChain } from '@angular/compiler-cli/src/ngtsc/diagnostics';
|
||||
|
||||
@Component({
|
||||
selector: 'tb-widget',
|
||||
@ -1222,12 +1224,16 @@ export class WidgetComponent extends PageComponent implements OnInit, OnChanges,
|
||||
switch (type) {
|
||||
case WidgetMobileActionType.takePictureFromGallery:
|
||||
case WidgetMobileActionType.takePhoto:
|
||||
case WidgetMobileActionType.takeScreenshot:
|
||||
argsObservable = of([mobileAction.saveToGallery]);
|
||||
break;
|
||||
case WidgetMobileActionType.scanQrCode:
|
||||
case WidgetMobileActionType.getLocation:
|
||||
case WidgetMobileActionType.takeScreenshot:
|
||||
case WidgetMobileActionType.deviceProvision:
|
||||
argsObservable = of([]);
|
||||
break;
|
||||
case WidgetMobileActionType.deviceProvision:
|
||||
argsObservable = of([mobileAction.provisionType]);
|
||||
break;
|
||||
case WidgetMobileActionType.mapDirection:
|
||||
case WidgetMobileActionType.mapLocation:
|
||||
argsObservable = compileTbFunction(this.http, mobileAction.getLocationFunction, '$event', 'widgetContext', 'entityId',
|
||||
@ -1297,6 +1303,10 @@ export class WidgetComponent extends PageComponent implements OnInit, OnChanges,
|
||||
case WidgetMobileActionType.takePhoto:
|
||||
case WidgetMobileActionType.takeScreenshot:
|
||||
const imageUrl = actionResult.imageUrl;
|
||||
if (!additionalParams) {
|
||||
additionalParams = {};
|
||||
}
|
||||
additionalParams.imageInfo = actionResult.imageInfo;
|
||||
if (isNotEmptyTbFunction(mobileAction.processImageFunction)) {
|
||||
compileTbFunction(this.http, mobileAction.processImageFunction, 'imageUrl', '$event', 'widgetContext', 'entityId',
|
||||
'entityName', 'additionalParams', 'entityLabel').subscribe(
|
||||
@ -1421,6 +1431,23 @@ export class WidgetComponent extends PageComponent implements OnInit, OnChanges,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else if (!this.mobileService.isMobileApp()) {
|
||||
if (isNotEmptyTbFunction(mobileAction.handleNonMobileFallbackFunction)) {
|
||||
compileTbFunction(this.http, mobileAction.handleNonMobileFallbackFunction, '$event', 'widgetContext',).subscribe(
|
||||
{
|
||||
next: (compiled) => {
|
||||
try {
|
||||
compiled.execute($event, this.widgetContext);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
},
|
||||
error: (err) => {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@ -29,6 +29,7 @@ import { MobileAppService } from '@core/http/mobile-app.service';
|
||||
|
||||
export interface MobileAppDialogData {
|
||||
platformType: PlatformType;
|
||||
name?: string
|
||||
}
|
||||
|
||||
@Component({
|
||||
@ -55,6 +56,9 @@ export class MobileAppDialogComponent extends DialogComponent<MobileAppDialogCom
|
||||
ngAfterViewInit(): void {
|
||||
setTimeout(() => {
|
||||
this.mobileAppComponent.entityForm.markAsDirty();
|
||||
if (this.data.name) {
|
||||
this.mobileAppComponent.entityForm.get('title').patchValue(this.data.name, {emitEvent: false});
|
||||
}
|
||||
this.mobileAppComponent.entityForm.patchValue({platformType: this.data.platformType});
|
||||
this.mobileAppComponent.entityForm.get('platformType').disable({emitEvent: false});
|
||||
this.mobileAppComponent.isEdit = true;
|
||||
|
||||
@ -58,7 +58,7 @@
|
||||
labelText="mobile.android-application"
|
||||
[entityType]="entityType.MOBILE_APP"
|
||||
[entitySubtype]="platformType.ANDROID"
|
||||
(createNew)="createApplication('androidAppId', platformType.ANDROID)"
|
||||
(createNew)="createApplication($event, 'androidAppId', platformType.ANDROID)"
|
||||
formControlName="androidAppId">
|
||||
</tb-entity-autocomplete>
|
||||
<tb-entity-autocomplete
|
||||
@ -68,7 +68,7 @@
|
||||
labelText="mobile.ios-application"
|
||||
[entityType]="entityType.MOBILE_APP"
|
||||
[entitySubtype]="platformType.IOS"
|
||||
(createNew)="createApplication('iosAppId', platformType.IOS)"
|
||||
(createNew)="createApplication($event, 'iosAppId', platformType.IOS)"
|
||||
formControlName="iosAppId">
|
||||
</tb-entity-autocomplete>
|
||||
<mat-form-field appearance="outline">
|
||||
|
||||
@ -148,12 +148,13 @@ export class MobileBundleDialogComponent extends DialogComponent<MobileBundleDia
|
||||
}
|
||||
}
|
||||
|
||||
createApplication(formControl: string, platformType: PlatformType) {
|
||||
createApplication(name: string, formControl: string, platformType: PlatformType) {
|
||||
this.dialog.open<MobileAppDialogComponent, MobileAppDialogData, MobileApp>(MobileAppDialogComponent, {
|
||||
disableClose: true,
|
||||
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
|
||||
data: {
|
||||
platformType
|
||||
platformType,
|
||||
name
|
||||
}
|
||||
}).afterClosed()
|
||||
.subscribe((app) => {
|
||||
|
||||
@ -71,6 +71,11 @@
|
||||
<span>
|
||||
{{ noEntitiesMatchingText | translate: {entity: searchText} }}
|
||||
</span>
|
||||
@if (allowCreateNew) {
|
||||
<span>
|
||||
<a translate (click)="createNewEntity($event, searchText)">entity.create-new-key</a>
|
||||
</span>
|
||||
}
|
||||
</ng-template>
|
||||
</div>
|
||||
</mat-option>
|
||||
|
||||
@ -142,7 +142,7 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit
|
||||
entityChanged = new EventEmitter<BaseData<EntityId>>();
|
||||
|
||||
@Output()
|
||||
createNew = new EventEmitter<void>();
|
||||
createNew = new EventEmitter<string>();
|
||||
|
||||
@ViewChild('entityInput', {static: true}) entityInput: ElementRef;
|
||||
|
||||
@ -451,9 +451,9 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit
|
||||
return entityType;
|
||||
}
|
||||
|
||||
createNewEntity($event: Event) {
|
||||
createNewEntity($event: Event, searchText?: string) {
|
||||
$event.stopPropagation();
|
||||
this.createNew.emit();
|
||||
this.createNew.emit(searchText);
|
||||
}
|
||||
|
||||
get showEntityLink(): boolean {
|
||||
|
||||
@ -36,7 +36,15 @@ import { Store } from '@ngrx/store';
|
||||
import { AppState } from '@core/core.state';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { TimeService } from '@core/services/time.service';
|
||||
import { deepClone, isDefined, isDefinedAndNotNull, isObject, mergeDeep } from '@core/utils';
|
||||
import {
|
||||
deepClean,
|
||||
deepClone,
|
||||
deleteFalseProperties,
|
||||
isDefined,
|
||||
isDefinedAndNotNull,
|
||||
isEmpty,
|
||||
mergeDeepIgnoreArray
|
||||
} from '@core/utils';
|
||||
import { ToggleHeaderOption } from '@shared/components/toggle-header.component';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
@ -152,73 +160,73 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On
|
||||
this.timewindowForm = this.fb.group({
|
||||
selectedTab: [isDefined(this.timewindow.selectedTab) ? this.timewindow.selectedTab : TimewindowType.REALTIME],
|
||||
realtime: this.fb.group({
|
||||
realtimeType: [ isDefined(realtime?.realtimeType) ? this.timewindow.realtime.realtimeType : RealtimeWindowType.LAST_INTERVAL ],
|
||||
timewindowMs: [ isDefined(realtime?.timewindowMs) ? this.timewindow.realtime.timewindowMs : null ],
|
||||
interval: [ isDefined(realtime?.interval) ? this.timewindow.realtime.interval : null ],
|
||||
quickInterval: [ isDefined(realtime?.quickInterval) ? this.timewindow.realtime.quickInterval : null ],
|
||||
disableCustomInterval: [ isDefinedAndNotNull(this.timewindow.realtime?.disableCustomInterval)
|
||||
? this.timewindow.realtime?.disableCustomInterval : false ],
|
||||
disableCustomGroupInterval: [ isDefinedAndNotNull(this.timewindow.realtime?.disableCustomGroupInterval)
|
||||
? this.timewindow.realtime?.disableCustomGroupInterval : false ],
|
||||
hideInterval: [ isDefinedAndNotNull(this.timewindow.realtime.hideInterval)
|
||||
? this.timewindow.realtime.hideInterval : false ],
|
||||
realtimeType: [ isDefined(realtime?.realtimeType) ? realtime.realtimeType : RealtimeWindowType.LAST_INTERVAL ],
|
||||
timewindowMs: [ isDefined(realtime?.timewindowMs) ? realtime.timewindowMs : null ],
|
||||
interval: [ isDefined(realtime?.interval) ? realtime.interval : null ],
|
||||
quickInterval: [ isDefined(realtime?.quickInterval) ? realtime.quickInterval : null ],
|
||||
disableCustomInterval: [ isDefinedAndNotNull(realtime?.disableCustomInterval)
|
||||
? realtime.disableCustomInterval : false ],
|
||||
disableCustomGroupInterval: [ isDefinedAndNotNull(realtime?.disableCustomGroupInterval)
|
||||
? realtime.disableCustomGroupInterval : false ],
|
||||
hideInterval: [ isDefinedAndNotNull(realtime?.hideInterval)
|
||||
? realtime.hideInterval : false ],
|
||||
hideLastInterval: [{
|
||||
value: isDefinedAndNotNull(this.timewindow.realtime.hideLastInterval)
|
||||
? this.timewindow.realtime.hideLastInterval : false,
|
||||
disabled: this.timewindow.realtime.hideInterval
|
||||
value: isDefinedAndNotNull(realtime?.hideLastInterval)
|
||||
? realtime.hideLastInterval : false,
|
||||
disabled: realtime?.hideInterval
|
||||
}],
|
||||
hideQuickInterval: [{
|
||||
value: isDefinedAndNotNull(this.timewindow.realtime.hideQuickInterval)
|
||||
? this.timewindow.realtime.hideQuickInterval : false,
|
||||
disabled: this.timewindow.realtime.hideInterval
|
||||
value: isDefinedAndNotNull(realtime?.hideQuickInterval)
|
||||
? realtime.hideQuickInterval : false,
|
||||
disabled: realtime?.hideInterval
|
||||
}],
|
||||
advancedParams: this.fb.group({
|
||||
allowedLastIntervals: [ isDefinedAndNotNull(this.timewindow.realtime?.advancedParams?.allowedLastIntervals)
|
||||
? this.timewindow.realtime.advancedParams.allowedLastIntervals : null ],
|
||||
allowedQuickIntervals: [ isDefinedAndNotNull(this.timewindow.realtime?.advancedParams?.allowedQuickIntervals)
|
||||
? this.timewindow.realtime.advancedParams.allowedQuickIntervals : null ],
|
||||
lastAggIntervalsConfig: [ isDefinedAndNotNull(this.timewindow.realtime?.advancedParams?.lastAggIntervalsConfig)
|
||||
? this.timewindow.realtime.advancedParams.lastAggIntervalsConfig : null ],
|
||||
quickAggIntervalsConfig: [ isDefinedAndNotNull(this.timewindow.realtime?.advancedParams?.quickAggIntervalsConfig)
|
||||
? this.timewindow.realtime.advancedParams.quickAggIntervalsConfig : null ]
|
||||
allowedLastIntervals: [ isDefinedAndNotNull(realtime?.advancedParams?.allowedLastIntervals)
|
||||
? realtime.advancedParams.allowedLastIntervals : null ],
|
||||
allowedQuickIntervals: [ isDefinedAndNotNull(realtime?.advancedParams?.allowedQuickIntervals)
|
||||
? realtime.advancedParams.allowedQuickIntervals : null ],
|
||||
lastAggIntervalsConfig: [ isDefinedAndNotNull(realtime?.advancedParams?.lastAggIntervalsConfig)
|
||||
? realtime.advancedParams.lastAggIntervalsConfig : null ],
|
||||
quickAggIntervalsConfig: [ isDefinedAndNotNull(realtime?.advancedParams?.quickAggIntervalsConfig)
|
||||
? realtime.advancedParams.quickAggIntervalsConfig : null ]
|
||||
})
|
||||
}),
|
||||
history: this.fb.group({
|
||||
historyType: [ isDefined(history?.historyType) ? this.timewindow.history.historyType : HistoryWindowType.LAST_INTERVAL ],
|
||||
timewindowMs: [ isDefined(history?.timewindowMs) ? this.timewindow.history.timewindowMs : null ],
|
||||
interval: [ isDefined(history?.interval) ? this.timewindow.history.interval : null ],
|
||||
fixedTimewindow: [ isDefined(history?.fixedTimewindow) ? this.timewindow.history.fixedTimewindow : null ],
|
||||
quickInterval: [ isDefined(history?.quickInterval) ? this.timewindow.history.quickInterval : null ],
|
||||
disableCustomInterval: [ isDefinedAndNotNull(this.timewindow.history?.disableCustomInterval)
|
||||
? this.timewindow.history?.disableCustomInterval : false ],
|
||||
disableCustomGroupInterval: [ isDefinedAndNotNull(this.timewindow.history?.disableCustomGroupInterval)
|
||||
? this.timewindow.history?.disableCustomGroupInterval : false ],
|
||||
hideInterval: [ isDefinedAndNotNull(this.timewindow.history.hideInterval)
|
||||
? this.timewindow.history.hideInterval : false ],
|
||||
historyType: [ isDefined(history?.historyType) ? history.historyType : HistoryWindowType.LAST_INTERVAL ],
|
||||
timewindowMs: [ isDefined(history?.timewindowMs) ? history.timewindowMs : null ],
|
||||
interval: [ isDefined(history?.interval) ? history.interval : null ],
|
||||
fixedTimewindow: [ isDefined(history?.fixedTimewindow) ? history.fixedTimewindow : null ],
|
||||
quickInterval: [ isDefined(history?.quickInterval) ? history.quickInterval : null ],
|
||||
disableCustomInterval: [ isDefinedAndNotNull(history?.disableCustomInterval)
|
||||
? history.disableCustomInterval : false ],
|
||||
disableCustomGroupInterval: [ isDefinedAndNotNull(history?.disableCustomGroupInterval)
|
||||
? history.disableCustomGroupInterval : false ],
|
||||
hideInterval: [ isDefinedAndNotNull(history?.hideInterval)
|
||||
? history.hideInterval : false ],
|
||||
hideLastInterval: [{
|
||||
value: isDefinedAndNotNull(this.timewindow.history.hideLastInterval)
|
||||
? this.timewindow.history.hideLastInterval : false,
|
||||
disabled: this.timewindow.history.hideInterval
|
||||
value: isDefinedAndNotNull(history?.hideLastInterval)
|
||||
? history.hideLastInterval : false,
|
||||
disabled: history?.hideInterval
|
||||
}],
|
||||
hideQuickInterval: [{
|
||||
value: isDefinedAndNotNull(this.timewindow.history.hideQuickInterval)
|
||||
? this.timewindow.history.hideQuickInterval : false,
|
||||
disabled: this.timewindow.history.hideInterval
|
||||
value: isDefinedAndNotNull(history?.hideQuickInterval)
|
||||
? history.hideQuickInterval : false,
|
||||
disabled: history?.hideInterval
|
||||
}],
|
||||
hideFixedInterval: [{
|
||||
value: isDefinedAndNotNull(this.timewindow.history.hideFixedInterval)
|
||||
? this.timewindow.history.hideFixedInterval : false,
|
||||
disabled: this.timewindow.history.hideInterval
|
||||
value: isDefinedAndNotNull(history?.hideFixedInterval)
|
||||
? history.hideFixedInterval : false,
|
||||
disabled: history?.hideInterval
|
||||
}],
|
||||
advancedParams: this.fb.group({
|
||||
allowedLastIntervals: [ isDefinedAndNotNull(this.timewindow.history?.advancedParams?.allowedLastIntervals)
|
||||
? this.timewindow.history.advancedParams.allowedLastIntervals : null ],
|
||||
allowedQuickIntervals: [ isDefinedAndNotNull(this.timewindow.history?.advancedParams?.allowedQuickIntervals)
|
||||
? this.timewindow.history.advancedParams.allowedQuickIntervals : null ],
|
||||
lastAggIntervalsConfig: [ isDefinedAndNotNull(this.timewindow.history?.advancedParams?.lastAggIntervalsConfig)
|
||||
? this.timewindow.history.advancedParams.lastAggIntervalsConfig : null ],
|
||||
quickAggIntervalsConfig: [ isDefinedAndNotNull(this.timewindow.history?.advancedParams?.quickAggIntervalsConfig)
|
||||
? this.timewindow.history.advancedParams.quickAggIntervalsConfig : null ]
|
||||
allowedLastIntervals: [ isDefinedAndNotNull(history?.advancedParams?.allowedLastIntervals)
|
||||
? history.advancedParams.allowedLastIntervals : null ],
|
||||
allowedQuickIntervals: [ isDefinedAndNotNull(history?.advancedParams?.allowedQuickIntervals)
|
||||
? history.advancedParams.allowedQuickIntervals : null ],
|
||||
lastAggIntervalsConfig: [ isDefinedAndNotNull(history?.advancedParams?.lastAggIntervalsConfig)
|
||||
? history.advancedParams.lastAggIntervalsConfig : null ],
|
||||
quickAggIntervalsConfig: [ isDefinedAndNotNull(history?.advancedParams?.quickAggIntervalsConfig)
|
||||
? history.advancedParams.quickAggIntervalsConfig : null ]
|
||||
})
|
||||
}),
|
||||
aggregation: this.fb.group({
|
||||
@ -423,7 +431,7 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On
|
||||
|
||||
update() {
|
||||
const timewindowFormValue = this.timewindowForm.getRawValue();
|
||||
this.timewindow = mergeDeep(this.timewindow, timewindowFormValue);
|
||||
this.timewindow = mergeDeepIgnoreArray(this.timewindow, timewindowFormValue);
|
||||
|
||||
const realtimeConfigurableLastIntervalsAvailable = !(timewindowFormValue.hideAggInterval &&
|
||||
(timewindowFormValue.realtime.hideInterval || timewindowFormValue.realtime.hideLastInterval));
|
||||
@ -434,82 +442,50 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On
|
||||
const historyConfigurableQuickIntervalsAvailable = !(timewindowFormValue.hideAggInterval &&
|
||||
(timewindowFormValue.history.hideInterval || timewindowFormValue.history.hideQuickInterval));
|
||||
|
||||
if (realtimeConfigurableLastIntervalsAvailable && timewindowFormValue.realtime.advancedParams.allowedLastIntervals?.length) {
|
||||
this.timewindow.realtime.advancedParams.allowedLastIntervals = timewindowFormValue.realtime.advancedParams.allowedLastIntervals;
|
||||
} else {
|
||||
if (!realtimeConfigurableLastIntervalsAvailable) {
|
||||
delete this.timewindow.realtime.advancedParams.allowedLastIntervals;
|
||||
}
|
||||
if (realtimeConfigurableQuickIntervalsAvailable && timewindowFormValue.realtime.advancedParams.allowedQuickIntervals?.length) {
|
||||
this.timewindow.realtime.advancedParams.allowedQuickIntervals = timewindowFormValue.realtime.advancedParams.allowedQuickIntervals;
|
||||
} else {
|
||||
if (!realtimeConfigurableQuickIntervalsAvailable) {
|
||||
delete this.timewindow.realtime.advancedParams.allowedQuickIntervals;
|
||||
}
|
||||
if (realtimeConfigurableLastIntervalsAvailable && isObject(timewindowFormValue.realtime.advancedParams.lastAggIntervalsConfig) &&
|
||||
Object.keys(timewindowFormValue.realtime.advancedParams.lastAggIntervalsConfig).length) {
|
||||
if (realtimeConfigurableLastIntervalsAvailable && !isEmpty(timewindowFormValue.realtime.advancedParams.lastAggIntervalsConfig)) {
|
||||
this.timewindow.realtime.advancedParams.lastAggIntervalsConfig = timewindowFormValue.realtime.advancedParams.lastAggIntervalsConfig;
|
||||
} else {
|
||||
delete this.timewindow.realtime.advancedParams.lastAggIntervalsConfig;
|
||||
}
|
||||
if (realtimeConfigurableQuickIntervalsAvailable && isObject(timewindowFormValue.realtime.advancedParams.quickAggIntervalsConfig) &&
|
||||
Object.keys(timewindowFormValue.realtime.advancedParams.quickAggIntervalsConfig).length) {
|
||||
if (realtimeConfigurableQuickIntervalsAvailable && !isEmpty(timewindowFormValue.realtime.advancedParams.quickAggIntervalsConfig)) {
|
||||
this.timewindow.realtime.advancedParams.quickAggIntervalsConfig = timewindowFormValue.realtime.advancedParams.quickAggIntervalsConfig;
|
||||
} else {
|
||||
delete this.timewindow.realtime.advancedParams.quickAggIntervalsConfig;
|
||||
}
|
||||
|
||||
if (historyConfigurableLastIntervalsAvailable && timewindowFormValue.history.advancedParams.allowedLastIntervals?.length) {
|
||||
this.timewindow.history.advancedParams.allowedLastIntervals = timewindowFormValue.history.advancedParams.allowedLastIntervals;
|
||||
} else {
|
||||
if (!historyConfigurableLastIntervalsAvailable) {
|
||||
delete this.timewindow.history.advancedParams.allowedLastIntervals;
|
||||
}
|
||||
if (historyConfigurableQuickIntervalsAvailable && timewindowFormValue.history.advancedParams.allowedQuickIntervals?.length) {
|
||||
this.timewindow.history.advancedParams.allowedQuickIntervals = timewindowFormValue.history.advancedParams.allowedQuickIntervals;
|
||||
} else {
|
||||
if (!historyConfigurableQuickIntervalsAvailable) {
|
||||
delete this.timewindow.history.advancedParams.allowedQuickIntervals;
|
||||
}
|
||||
if (historyConfigurableLastIntervalsAvailable && isObject(timewindowFormValue.history.advancedParams.lastAggIntervalsConfig) &&
|
||||
Object.keys(timewindowFormValue.history.advancedParams.lastAggIntervalsConfig).length) {
|
||||
if (historyConfigurableLastIntervalsAvailable && !isEmpty(timewindowFormValue.history.advancedParams.lastAggIntervalsConfig)) {
|
||||
this.timewindow.history.advancedParams.lastAggIntervalsConfig = timewindowFormValue.history.advancedParams.lastAggIntervalsConfig;
|
||||
} else {
|
||||
delete this.timewindow.history.advancedParams.lastAggIntervalsConfig;
|
||||
}
|
||||
if (historyConfigurableQuickIntervalsAvailable && isObject(timewindowFormValue.history.advancedParams.quickAggIntervalsConfig) &&
|
||||
Object.keys(timewindowFormValue.history.advancedParams.quickAggIntervalsConfig).length) {
|
||||
if (historyConfigurableQuickIntervalsAvailable && !isEmpty(timewindowFormValue.history.advancedParams.quickAggIntervalsConfig)) {
|
||||
this.timewindow.history.advancedParams.quickAggIntervalsConfig = timewindowFormValue.history.advancedParams.quickAggIntervalsConfig;
|
||||
} else {
|
||||
delete this.timewindow.history.advancedParams.quickAggIntervalsConfig;
|
||||
}
|
||||
|
||||
if (!Object.keys(this.timewindow.realtime.advancedParams).length) {
|
||||
delete this.timewindow.realtime.advancedParams;
|
||||
}
|
||||
if (!Object.keys(this.timewindow.history.advancedParams).length) {
|
||||
delete this.timewindow.history.advancedParams;
|
||||
}
|
||||
|
||||
if (timewindowFormValue.allowedAggTypes?.length && !timewindowFormValue.hideAggregation) {
|
||||
this.timewindow.allowedAggTypes = timewindowFormValue.allowedAggTypes;
|
||||
} else {
|
||||
if (timewindowFormValue.hideAggregation) {
|
||||
delete this.timewindow.allowedAggTypes;
|
||||
}
|
||||
|
||||
if (!this.timewindow.realtime.disableCustomInterval) {
|
||||
delete this.timewindow.realtime.disableCustomInterval;
|
||||
}
|
||||
if (!this.timewindow.realtime.disableCustomGroupInterval) {
|
||||
delete this.timewindow.realtime.disableCustomGroupInterval;
|
||||
}
|
||||
if (!this.timewindow.history.disableCustomInterval) {
|
||||
delete this.timewindow.history.disableCustomInterval;
|
||||
}
|
||||
if (!this.timewindow.history.disableCustomGroupInterval) {
|
||||
delete this.timewindow.history.disableCustomGroupInterval;
|
||||
}
|
||||
|
||||
if (!this.aggregation) {
|
||||
delete this.timewindow.aggregation;
|
||||
}
|
||||
this.dialogRef.close(this.timewindow);
|
||||
|
||||
deleteFalseProperties(this.timewindow);
|
||||
this.dialogRef.close(deepClean(this.timewindow));
|
||||
}
|
||||
|
||||
cancel() {
|
||||
|
||||
@ -27,12 +27,14 @@ import {
|
||||
} from '@angular/core';
|
||||
import {
|
||||
AggregationType,
|
||||
clearTimewindowConfig,
|
||||
currentHistoryTimewindow,
|
||||
currentRealtimeTimewindow,
|
||||
historyAllowedAggIntervals,
|
||||
HistoryWindowType,
|
||||
historyWindowTypeTranslations,
|
||||
Interval,
|
||||
MINUTE,
|
||||
QuickTimeInterval,
|
||||
realtimeAllowedAggIntervals,
|
||||
RealtimeWindowType,
|
||||
@ -167,14 +169,14 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit, O
|
||||
});
|
||||
}
|
||||
|
||||
if ((this.isEdit || !this.timewindow.realtime.hideLastInterval) && !this.quickIntervalOnly) {
|
||||
if ((this.isEdit || !this.timewindow.realtime?.hideLastInterval) && !this.quickIntervalOnly) {
|
||||
this.realtimeTimewindowOptions.push({
|
||||
name: this.translate.instant(realtimeWindowTypeTranslations.get(RealtimeWindowType.LAST_INTERVAL)),
|
||||
value: this.realtimeTypes.LAST_INTERVAL
|
||||
});
|
||||
}
|
||||
|
||||
if (this.isEdit || !this.timewindow.realtime.hideQuickInterval || this.quickIntervalOnly) {
|
||||
if (this.isEdit || !this.timewindow.realtime?.hideQuickInterval || this.quickIntervalOnly) {
|
||||
this.realtimeTimewindowOptions.push({
|
||||
name: this.translate.instant(realtimeWindowTypeTranslations.get(RealtimeWindowType.INTERVAL)),
|
||||
value: this.realtimeTypes.INTERVAL
|
||||
@ -188,21 +190,21 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit, O
|
||||
});
|
||||
}
|
||||
|
||||
if (this.isEdit || !this.timewindow.history.hideLastInterval) {
|
||||
if (this.isEdit || !this.timewindow.history?.hideLastInterval) {
|
||||
this.historyTimewindowOptions.push({
|
||||
name: this.translate.instant(historyWindowTypeTranslations.get(HistoryWindowType.LAST_INTERVAL)),
|
||||
value: this.historyTypes.LAST_INTERVAL
|
||||
});
|
||||
}
|
||||
|
||||
if (this.isEdit || !this.timewindow.history.hideFixedInterval) {
|
||||
if (this.isEdit || !this.timewindow.history?.hideFixedInterval) {
|
||||
this.historyTimewindowOptions.push({
|
||||
name: this.translate.instant(historyWindowTypeTranslations.get(HistoryWindowType.FIXED)),
|
||||
value: this.historyTypes.FIXED
|
||||
});
|
||||
}
|
||||
|
||||
if (this.isEdit || !this.timewindow.history.hideQuickInterval) {
|
||||
if (this.isEdit || !this.timewindow.history?.hideQuickInterval) {
|
||||
this.historyTimewindowOptions.push({
|
||||
name: this.translate.instant(historyWindowTypeTranslations.get(HistoryWindowType.INTERVAL)),
|
||||
value: this.historyTypes.INTERVAL
|
||||
@ -211,10 +213,10 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit, O
|
||||
|
||||
this.realtimeTypeSelectionAvailable = this.realtimeTimewindowOptions.length > 1;
|
||||
this.historyTypeSelectionAvailable = this.historyTimewindowOptions.length > 1;
|
||||
this.realtimeIntervalSelectionAvailable = this.isEdit || !(this.timewindow.realtime.hideInterval ||
|
||||
(this.timewindow.realtime.hideLastInterval && this.timewindow.realtime.hideQuickInterval));
|
||||
this.historyIntervalSelectionAvailable = this.isEdit || !(this.timewindow.history.hideInterval ||
|
||||
(this.timewindow.history.hideLastInterval && this.timewindow.history.hideQuickInterval && this.timewindow.history.hideFixedInterval));
|
||||
this.realtimeIntervalSelectionAvailable = this.isEdit || !(this.timewindow.realtime?.hideInterval ||
|
||||
(this.timewindow.realtime?.hideLastInterval && this.timewindow.realtime?.hideQuickInterval));
|
||||
this.historyIntervalSelectionAvailable = this.isEdit || !(this.timewindow.history?.hideInterval ||
|
||||
(this.timewindow.history?.hideLastInterval && this.timewindow.history?.hideQuickInterval && this.timewindow.history?.hideFixedInterval));
|
||||
|
||||
this.aggregationOptionsAvailable = this.aggregation && (this.isEdit ||
|
||||
!(this.timewindow.hideAggregation && this.timewindow.hideAggInterval));
|
||||
@ -230,28 +232,28 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit, O
|
||||
const aggregation = this.timewindow.aggregation;
|
||||
|
||||
if (!this.isEdit) {
|
||||
if (realtime.hideLastInterval && !realtime.hideQuickInterval) {
|
||||
if (realtime?.hideLastInterval && !realtime?.hideQuickInterval) {
|
||||
realtime.realtimeType = RealtimeWindowType.INTERVAL;
|
||||
}
|
||||
if (realtime.hideQuickInterval && !realtime.hideLastInterval) {
|
||||
if (realtime?.hideQuickInterval && !realtime?.hideLastInterval) {
|
||||
realtime.realtimeType = RealtimeWindowType.LAST_INTERVAL;
|
||||
}
|
||||
|
||||
if (history.hideLastInterval) {
|
||||
if (history?.hideLastInterval) {
|
||||
if (!history.hideFixedInterval) {
|
||||
history.historyType = HistoryWindowType.FIXED;
|
||||
} else if (!history.hideQuickInterval) {
|
||||
history.historyType = HistoryWindowType.INTERVAL;
|
||||
}
|
||||
}
|
||||
if (history.hideFixedInterval) {
|
||||
if (history?.hideFixedInterval) {
|
||||
if (!history.hideLastInterval) {
|
||||
history.historyType = HistoryWindowType.LAST_INTERVAL;
|
||||
} else if (!history.hideQuickInterval) {
|
||||
history.historyType = HistoryWindowType.INTERVAL;
|
||||
}
|
||||
}
|
||||
if (history.hideQuickInterval) {
|
||||
if (history?.hideQuickInterval) {
|
||||
if (!history.hideLastInterval) {
|
||||
history.historyType = HistoryWindowType.LAST_INTERVAL;
|
||||
} else if (!history.hideFixedInterval) {
|
||||
@ -265,29 +267,29 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit, O
|
||||
realtime: this.fb.group({
|
||||
realtimeType: [{
|
||||
value: isDefined(realtime?.realtimeType) ? realtime.realtimeType : RealtimeWindowType.LAST_INTERVAL,
|
||||
disabled: realtime.hideInterval
|
||||
disabled: realtime?.hideInterval
|
||||
}],
|
||||
timewindowMs: [{
|
||||
value: isDefined(realtime?.timewindowMs) ? realtime.timewindowMs : null,
|
||||
disabled: realtime.hideInterval || realtime.hideLastInterval
|
||||
value: isDefined(realtime?.timewindowMs) ? realtime.timewindowMs : MINUTE,
|
||||
disabled: realtime?.hideInterval || realtime?.hideLastInterval
|
||||
}],
|
||||
interval: [{
|
||||
value:isDefined(realtime?.interval) ? realtime.interval : null,
|
||||
disabled: hideAggInterval
|
||||
}],
|
||||
quickInterval: [{
|
||||
value: isDefined(realtime?.quickInterval) ? realtime.quickInterval : null,
|
||||
disabled: realtime.hideInterval || realtime.hideQuickInterval
|
||||
value: isDefined(realtime?.quickInterval) ? realtime.quickInterval : QuickTimeInterval.CURRENT_DAY,
|
||||
disabled: realtime?.hideInterval || realtime?.hideQuickInterval
|
||||
}]
|
||||
}),
|
||||
history: this.fb.group({
|
||||
historyType: [{
|
||||
value: isDefined(history?.historyType) ? history.historyType : HistoryWindowType.LAST_INTERVAL,
|
||||
disabled: history.hideInterval
|
||||
disabled: history?.hideInterval
|
||||
}],
|
||||
timewindowMs: [{
|
||||
value: isDefined(history?.timewindowMs) ? history.timewindowMs : null,
|
||||
disabled: history.hideInterval || history.hideLastInterval
|
||||
value: isDefined(history?.timewindowMs) ? history.timewindowMs : MINUTE,
|
||||
disabled: history?.hideInterval || history?.hideLastInterval
|
||||
}],
|
||||
interval: [{
|
||||
value:isDefined(history?.interval) ? history.interval : null,
|
||||
@ -296,11 +298,11 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit, O
|
||||
fixedTimewindow: [{
|
||||
value: isDefined(history?.fixedTimewindow) && this.timewindow.selectedTab === TimewindowType.HISTORY
|
||||
&& history.historyType === HistoryWindowType.FIXED ? history.fixedTimewindow : null,
|
||||
disabled: history.hideInterval || history.hideFixedInterval
|
||||
disabled: history?.hideInterval || history?.hideFixedInterval
|
||||
}],
|
||||
quickInterval: [{
|
||||
value: isDefined(history?.quickInterval) ? history.quickInterval : null,
|
||||
disabled: history.hideInterval || history.hideQuickInterval
|
||||
value: isDefined(history?.quickInterval) ? history.quickInterval : QuickTimeInterval.CURRENT_DAY,
|
||||
disabled: history?.hideInterval || history?.hideQuickInterval
|
||||
}]
|
||||
}),
|
||||
aggregation: this.fb.group({
|
||||
@ -379,8 +381,7 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit, O
|
||||
this.timewindowForm.valueChanges.pipe(
|
||||
takeUntil(this.destroy$)
|
||||
).subscribe(() => {
|
||||
this.prepareTimewindowConfig();
|
||||
this.changeTimewindow.emit(this.timewindow);
|
||||
this.changeTimewindow.emit(this.prepareTimewindowConfig());
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -406,27 +407,29 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit, O
|
||||
}
|
||||
|
||||
update() {
|
||||
this.prepareTimewindowConfig();
|
||||
this.result = this.timewindow;
|
||||
this.result = this.prepareTimewindowConfig();
|
||||
this.overlayRef?.dispose();
|
||||
}
|
||||
|
||||
private prepareTimewindowConfig() {
|
||||
private prepareTimewindowConfig(clearConfig = true): Timewindow {
|
||||
const timewindowFormValue = this.timewindowForm.getRawValue();
|
||||
this.timewindow.selectedTab = timewindowFormValue.selectedTab;
|
||||
this.timewindow.realtime = {...this.timewindow.realtime, ...{
|
||||
realtimeType: timewindowFormValue.realtime.realtimeType,
|
||||
timewindowMs: timewindowFormValue.realtime.timewindowMs,
|
||||
quickInterval: timewindowFormValue.realtime.quickInterval,
|
||||
interval: timewindowFormValue.realtime.interval
|
||||
}};
|
||||
this.timewindow.history = {...this.timewindow.history, ...{
|
||||
historyType: timewindowFormValue.history.historyType,
|
||||
timewindowMs: timewindowFormValue.history.timewindowMs,
|
||||
interval: timewindowFormValue.history.interval,
|
||||
fixedTimewindow: timewindowFormValue.history.fixedTimewindow,
|
||||
quickInterval: timewindowFormValue.history.quickInterval,
|
||||
}};
|
||||
if (this.timewindow.selectedTab === TimewindowType.REALTIME) {
|
||||
this.timewindow.realtime = {...this.timewindow.realtime, ...{
|
||||
realtimeType: timewindowFormValue.realtime.realtimeType,
|
||||
timewindowMs: timewindowFormValue.realtime.timewindowMs,
|
||||
quickInterval: timewindowFormValue.realtime.quickInterval,
|
||||
interval: timewindowFormValue.realtime.interval,
|
||||
}};
|
||||
} else {
|
||||
this.timewindow.history = {...this.timewindow.history, ...{
|
||||
historyType: timewindowFormValue.history.historyType,
|
||||
timewindowMs: timewindowFormValue.history.timewindowMs,
|
||||
fixedTimewindow: timewindowFormValue.history.fixedTimewindow,
|
||||
quickInterval: timewindowFormValue.history.quickInterval,
|
||||
interval: timewindowFormValue.history.interval,
|
||||
}};
|
||||
}
|
||||
|
||||
if (this.aggregation) {
|
||||
this.timewindow.aggregation = {
|
||||
@ -437,6 +440,12 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit, O
|
||||
if (this.timezone) {
|
||||
this.timewindow.timezone = timewindowFormValue.timezone;
|
||||
}
|
||||
|
||||
if (clearConfig) {
|
||||
return clearTimewindowConfig(this.timewindow, this.quickIntervalOnly, this.historyOnly, this.aggregation, this.timezone);
|
||||
} else {
|
||||
return deepClone(this.timewindow);
|
||||
}
|
||||
}
|
||||
|
||||
private updateTimewindowForm() {
|
||||
@ -546,7 +555,6 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit, O
|
||||
}
|
||||
|
||||
openTimewindowConfig() {
|
||||
this.prepareTimewindowConfig();
|
||||
this.dialog.open<TimewindowConfigDialogComponent, TimewindowConfigDialogData, Timewindow>(
|
||||
TimewindowConfigDialogComponent, {
|
||||
autoFocus: false,
|
||||
@ -555,7 +563,7 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit, O
|
||||
data: {
|
||||
quickIntervalOnly: this.quickIntervalOnly,
|
||||
aggregation: this.aggregation,
|
||||
timewindow: deepClone(this.timewindow)
|
||||
timewindow: this.prepareTimewindowConfig(false)
|
||||
}
|
||||
}).afterClosed()
|
||||
.subscribe((res) => {
|
||||
@ -568,12 +576,12 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit, O
|
||||
}
|
||||
|
||||
private updateTimewindowAdvancedParams() {
|
||||
this.realtimeDisableCustomInterval = this.timewindow.realtime.disableCustomInterval;
|
||||
this.realtimeDisableCustomGroupInterval = this.timewindow.realtime.disableCustomGroupInterval;
|
||||
this.historyDisableCustomInterval = this.timewindow.history.disableCustomInterval;
|
||||
this.historyDisableCustomGroupInterval = this.timewindow.history.disableCustomGroupInterval;
|
||||
this.realtimeDisableCustomInterval = this.timewindow.realtime?.disableCustomInterval;
|
||||
this.realtimeDisableCustomGroupInterval = this.timewindow.realtime?.disableCustomGroupInterval;
|
||||
this.historyDisableCustomInterval = this.timewindow.history?.disableCustomInterval;
|
||||
this.historyDisableCustomGroupInterval = this.timewindow.history?.disableCustomGroupInterval;
|
||||
|
||||
if (this.timewindow.realtime.advancedParams) {
|
||||
if (this.timewindow.realtime?.advancedParams) {
|
||||
this.realtimeAdvancedParams = this.timewindow.realtime.advancedParams;
|
||||
this.realtimeAllowedLastIntervals = this.timewindow.realtime.advancedParams.allowedLastIntervals;
|
||||
this.realtimeAllowedQuickIntervals = this.timewindow.realtime.advancedParams.allowedQuickIntervals;
|
||||
@ -582,7 +590,7 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit, O
|
||||
this.realtimeAllowedLastIntervals = null;
|
||||
this.realtimeAllowedQuickIntervals = null;
|
||||
}
|
||||
if (this.timewindow.history.advancedParams) {
|
||||
if (this.timewindow.history?.advancedParams) {
|
||||
this.historyAdvancedParams = this.timewindow.history.advancedParams;
|
||||
this.historyAllowedLastIntervals = this.timewindow.history.advancedParams.allowedLastIntervals;
|
||||
this.historyAllowedQuickIntervals = this.timewindow.history.advancedParams.allowedQuickIntervals;
|
||||
|
||||
@ -319,7 +319,8 @@ export class TimewindowComponent implements ControlValueAccessor, OnInit, OnChan
|
||||
}
|
||||
|
||||
writeValue(obj: Timewindow): void {
|
||||
this.innerValue = initModelFromDefaultTimewindow(obj, this.quickIntervalOnly, this.historyOnly, this.timeService);
|
||||
this.innerValue = initModelFromDefaultTimewindow(obj, this.quickIntervalOnly, this.historyOnly, this.timeService,
|
||||
this.aggregation);
|
||||
this.timewindowDisabled = this.isTimewindowDisabled();
|
||||
if (this.onHistoryOnlyChanged()) {
|
||||
setTimeout(() => {
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
///
|
||||
|
||||
import { TimeService } from '@core/services/time.service';
|
||||
import { deepClone, isDefined, isDefinedAndNotNull, isNumeric, isUndefined } from '@app/core/utils';
|
||||
import { deepClean, deepClone, isDefined, isDefinedAndNotNull, isNumeric, isUndefined } from '@app/core/utils';
|
||||
import moment_ from 'moment';
|
||||
import * as momentTz from 'moment-timezone';
|
||||
import { IntervalType } from '@shared/models/telemetry/telemetry.models';
|
||||
@ -281,19 +281,12 @@ export const historyInterval = (timewindowMs: number): Timewindow => ({
|
||||
export const defaultTimewindow = (timeService: TimeService): Timewindow => {
|
||||
const currentTime = moment().valueOf();
|
||||
return {
|
||||
displayValue: '',
|
||||
hideAggregation: false,
|
||||
hideAggInterval: false,
|
||||
hideTimezone: false,
|
||||
selectedTab: TimewindowType.REALTIME,
|
||||
realtime: {
|
||||
realtimeType: RealtimeWindowType.LAST_INTERVAL,
|
||||
interval: SECOND,
|
||||
timewindowMs: MINUTE,
|
||||
quickInterval: QuickTimeInterval.CURRENT_DAY,
|
||||
hideInterval: false,
|
||||
hideLastInterval: false,
|
||||
hideQuickInterval: false
|
||||
},
|
||||
history: {
|
||||
historyType: HistoryWindowType.LAST_INTERVAL,
|
||||
@ -304,10 +297,6 @@ export const defaultTimewindow = (timeService: TimeService): Timewindow => {
|
||||
endTimeMs: currentTime
|
||||
},
|
||||
quickInterval: QuickTimeInterval.CURRENT_DAY,
|
||||
hideInterval: false,
|
||||
hideLastInterval: false,
|
||||
hideFixedInterval: false,
|
||||
hideQuickInterval: false
|
||||
},
|
||||
aggregation: {
|
||||
type: AggregationType.AVG,
|
||||
@ -325,40 +314,47 @@ const getTimewindowType = (timewindow: Timewindow): TimewindowType => {
|
||||
};
|
||||
|
||||
export const initModelFromDefaultTimewindow = (value: Timewindow, quickIntervalOnly: boolean,
|
||||
historyOnly: boolean, timeService: TimeService): Timewindow => {
|
||||
historyOnly: boolean, timeService: TimeService, hasAggregation: boolean): Timewindow => {
|
||||
const model = defaultTimewindow(timeService);
|
||||
if (value) {
|
||||
if (value.allowedAggTypes?.length) {
|
||||
model.allowedAggTypes = value.allowedAggTypes;
|
||||
}
|
||||
model.hideAggregation = value.hideAggregation;
|
||||
model.hideAggInterval = value.hideAggInterval;
|
||||
model.hideTimezone = value.hideTimezone;
|
||||
if (value.hideAggregation) {
|
||||
model.hideAggregation = value.hideAggregation;
|
||||
}
|
||||
if (value.hideAggInterval) {
|
||||
model.hideAggInterval = value.hideAggInterval;
|
||||
}
|
||||
if (value.hideTimezone) {
|
||||
model.hideTimezone = value.hideTimezone;
|
||||
}
|
||||
|
||||
model.selectedTab = getTimewindowType(value);
|
||||
|
||||
// for backward compatibility
|
||||
if (isDefinedAndNotNull((value as any).hideInterval)) {
|
||||
if ((value as any).hideInterval) {
|
||||
model.realtime.hideInterval = (value as any).hideInterval;
|
||||
model.history.hideInterval = (value as any).hideInterval;
|
||||
delete (value as any).hideInterval;
|
||||
}
|
||||
if (isDefinedAndNotNull((value as any).hideLastInterval)) {
|
||||
if ((value as any).hideLastInterval) {
|
||||
model.realtime.hideLastInterval = (value as any).hideLastInterval;
|
||||
delete (value as any).hideLastInterval;
|
||||
}
|
||||
if (isDefinedAndNotNull((value as any).hideQuickInterval)) {
|
||||
if ((value as any).hideQuickInterval) {
|
||||
model.realtime.hideQuickInterval = (value as any).hideQuickInterval;
|
||||
delete (value as any).hideQuickInterval;
|
||||
}
|
||||
|
||||
if (isDefined(value.realtime)) {
|
||||
if (isDefinedAndNotNull(value.realtime.hideInterval)) {
|
||||
if (value.realtime.hideInterval) {
|
||||
model.realtime.hideInterval = value.realtime.hideInterval;
|
||||
}
|
||||
if (isDefinedAndNotNull(value.realtime.hideLastInterval)) {
|
||||
if (value.realtime.hideLastInterval) {
|
||||
model.realtime.hideLastInterval = value.realtime.hideLastInterval;
|
||||
}
|
||||
if (isDefinedAndNotNull(value.realtime.hideQuickInterval)) {
|
||||
if (value.realtime.hideQuickInterval) {
|
||||
model.realtime.hideQuickInterval = value.realtime.hideQuickInterval;
|
||||
}
|
||||
if (value.realtime.disableCustomInterval) {
|
||||
@ -392,16 +388,16 @@ export const initModelFromDefaultTimewindow = (value: Timewindow, quickIntervalO
|
||||
}
|
||||
}
|
||||
if (isDefined(value.history)) {
|
||||
if (isDefinedAndNotNull(value.history.hideInterval)) {
|
||||
if (value.history.hideInterval) {
|
||||
model.history.hideInterval = value.history.hideInterval;
|
||||
}
|
||||
if (isDefinedAndNotNull(value.history.hideLastInterval)) {
|
||||
if (value.history.hideLastInterval) {
|
||||
model.history.hideLastInterval = value.history.hideLastInterval;
|
||||
}
|
||||
if (isDefinedAndNotNull(value.history.hideFixedInterval)) {
|
||||
if (value.history.hideFixedInterval) {
|
||||
model.history.hideFixedInterval = value.history.hideFixedInterval;
|
||||
}
|
||||
if (isDefinedAndNotNull(value.history.hideQuickInterval)) {
|
||||
if (value.history.hideQuickInterval) {
|
||||
model.history.hideQuickInterval = value.history.hideQuickInterval;
|
||||
}
|
||||
if (value.history.disableCustomInterval) {
|
||||
@ -450,7 +446,9 @@ export const initModelFromDefaultTimewindow = (value: Timewindow, quickIntervalO
|
||||
}
|
||||
model.aggregation.limit = value.aggregation.limit || Math.floor(timeService.getMaxDatapointsLimit() / 2);
|
||||
}
|
||||
model.timezone = value.timezone;
|
||||
if (value.timezone) {
|
||||
model.timezone = value.timezone;
|
||||
}
|
||||
}
|
||||
if (quickIntervalOnly) {
|
||||
model.realtime.realtimeType = RealtimeWindowType.INTERVAL;
|
||||
@ -458,7 +456,7 @@ export const initModelFromDefaultTimewindow = (value: Timewindow, quickIntervalO
|
||||
if (historyOnly) {
|
||||
model.selectedTab = TimewindowType.HISTORY;
|
||||
}
|
||||
return model;
|
||||
return clearTimewindowConfig(model, quickIntervalOnly, historyOnly, hasAggregation);
|
||||
};
|
||||
|
||||
export const toHistoryTimewindow = (timewindow: Timewindow, startTimeMs: number, endTimeMs: number,
|
||||
@ -1098,19 +1096,91 @@ export const cloneSelectedTimewindow = (timewindow: Timewindow): Timewindow => {
|
||||
if (timewindow.allowedAggTypes?.length) {
|
||||
cloned.allowedAggTypes = timewindow.allowedAggTypes;
|
||||
}
|
||||
cloned.hideAggregation = timewindow.hideAggregation || false;
|
||||
cloned.hideAggInterval = timewindow.hideAggInterval || false;
|
||||
cloned.hideTimezone = timewindow.hideTimezone || false;
|
||||
if (timewindow.hideAggregation) {
|
||||
cloned.hideAggregation = timewindow.hideAggregation;
|
||||
}
|
||||
if (timewindow.hideAggInterval) {
|
||||
cloned.hideAggInterval = timewindow.hideAggInterval;
|
||||
}
|
||||
if (timewindow.hideTimezone) {
|
||||
cloned.hideTimezone = timewindow.hideTimezone;
|
||||
}
|
||||
if (isDefined(timewindow.selectedTab)) {
|
||||
cloned.selectedTab = timewindow.selectedTab;
|
||||
}
|
||||
cloned.realtime = deepClone(timewindow.realtime);
|
||||
cloned.history = deepClone(timewindow.history);
|
||||
cloned.aggregation = deepClone(timewindow.aggregation);
|
||||
cloned.timezone = timewindow.timezone;
|
||||
if (isDefined(timewindow.realtime)) {
|
||||
cloned.realtime = deepClone(timewindow.realtime);
|
||||
}
|
||||
if (isDefined(timewindow.history)) {
|
||||
cloned.history = deepClone(timewindow.history);
|
||||
}
|
||||
if (isDefined(timewindow.aggregation)) {
|
||||
cloned.aggregation = deepClone(timewindow.aggregation);
|
||||
}
|
||||
if (timewindow.timezone) {
|
||||
cloned.timezone = timewindow.timezone;
|
||||
}
|
||||
return cloned;
|
||||
};
|
||||
|
||||
export const clearTimewindowConfig = (timewindow: Timewindow, quickIntervalOnly: boolean,
|
||||
historyOnly: boolean, hasAggregation: boolean, hasTimezone = true): Timewindow => {
|
||||
if (timewindow.selectedTab === TimewindowType.REALTIME) {
|
||||
if (quickIntervalOnly || timewindow.realtime.realtimeType === RealtimeWindowType.INTERVAL) {
|
||||
delete timewindow.realtime.timewindowMs;
|
||||
} else {
|
||||
delete timewindow.realtime.quickInterval;
|
||||
}
|
||||
|
||||
delete timewindow.history?.historyType;
|
||||
delete timewindow.history?.timewindowMs;
|
||||
delete timewindow.history?.fixedTimewindow;
|
||||
delete timewindow.history?.quickInterval;
|
||||
|
||||
delete timewindow.history?.interval;
|
||||
if (!hasAggregation) {
|
||||
delete timewindow.realtime.interval;
|
||||
}
|
||||
} else {
|
||||
if (timewindow.history.historyType === HistoryWindowType.LAST_INTERVAL) {
|
||||
delete timewindow.history.fixedTimewindow;
|
||||
delete timewindow.history.quickInterval;
|
||||
} else if (timewindow.history.historyType === HistoryWindowType.FIXED) {
|
||||
delete timewindow.history.timewindowMs;
|
||||
delete timewindow.history.quickInterval;
|
||||
} else if (timewindow.history.historyType === HistoryWindowType.INTERVAL) {
|
||||
delete timewindow.history.timewindowMs;
|
||||
delete timewindow.history.fixedTimewindow;
|
||||
} else {
|
||||
delete timewindow.history.timewindowMs;
|
||||
delete timewindow.history.fixedTimewindow;
|
||||
delete timewindow.history.quickInterval;
|
||||
}
|
||||
|
||||
delete timewindow.realtime?.realtimeType;
|
||||
delete timewindow.realtime?.timewindowMs;
|
||||
delete timewindow.realtime?.quickInterval;
|
||||
|
||||
delete timewindow.realtime?.interval;
|
||||
if (!hasAggregation) {
|
||||
delete timewindow.history.interval;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasAggregation) {
|
||||
delete timewindow.aggregation;
|
||||
}
|
||||
|
||||
if (historyOnly) {
|
||||
delete timewindow.realtime;
|
||||
}
|
||||
|
||||
if (!hasTimezone) {
|
||||
delete timewindow.timezone;
|
||||
}
|
||||
return deepClean(timewindow);
|
||||
};
|
||||
|
||||
export interface TimeInterval {
|
||||
name: string;
|
||||
translateParams: {[key: string]: any};
|
||||
|
||||
@ -33,7 +33,7 @@ import { PageComponent } from '@shared/components/page.component';
|
||||
import { AfterViewInit, DestroyRef, Directive, EventEmitter, inject, Inject, OnInit, Type } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { AppState } from '@core/core.state';
|
||||
import { AbstractControl, UntypedFormGroup } from '@angular/forms';
|
||||
import { AbstractControl, UntypedFormGroup, ValidatorFn } from '@angular/forms';
|
||||
import { Observable } from 'rxjs';
|
||||
import { Dashboard } from '@shared/models/dashboard.models';
|
||||
import { IAliasController } from '@core/api/widget-api.models';
|
||||
@ -51,6 +51,7 @@ import { TbFunction } from '@shared/models/js-function.models';
|
||||
import { FormProperty, jsonFormSchemaToFormProperties } from '@shared/models/dynamic-form.models';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { TbUnit } from '@shared/models/unit.models';
|
||||
import { ImageResourceInfo } from '@shared/models/resource.models';
|
||||
|
||||
export enum widgetType {
|
||||
timeseries = 'timeseries',
|
||||
@ -489,6 +490,14 @@ export const targetDeviceValid = (targetDevice?: TargetDevice): boolean =>
|
||||
((targetDevice.type === TargetDeviceType.device && !!targetDevice.deviceId) ||
|
||||
(targetDevice.type === TargetDeviceType.entity && !!targetDevice.entityAliasId));
|
||||
|
||||
export const widgetTypeHasTimewindow = (type: widgetType): boolean => {
|
||||
return type === widgetType.timeseries || type === widgetType.alarm;
|
||||
}
|
||||
|
||||
export const widgetTypeCanHaveTimewindow = (type: widgetType): boolean => {
|
||||
return widgetTypeHasTimewindow(type) || type === widgetType.latest;
|
||||
}
|
||||
|
||||
export const datasourcesHasAggregation = (datasources?: Array<Datasource>): boolean => {
|
||||
if (datasources) {
|
||||
const foundDatasource = datasources.find(datasource => {
|
||||
@ -622,6 +631,30 @@ export enum WidgetMobileActionType {
|
||||
deviceProvision = 'deviceProvision',
|
||||
}
|
||||
|
||||
export interface ActionConfig {
|
||||
title: string,
|
||||
formControlName: string,
|
||||
functionName: string,
|
||||
functionArgs: string[],
|
||||
helpId?: string
|
||||
}
|
||||
|
||||
export enum ProvisionType {
|
||||
auto = 'auto',
|
||||
wiFi = 'wiFi',
|
||||
ble = 'ble',
|
||||
softAp = 'softAp'
|
||||
}
|
||||
|
||||
export const provisionTypeTranslationMap = new Map<ProvisionType, string>(
|
||||
[
|
||||
[ ProvisionType.auto, 'widget-action.mobile.auto' ],
|
||||
[ ProvisionType.wiFi, 'widget-action.mobile.wi-fi' ],
|
||||
[ ProvisionType.ble, 'widget-action.mobile.ble' ],
|
||||
[ ProvisionType.softAp, 'widget-action.mobile.soft-ap' ],
|
||||
]
|
||||
);
|
||||
|
||||
export enum MapItemType {
|
||||
marker = 'marker',
|
||||
polygon = 'polygon',
|
||||
@ -675,6 +708,7 @@ export interface MobileLaunchResult {
|
||||
|
||||
export interface MobileImageResult {
|
||||
imageUrl: string;
|
||||
imageInfo?: ImageResourceInfo;
|
||||
}
|
||||
|
||||
export interface MobileQrCodeResult {
|
||||
@ -706,10 +740,12 @@ export interface WidgetMobileActionResult<T extends MobileActionResult> {
|
||||
|
||||
export interface ProvisionSuccessDescriptor {
|
||||
handleProvisionSuccessFunction: TbFunction;
|
||||
provisionType?: string;
|
||||
}
|
||||
|
||||
export interface ProcessImageDescriptor {
|
||||
processImageFunction: TbFunction;
|
||||
saveToGallery?: boolean;
|
||||
}
|
||||
|
||||
export interface ProcessLaunchResultDescriptor {
|
||||
@ -743,6 +779,7 @@ export interface WidgetMobileActionDescriptor extends WidgetMobileActionDescript
|
||||
type: WidgetMobileActionType;
|
||||
handleErrorFunction?: TbFunction;
|
||||
handleEmptyResultFunction?: TbFunction;
|
||||
handleNonMobileFallbackFunction?: TbFunction;
|
||||
}
|
||||
|
||||
export interface CustomActionDescriptor {
|
||||
|
||||
@ -17,13 +17,18 @@
|
||||
import { EntityAliases, EntityAliasInfo, getEntityAliasId } from '@shared/models/alias.models';
|
||||
import { FilterInfo, Filters, getFilterId } from '@shared/models/query/query.models';
|
||||
import { Dashboard } from '@shared/models/dashboard.models';
|
||||
import { Datasource, DatasourceType, Widget } from '@shared/models/widget.models';
|
||||
import { DataKey, Datasource, datasourcesHasAggregation, DatasourceType, Widget } from '@shared/models/widget.models';
|
||||
import {
|
||||
additionalMapDataSourcesToDatasources,
|
||||
BaseMapSettings,
|
||||
CirclesDataLayerSettings,
|
||||
MapDataLayerSettings,
|
||||
MapDataLayerType,
|
||||
MapDataSourceSettings,
|
||||
mapDataSourceSettingsToDatasource,
|
||||
MapType
|
||||
MapType,
|
||||
MarkersDataLayerSettings,
|
||||
PolygonsDataLayerSettings
|
||||
} from '@shared/models/widget/maps/map.models';
|
||||
import { WidgetModelDefinition } from '@shared/models/widget/widget-model.definition';
|
||||
|
||||
@ -124,6 +129,27 @@ export const MapModelDefinition: WidgetModelDefinition<MapDatasourcesInfo> = {
|
||||
datasources.push(...getMapDataLayersDatasources(settings.additionalDataSources));
|
||||
}
|
||||
return datasources;
|
||||
},
|
||||
hasTimewindow(widget: Widget): boolean {
|
||||
const settings: BaseMapSettings = widget.config.settings as BaseMapSettings;
|
||||
if (settings.trips?.length) {
|
||||
return true;
|
||||
} else {
|
||||
const datasources: Datasource[] = [];
|
||||
if (settings.markers?.length) {
|
||||
datasources.push(...getMapLatestDataLayersDatasources(settings.markers, 'markers'));
|
||||
}
|
||||
if (settings.polygons?.length) {
|
||||
datasources.push(...getMapLatestDataLayersDatasources(settings.polygons, 'polygons'));
|
||||
}
|
||||
if (settings.circles?.length) {
|
||||
datasources.push(...getMapLatestDataLayersDatasources(settings.circles, 'circles'));
|
||||
}
|
||||
if (settings.additionalDataSources?.length) {
|
||||
datasources.push(...additionalMapDataSourcesToDatasources(settings.additionalDataSources));
|
||||
}
|
||||
return datasourcesHasAggregation(datasources);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -223,3 +249,40 @@ const getMapDataLayersDatasources = (settings: MapDataLayerSettings[] | MapDataS
|
||||
});
|
||||
return datasources;
|
||||
};
|
||||
|
||||
const getMapLatestDataLayersDatasources = (settings: MapDataLayerSettings[],
|
||||
dataLayerType: MapDataLayerType): Datasource[] => {
|
||||
const datasources: Datasource[] = [];
|
||||
settings.forEach((dsSettings) => {
|
||||
const dataKeys: DataKey[] = getMapLatestDataLayerDatasourceDataKeys(dsSettings, dataLayerType);
|
||||
const datasource: Datasource = mapDataSourceSettingsToDatasource(dsSettings);
|
||||
datasource.dataKeys.push(...dataKeys);
|
||||
datasources.push(datasource);
|
||||
if ((dsSettings).additionalDataSources?.length) {
|
||||
(dsSettings).additionalDataSources.forEach((ds) => {
|
||||
const additionalDatasource: Datasource = mapDataSourceSettingsToDatasource(ds);
|
||||
additionalDatasource.dataKeys.push(...dataKeys);
|
||||
datasources.push(additionalDatasource);
|
||||
});
|
||||
}
|
||||
});
|
||||
return datasources;
|
||||
};
|
||||
|
||||
const getMapLatestDataLayerDatasourceDataKeys = (settings: MapDataLayerSettings,
|
||||
dataLayerType: MapDataLayerType): DataKey[] => {
|
||||
const dataKeys = settings.additionalDataKeys || [];
|
||||
switch (dataLayerType) {
|
||||
case 'markers':
|
||||
const markersSettings = settings as MarkersDataLayerSettings;
|
||||
dataKeys.push(markersSettings.xKey, markersSettings.yKey);
|
||||
break;
|
||||
case 'polygons':
|
||||
dataKeys.push((settings as PolygonsDataLayerSettings).polygonKey);
|
||||
break;
|
||||
case 'circles':
|
||||
dataKeys.push((settings as CirclesDataLayerSettings).circleKey);
|
||||
break;
|
||||
}
|
||||
return dataKeys;
|
||||
};
|
||||
|
||||
@ -25,6 +25,7 @@ export interface WidgetModelDefinition<T = any> {
|
||||
prepareExportInfo(dashboard: Dashboard, widget: Widget): T;
|
||||
updateFromExportInfo(widget: Widget, entityAliases: EntityAliases, filters: Filters, info: T): void;
|
||||
datasources(widget: Widget): Datasource[];
|
||||
hasTimewindow(widget: Widget): boolean;
|
||||
}
|
||||
|
||||
const widgetModelRegistry: WidgetModelDefinition[] = [
|
||||
|
||||
@ -6868,7 +6868,23 @@
|
||||
"scan-qr-code": "Scan QR Code",
|
||||
"make-phone-call": "Make phone call",
|
||||
"get-location": "Get phone location",
|
||||
"take-screenshot": "Take screenshot"
|
||||
"take-screenshot": "Take screenshot",
|
||||
"handle-provision-success-function": "Handle provision success function",
|
||||
"get-location-function": "Get location function",
|
||||
"process-launch-result-function": "Process launch result function",
|
||||
"get-phone-number-function": "Get phone number function",
|
||||
"process-image-function": "Process image function",
|
||||
"process-qr-code-function": "Process QR code function",
|
||||
"process-location-function": "Process location function",
|
||||
"handle-empty-result-function": "Handle empty result function",
|
||||
"handle-error-function": "Handle error function",
|
||||
"handle-non-mobile-fallback-function": "Handle Non-Mobile fallback function",
|
||||
"save-to-gallery": "Save to gallery",
|
||||
"provision-type": "Provision type",
|
||||
"auto": "Auto",
|
||||
"wi-fi": "Wi-Fi",
|
||||
"ble": "BLE",
|
||||
"soft-ap": "Soft AP"
|
||||
},
|
||||
"custom-action-function": "Custom action function",
|
||||
"custom-pretty-function": "Custom action (with HTML template) function",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user