Browse Source

Merge branch 'master' into lwm2m_rpc_fw_sw

pull/4607/head
nickAS21 5 years ago
parent
commit
c57fc5504b
  1. 6
      application/src/main/data/json/system/widget_bundles/cards.json
  2. 4
      application/src/main/data/json/system/widget_bundles/control_widgets.json
  3. 23
      application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
  4. 14
      application/src/main/java/org/thingsboard/server/controller/RpcController.java
  5. 3
      application/src/test/resources/application-test.properties
  6. 28
      common/data/src/main/java/org/thingsboard/server/common/data/rpc/RpcRequest.java
  7. 3
      common/queue/src/main/proto/queue.proto
  8. 2
      rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js
  9. 4
      ui-ngx/angular.json
  10. 8
      ui-ngx/extra-webpack.config.js
  11. 128
      ui-ngx/package.json
  12. 2
      ui-ngx/src/.browserslistrc
  13. 2
      ui-ngx/src/app/app-routing.module.ts
  14. 8
      ui-ngx/src/app/core/api/widget-api.models.ts
  15. 19
      ui-ngx/src/app/core/api/widget-subscription.ts
  16. 2
      ui-ngx/src/app/core/http/firmware.service.ts
  17. 7
      ui-ngx/src/app/core/http/widget.service.ts
  18. 6
      ui-ngx/src/app/core/services/dynamic-component-factory.service.ts
  19. 4
      ui-ngx/src/app/core/utils.ts
  20. 6
      ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions.component.ts
  21. 2
      ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.html
  22. 5
      ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.ts
  23. 14
      ui-ngx/src/app/modules/home/components/widget/dialog/custom-dialog.component.ts
  24. 16
      ui-ngx/src/app/modules/home/components/widget/dynamic-widget.component.ts
  25. 4
      ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator.component.ts
  26. 4
      ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.ts
  27. 2
      ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.html
  28. 10
      ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts
  29. 1
      ui-ngx/src/app/modules/home/components/widget/widget-config.component.html
  30. 11
      ui-ngx/src/app/modules/home/models/widget-component.models.ts
  31. 5
      ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.scss
  32. 29
      ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts
  33. 6
      ui-ngx/src/app/shared/components/firmware/firmware-autocomplete.component.ts
  34. 2
      ui-ngx/src/app/shared/components/snack-bar-component.html
  35. 3
      ui-ngx/src/app/shared/components/toast.directive.ts
  36. 23
      ui-ngx/src/app/shared/decorators/tb-inject.ts
  37. 1
      ui-ngx/src/assets/locale/locale.constant-en_US.json
  38. 5376
      ui-ngx/yarn.lock

6
application/src/main/data/json/system/widget_bundles/cards.json

File diff suppressed because one or more lines are too long

4
application/src/main/data/json/system/widget_bundles/control_widgets.json

@ -18,8 +18,8 @@
"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",
"controllerScript": "var requestTimeout = 500;\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.targetDeviceName && subscription.targetDeviceName.length) {\n deviceName = subscription.targetDeviceName;\n }\n if (self.ctx.settings.requestTimeout) {\n requestTimeout = self.ctx.settings.requestTimeout;\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 if (localCommand === 'help') {\n printUsage(this);\n } else {\n var cmdObj = $.terminal.parse_command(localCommand);\n if (cmdObj.args.length > 1) {\n this.error(\"Wrong number of arguments!\");\n this.echo(' ');\n } else {\n var params;\n if (cmdObj.args.length && cmdObj.args[0]) {\n try {\n params = JSON.parse(cmdObj.args[0]);\n } catch (e) {\n params = cmdObj.args[0];\n }\n }\n performRpc(this, cmdObj.name, params);\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 \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\nfunction performRpc(terminal, method, params) {\n terminal.pause();\n self.ctx.controlApi.sendTwoWayCommand(method, params, requestTimeout).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 \nself.onDestroy = function() {\n}\n",
"settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"requestTimeout\": {\n \"title\": \"RPC request timeout (ms)\",\n \"type\": \"number\",\n \"default\": 500\n }\n },\n \"required\": [\"requestTimeout\"]\n },\n \"form\": [\n \"requestTimeout\"\n ]\n}",
"controllerScript": "var requestTimeout = 500;\nvar multiParams = false;\n\nself.onInit = function() {\n var subscription = self.ctx.defaultSubscription;\n var utils = self.ctx.$scope.$injector.get(self.ctx.servicesMap.get('utils'));\n var rpcEnabled = subscription.rpcEnabled;\n var deviceName = 'Simulated';\n var prompt;\n if (subscription.targetDeviceName && subscription.targetDeviceName.length) {\n deviceName = subscription.targetDeviceName;\n }\n if (self.ctx.settings.requestTimeout) {\n requestTimeout = self.ctx.settings.requestTimeout;\n }\n if (self.ctx.settings.multiParams) {\n multiParams = self.ctx.settings.multiParams;\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 = utils.guid();\n if (localCommand === 'help') {\n printUsage(this);\n } else {\n var cmdObj = $.terminal.parse_command(localCommand);\n if (cmdObj.args) {\n if (!multiParams && cmdObj.args.length > 1) {\n this.error(\"Wrong number of arguments!\");\n this.echo(' ');\n }\n else {\n if (cmdObj.args.length) {\n var params = getMultiParams(cmdObj.args);\n }\n performRpc(this, cmdObj.name, params, requestUUID);\n }\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 \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 (multiParams===false):]\\n'; \n commandsListText += ' myRemoteMethod1 myText\\n\\n'; \n commandsListText += '[[b;#fff;]Example 2 (multiParams===false):]\\n'; \n commandsListText += ' myOtherRemoteMethod \"{\\\\\"key1\\\\\":2,\\\\\"key2\\\\\":\\\\\"myVal\\\\\"}\"\\n\\n'; \n commandsListText += '[[b;#fff;]Example 3 (multiParams===true)]\\n'; \n commandsListText += ' <method> [params body] = \"all the string after the method, including spaces\"]\\n';\n commandsListText += ' myOtherRemoteMethod \"{\\\\\"key1\\\\\": \"battery level\", \\\\\"key2\\\\\": \\\\\"myVal\\\\\"}\"\\n'; \n terminal.echo(new String(commandsListText));\n}\n\nfunction performRpc(terminal, method, params, requestUUID) {\n terminal.pause();\n self.ctx.controlApi.sendTwoWayCommand(method, params, requestTimeout, 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\nfunction getMultiParams(cmdObj) {\n var params = \"\";\n cmdObj.forEach((element) => {\n try {\n params += \" \" + JSON.strigify(JSON.parse(element));\n } catch (e) {\n params += \" \" + element;\n }\n })\n return params.trim();\n}\n\n \nself.onDestroy = function() {\n}",
"settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"requestTimeout\": {\n \"title\": \"RPC request timeout (ms)\",\n \"type\": \"number\",\n \"default\": 500\n },\n \"multiParams\": {\n \"title\": \"RPC params All line\",\n \"type\": \"boolean\",\n \"default\": false\n }\n },\n \"required\": [\"requestTimeout\"]\n },\n \"form\": [\n \"requestTimeout\",\n \"multiParams\"\n ]\n}",
"dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":true,\"backgroundColor\":\"#010101\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"parseGpioStatusFunction\":\"return body[pin] === true;\",\"gpioStatusChangeRequest\":{\"method\":\"setGpioStatus\",\"paramsBody\":\"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"},\"requestTimeout\":500,\"switchPanelBackgroundColor\":\"#b71c1c\",\"gpioStatusRequest\":{\"method\":\"getGpioStatus\",\"paramsBody\":\"{}\"},\"gpioList\":[{\"pin\":1,\"label\":\"GPIO 1\",\"row\":0,\"col\":0,\"_uniqueKey\":0},{\"pin\":2,\"label\":\"GPIO 2\",\"row\":0,\"col\":1,\"_uniqueKey\":1},{\"pin\":3,\"label\":\"GPIO 3\",\"row\":1,\"col\":0,\"_uniqueKey\":2}]},\"title\":\"RPC debug terminal\",\"dropShadow\":true,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
}

23
application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java

@ -25,8 +25,8 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.thingsboard.rule.engine.api.RpcError;
import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg;
import org.thingsboard.rule.engine.api.msg.DeviceEdgeUpdateMsg;
import org.thingsboard.rule.engine.api.msg.DeviceCredentialsUpdateNotificationMsg;
import org.thingsboard.rule.engine.api.msg.DeviceEdgeUpdateMsg;
import org.thingsboard.rule.engine.api.msg.DeviceNameOrTypeUpdateMsg;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.TbActorCtx;
@ -73,7 +73,6 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseM
import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg;
import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToTransportUpdateCredentialsProto;
import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto;
import org.thingsboard.server.service.rpc.FromDeviceRpcResponse;
import org.thingsboard.server.service.rpc.FromDeviceRpcResponseActorMsg;
@ -164,8 +163,14 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
void processRpcRequest(TbActorCtx context, ToDeviceRpcRequestActorMsg msg) {
ToDeviceRpcRequest request = msg.getMsg();
ToDeviceRpcRequestBody body = request.getBody();
ToDeviceRpcRequestMsg rpcRequest = ToDeviceRpcRequestMsg.newBuilder().setRequestId(
rpcSeq++).setMethodName(body.getMethod()).setParams(body.getParams()).build();
ToDeviceRpcRequestMsg rpcRequest = ToDeviceRpcRequestMsg.newBuilder()
.setRequestId(rpcSeq++)
.setMethodName(body.getMethod())
.setParams(body.getParams())
.setExpirationTime(request.getExpirationTime())
.setRequestIdMSB(request.getId().getMostSignificantBits())
.setRequestIdLSB(request.getId().getLeastSignificantBits())
.build();
long timeout = request.getExpirationTime() - System.currentTimeMillis();
if (timeout <= 0) {
@ -258,8 +263,14 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
sentOneWayIds.add(entry.getKey());
systemContext.getTbCoreDeviceRpcService().processRpcResponseFromDeviceActor(new FromDeviceRpcResponse(request.getId(), null, null));
}
ToDeviceRpcRequestMsg rpcRequest = ToDeviceRpcRequestMsg.newBuilder().setRequestId(
entry.getKey()).setMethodName(body.getMethod()).setParams(body.getParams()).build();
ToDeviceRpcRequestMsg rpcRequest = ToDeviceRpcRequestMsg.newBuilder()
.setRequestId(entry.getKey())
.setMethodName(body.getMethod())
.setParams(body.getParams())
.setExpirationTime(request.getExpirationTime())
.setRequestIdMSB(request.getId().getMostSignificantBits())
.setRequestIdLSB(request.getId().getLeastSignificantBits())
.build();
sendToTransport(rpcRequest, sessionId, nodeId);
};
}

14
application/src/main/java/org/thingsboard/server/controller/RpcController.java

@ -40,7 +40,6 @@ import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.rpc.RpcRequest;
import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody;
import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
import org.thingsboard.server.queue.util.TbCoreComponent;
@ -97,22 +96,17 @@ public class RpcController extends BaseController {
private DeferredResult<ResponseEntity> handleDeviceRPCRequest(boolean oneWay, DeviceId deviceId, String requestBody) throws ThingsboardException {
try {
JsonNode rpcRequestBody = jsonMapper.readTree(requestBody);
RpcRequest cmd = new RpcRequest(rpcRequestBody.get("method").asText(),
jsonMapper.writeValueAsString(rpcRequestBody.get("params")));
if (rpcRequestBody.has("timeout")) {
cmd.setTimeout(rpcRequestBody.get("timeout").asLong());
}
ToDeviceRpcRequestBody body = new ToDeviceRpcRequestBody(rpcRequestBody.get("method").asText(), jsonMapper.writeValueAsString(rpcRequestBody.get("params")));
SecurityUser currentUser = getCurrentUser();
TenantId tenantId = currentUser.getTenantId();
final DeferredResult<ResponseEntity> response = new DeferredResult<>();
long timeout = cmd.getTimeout() != null ? cmd.getTimeout() : defaultTimeout;
long timeout = rpcRequestBody.has("timeout") ? rpcRequestBody.get("timeout").asLong() : defaultTimeout;
long expTime = System.currentTimeMillis() + Math.max(minTimeout, timeout);
ToDeviceRpcRequestBody body = new ToDeviceRpcRequestBody(cmd.getMethodName(), cmd.getRequestData());
UUID rpcRequestUUID = rpcRequestBody.has("requestUUID") ? UUID.fromString(rpcRequestBody.get("requestUUID").asText()) : UUID.randomUUID();
accessValidator.validate(currentUser, Operation.RPC_CALL, deviceId, new HttpValidationCallback(response, new FutureCallback<DeferredResult<ResponseEntity>>() {
@Override
public void onSuccess(@Nullable DeferredResult<ResponseEntity> result) {
ToDeviceRpcRequest rpcRequest = new ToDeviceRpcRequest(UUID.randomUUID(),
ToDeviceRpcRequest rpcRequest = new ToDeviceRpcRequest(rpcRequestUUID,
tenantId,
deviceId,
oneWay,

3
application/src/test/resources/application-test.properties

@ -1,3 +1,4 @@
transport.lwm2m.security.key_store=lwm2m/credentials/serverKeyStore.jks
transport.lwm2m.security.key_store_password=server
edges.enabled=true
edges.enabled=true
transport.lwm2m.bootstrap.security.alias=server

28
common/data/src/main/java/org/thingsboard/server/common/data/rpc/RpcRequest.java

@ -1,28 +0,0 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.rpc;
import lombok.Data;
/**
* @author Andrew Shvayka
*/
@Data
public class RpcRequest {
private final String methodName;
private final String requestData;
private Long timeout;
}

3
common/queue/src/main/proto/queue.proto

@ -318,6 +318,9 @@ message ToDeviceRpcRequestMsg {
int32 requestId = 1;
string methodName = 2;
string params = 3;
int64 expirationTime = 4;
int64 requestIdMSB = 5;
int64 requestIdLSB = 6;
}
message ToDeviceRpcResponseMsg {

2
rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js

File diff suppressed because one or more lines are too long

4
ui-ngx/angular.json

@ -132,7 +132,6 @@
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false,
@ -150,7 +149,6 @@
"serve": {
"builder": "@angular-builders/custom-webpack:dev-server",
"options": {
"aot": true,
"browserTarget": "thingsboard:build",
"proxyConfig": "proxy.conf.js"
},
@ -231,4 +229,4 @@
"packageManager": "yarn",
"analytics": false
}
}
}

8
ui-ngx/extra-webpack.config.js

@ -17,7 +17,7 @@ const CompressionPlugin = require("compression-webpack-plugin");
const TerserPlugin = require("terser-webpack-plugin");
const webpack = require("webpack");
const dirTree = require("directory-tree");
const AngularCompilerPlugin = require('@ngtools/webpack');
const ngWebpack = require('@ngtools/webpack');
var langs = [];
@ -56,12 +56,12 @@ module.exports = (config, options) => {
);
if (config.mode === 'production') {
const index = config.plugins.findIndex(p => p instanceof AngularCompilerPlugin.AngularCompilerPlugin);
const angularCompilerOptions = config.plugins[index]._options;
const index = config.plugins.findIndex(p => p instanceof ngWebpack.ivy.AngularWebpackPlugin);
const angularCompilerOptions = config.plugins[index].pluginOptions;
angularCompilerOptions.emitClassMetadata = true;
angularCompilerOptions.emitNgModuleScope = true;
config.plugins.splice(index, 1);
config.plugins.push(new AngularCompilerPlugin.AngularCompilerPlugin(angularCompilerOptions));
config.plugins.push(new ngWebpack.ivy.AngularWebpackPlugin(angularCompilerOptions));
const terserPluginOptions = config.optimization.minimizer[1].options;
delete terserPluginOptions.terserOptions.compress.global_defs.ngJitMode;
terserPluginOptions.terserOptions.compress.side_effects = false;

128
ui-ngx/package.json

@ -12,68 +12,68 @@
},
"private": true,
"dependencies": {
"@angular/animations": "^10.1.5",
"@angular/cdk": "^10.2.4",
"@angular/common": "^10.1.5",
"@angular/compiler": "^10.1.5",
"@angular/core": "^10.1.5",
"@angular/flex-layout": "^10.0.0-beta.32",
"@angular/forms": "^10.1.5",
"@angular/material": "^10.2.4",
"@angular/platform-browser": "^10.1.5",
"@angular/platform-browser-dynamic": "^10.1.5",
"@angular/router": "^10.1.5",
"@auth0/angular-jwt": "^5.0.1",
"@date-io/date-fns": "^2.6.1",
"@angular/animations": "^11.2.14",
"@angular/cdk": "^11.2.13",
"@angular/common": "^11.2.14",
"@angular/compiler": "^11.2.14",
"@angular/core": "^11.2.14",
"@angular/flex-layout": "^11.0.0-beta.33",
"@angular/forms": "^11.2.14",
"@angular/material": "^11.2.13",
"@angular/platform-browser": "^11.2.14",
"@angular/platform-browser-dynamic": "^11.2.14",
"@angular/router": "^11.2.14",
"@auth0/angular-jwt": "^5.0.2",
"@date-io/date-fns": "^2.10.11",
"@flowjs/flow.js": "^2.14.1",
"@flowjs/ngx-flow": "^0.4.4",
"@juggle/resize-observer": "^3.1.3",
"@mat-datetimepicker/core": "^5.1.0",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/pickers": "^3.2.10",
"@ngrx/effects": "^10.0.1",
"@ngrx/store": "^10.0.1",
"@ngrx/store-devtools": "^10.0.1",
"@flowjs/ngx-flow": "~0.4.6",
"@juggle/resize-observer": "^3.3.1",
"@mat-datetimepicker/core": "~6.0.2",
"@material-ui/core": "^4.11.4",
"@material-ui/icons": "^4.11.2",
"@material-ui/pickers": "^3.3.10",
"@ngrx/effects": "^10.1.2",
"@ngrx/store": "^10.1.2",
"@ngrx/store-devtools": "^10.1.2",
"@ngx-translate/core": "^13.0.0",
"@ngx-translate/http-loader": "^6.0.0",
"ace-builds": "^1.4.12",
"angular-gridster2": "^10.1.6",
"angular-gridster2": "~11.2.0",
"angular2-hotkeys": "^2.2.0",
"canvas-gauges": "^2.1.7",
"compass-sass-mixins": "^0.12.7",
"core-js": "^3.6.5",
"date-fns": "^2.15.0",
"core-js": "^3.12.1",
"date-fns": "^2.21.3",
"flot": "git://github.com/thingsboard/flot.git#0.9-work",
"flot.curvedlines": "git://github.com/MichaelZinsmaier/CurvedLines.git#master",
"font-awesome": "^4.7.0",
"html2canvas": "^1.0.0-rc.7",
"jquery": "^3.5.1",
"jquery.terminal": "^2.18.3",
"js-beautify": "^1.13.0",
"jquery.terminal": "^2.24.0",
"js-beautify": "^1.13.13",
"json-schema-defaults": "^0.4.0",
"jstree": "^3.3.10",
"jstree": "^3.3.11",
"jstree-bootstrap-theme": "^1.0.1",
"jszip": "^3.5.0",
"jszip": "^3.6.0",
"leaflet": "^1.7.1",
"leaflet-editable": "^1.2.0",
"leaflet-polylinedecorator": "^1.6.0",
"leaflet-providers": "^1.10.2",
"leaflet.gridlayer.googlemutant": "0.10.0",
"leaflet.markercluster": "^1.4.1",
"leaflet-providers": "^1.12.0",
"leaflet.gridlayer.googlemutant": "0.10.2",
"leaflet.markercluster": "^1.5.0",
"material-design-icons": "^3.0.1",
"messageformat": "^2.3.0",
"moment": "^2.29.1",
"moment-timezone": "^0.5.31",
"moment-timezone": "^0.5.33",
"mousetrap": "1.6.3",
"ngx-clipboard": "^13.0.1",
"ngx-color-picker": "^10.1.0",
"ngx-clipboard": "^14.0.1",
"ngx-color-picker": "^11.0.0",
"ngx-daterangepicker-material": "^4.0.1",
"ngx-drag-drop": "^2.0.0",
"ngx-flowchart": "git://github.com/thingsboard/ngx-flowchart.git#master",
"ngx-hm-carousel": "^2.0.0-rc.1",
"ngx-sharebuttons": "^8.0.1",
"ngx-translate-messageformat-compiler": "^4.8.0",
"ngx-sharebuttons": "^8.0.5",
"ngx-translate-messageformat-compiler": "^4.9.0",
"objectpath": "^2.0.0",
"prettier": "^2.1.2",
"prop-types": "^15.7.2",
@ -84,58 +84,58 @@
"react-dom": "^16.13.1",
"react-dropzone": "^11.2.0",
"reactcss": "^1.2.3",
"rxjs": "^6.6.3",
"schema-inspector": "^1.7.0",
"screenfull": "^5.0.2",
"split.js": "^1.6.2",
"rxjs": "~6.6.7",
"schema-inspector": "^2.0.1",
"screenfull": "^5.1.0",
"split.js": "^1.6.4",
"systemjs": "0.21.5",
"tinycolor2": "^1.4.2",
"tooltipster": "^4.2.8",
"tslib": "^2.0.2",
"tslib": "^2.2.0",
"tv4": "^1.3.0",
"typeface-roboto": "^1.1.13",
"zone.js": "~0.10.3"
"zone.js": "~0.11.4"
},
"devDependencies": {
"@angular-builders/custom-webpack": "^10.0.1",
"@angular-devkit/build-angular": "^0.1001.5",
"@angular/cli": "^10.1.5",
"@angular/compiler-cli": "^10.1.5",
"@angular/language-service": "^10.1.5",
"@ngtools/webpack": "10.1.5",
"@angular-builders/custom-webpack": "~11.1.1",
"@angular-devkit/build-angular": "^0.1102.13",
"@angular/cli": "^11.2.13",
"@angular/compiler-cli": "^11.2.14",
"@angular/language-service": "^11.2.14",
"@ngtools/webpack": "~11.2.13",
"@types/canvas-gauges": "^2.1.2",
"@types/flot": "^0.0.31",
"@types/jasmine": "^3.5.12",
"@types/jasminewd2": "^2.0.8",
"@types/jasmine": "~3.7.4",
"@types/jasminewd2": "^2.0.9",
"@types/jquery": "^3.5.2",
"@types/js-beautify": "^1.11.0",
"@types/js-beautify": "^1.13.1",
"@types/jstree": "^3.3.40",
"@types/leaflet": "^1.5.17",
"@types/leaflet": "1.5.17",
"@types/leaflet-polylinedecorator": "^1.6.0",
"@types/leaflet.markercluster": "^1.4.3",
"@types/lodash": "^4.14.161",
"@types/leaflet.markercluster": "^1.4.4",
"@types/lodash": "^4.14.170",
"@types/moment-timezone": "^0.5.30",
"@types/mousetrap": "1.6.3",
"@types/raphael": "^2.3.0",
"@types/raphael": "^2.3.1",
"@types/react": "^16.9.51",
"@types/react-dom": "^16.9.8",
"@types/tinycolor2": "^1.4.2",
"@types/tooltipster": "^0.0.30",
"codelyzer": "^6.0.1",
"compression-webpack-plugin": "^6.0.2",
"codelyzer": "^6.0.2",
"compression-webpack-plugin": "^6.1.1",
"directory-tree": "^2.2.4",
"jasmine-core": "~3.6.0",
"jasmine-spec-reporter": "~5.0.2",
"karma": "~5.1.1",
"jasmine-core": "~3.7.1",
"jasmine-spec-reporter": "~7.0.0",
"karma": "~6.3.2",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage-istanbul-reporter": "~3.0.3",
"karma-jasmine": "~4.0.1",
"karma-jasmine-html-reporter": "^1.5.4",
"karma-jasmine": "~4.0.0",
"karma-jasmine-html-reporter": "^1.5.0",
"ngrx-store-freeze": "^0.2.4",
"protractor": "~7.0.0",
"ts-node": "^9.0.0",
"tslint": "~6.1.3",
"typescript": "~4.0.3",
"webpack": "^4.44.2"
"webpack": "^4.46.0"
}
}

2
ui-ngx/src/.browserslistrc

@ -8,4 +8,4 @@
last 2 versions
Firefox ESR
not dead
IE 10-11
IE 11

2
ui-ngx/src/app/app-routing.module.ts

@ -30,7 +30,7 @@ const routes: Routes = [
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
imports: [RouterModule.forRoot(routes, { relativeLinkResolution: 'legacy' })],
exports: [RouterModule]
})
export class AppRoutingModule { }

8
ui-ngx/src/app/core/api/widget-api.models.ts

@ -69,8 +69,8 @@ export interface WidgetSubscriptionApi {
}
export interface RpcApi {
sendOneWayCommand: (method: string, params?: any, timeout?: number) => Observable<any>;
sendTwoWayCommand: (method: string, params?: any, timeout?: number) => Observable<any>;
sendOneWayCommand: (method: string, params?: any, timeout?: number, requestUUID?: string) => Observable<any>;
sendTwoWayCommand: (method: string, params?: any, timeout?: number, requestUUID?: string) => Observable<any>;
}
export interface IWidgetUtils {
@ -298,8 +298,8 @@ export interface IWidgetSubscription {
onResetTimewindow(): void;
updateTimewindowConfig(newTimewindow: Timewindow): void;
sendOneWayCommand(method: string, params?: any, timeout?: number): Observable<any>;
sendTwoWayCommand(method: string, params?: any, timeout?: number): Observable<any>;
sendOneWayCommand(method: string, params?: any, timeout?: number, requestUUID?: string): Observable<any>;
sendTwoWayCommand(method: string, params?: any, timeout?: number, requestUUID?: string): Observable<any>;
clearRpcError(): void;
subscribe(): void;

19
ui-ngx/src/app/core/api/widget-subscription.ts

@ -38,12 +38,14 @@ import {
import { HttpErrorResponse } from '@angular/common/http';
import {
calculateIntervalStartEndTime,
calculateTsOffset, ComparisonDuration,
calculateTsOffset,
ComparisonDuration,
createSubscriptionTimewindow,
createTimewindowForComparison,
isHistoryTypeTimewindow,
SubscriptionTimewindow,
Timewindow, timewindowTypeChanged,
Timewindow,
timewindowTypeChanged,
toHistoryTimewindow,
WidgetTimewindow
} from '@app/shared/models/time/time.models';
@ -642,12 +644,12 @@ export class WidgetSubscription implements IWidgetSubscription {
}
}
sendOneWayCommand(method: string, params?: any, timeout?: number): Observable<any> {
return this.sendCommand(true, method, params, timeout);
sendOneWayCommand(method: string, params?: any, timeout?: number, requestUUID?: string): Observable<any> {
return this.sendCommand(true, method, params, timeout, requestUUID);
}
sendTwoWayCommand(method: string, params?: any, timeout?: number): Observable<any> {
return this.sendCommand(false, method, params, timeout);
sendTwoWayCommand(method: string, params?: any, timeout?: number, requestUUID?: string): Observable<any> {
return this.sendCommand(false, method, params, timeout, requestUUID);
}
clearRpcError(): void {
@ -656,7 +658,7 @@ export class WidgetSubscription implements IWidgetSubscription {
this.callbacks.onRpcErrorCleared(this);
}
sendCommand(oneWayElseTwoWay: boolean, method: string, params?: any, timeout?: number): Observable<any> {
sendCommand(oneWayElseTwoWay: boolean, method: string, params?: any, timeout?: number, requestUUID?: string): Observable<any> {
if (!this.rpcEnabled) {
return throwError(new Error('Rpc disabled!'));
} else {
@ -667,7 +669,8 @@ export class WidgetSubscription implements IWidgetSubscription {
}
const requestBody: any = {
method,
params
params,
requestUUID
};
if (timeout && timeout > 0) {
requestBody.timeout = timeout;

2
ui-ngx/src/app/core/http/firmware.service.ts

@ -22,7 +22,7 @@ import { Observable } from 'rxjs';
import { PageData } from '@shared/models/page/page-data';
import { ChecksumAlgorithm, Firmware, FirmwareInfo, FirmwareType } from '@shared/models/firmware.models';
import { catchError, map, mergeMap } from 'rxjs/operators';
import { deepClone, isDefinedAndNotNull } from '@core/utils';
import { deepClone } from '@core/utils';
@Injectable({
providedIn: 'root'

7
ui-ngx/src/app/core/http/widget.service.ts

@ -32,12 +32,7 @@ import {
import { UtilsService } from '@core/services/utils.service';
import { TranslateService } from '@ngx-translate/core';
import { ResourcesService } from '../services/resources.service';
import {
toWidgetInfo,
toWidgetType,
toWidgetTypeDetails,
WidgetInfo
} from '@app/modules/home/models/widget-component.models';
import { toWidgetInfo, toWidgetTypeDetails, WidgetInfo } from '@app/modules/home/models/widget-component.models';
import { filter, map, mergeMap, tap } from 'rxjs/operators';
import { WidgetTypeId } from '@shared/models/id/widget-type-id';
import { NULL_UUID } from '@shared/models/id/has-uuid';

6
ui-ngx/src/app/core/services/dynamic-component-factory.service.ts

@ -41,11 +41,9 @@ interface DynamicComponentModuleData {
moduleType: Type<DynamicComponentModule>;
}
@Injectable(
{
@Injectable({
providedIn: 'root'
}
)
})
export class DynamicComponentFactoryService {
private dynamicComponentModulesMap = new Map<ComponentFactory<any>, DynamicComponentModuleData>();

4
ui-ngx/src/app/core/utils.ts

@ -438,6 +438,6 @@ export function generateSecret(length?: number): string {
return str.concat(generateSecret(length - str.length));
}
export function validateEntityId(entityId: EntityId): boolean {
return isDefinedAndNotNull(entityId.id) && entityId.id !== NULL_UUID && isDefinedAndNotNull(entityId.entityType);
export function validateEntityId(entityId: EntityId | null): boolean {
return isDefinedAndNotNull(entityId?.id) && entityId.id !== NULL_UUID && isDefinedAndNotNull(entityId?.entityType);
}

6
ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions.component.ts

@ -42,6 +42,7 @@ import {
WidgetActionDialogData
} from '@home/components/widget/action/widget-action-dialog.component';
import { deepClone } from '@core/utils';
import { widgetType } from '@shared/models/widget.models';
@Component({
selector: 'tb-manage-widget-actions',
@ -59,6 +60,8 @@ export class ManageWidgetActionsComponent extends PageComponent implements OnIni
@Input() disabled: boolean;
@Input() widgetType: widgetType;
@Input() callbacks: WidgetActionCallbacks;
innerValue: WidgetActionsData;
@ -180,7 +183,8 @@ export class ManageWidgetActionsComponent extends PageComponent implements OnIni
isAdd,
callbacks: this.callbacks,
actionsData,
action: deepClone(action)
action: deepClone(action),
widgetType: this.widgetType
}
}).afterClosed().subscribe(
(res) => {

2
ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.html

@ -120,7 +120,7 @@
widgetActionFormGroup.get('type').value === widgetActionType.updateDashboardState ||
widgetActionFormGroup.get('type').value === widgetActionType.openDashboard ?
widgetActionFormGroup.get('type').value : ''">
<mat-checkbox formControlName="setEntityId">
<mat-checkbox *ngIf="data.widgetType !== widgetType.static" formControlName="setEntityId">
{{ 'widget-action.set-entity-from-widget' | translate }}
</mat-checkbox>
<mat-form-field *ngIf="actionTypeFormGroup.get('setEntityId').value"

5
ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.ts

@ -46,12 +46,14 @@ import { DashboardUtilsService } from '@core/services/dashboard-utils.service';
import { CustomActionEditorCompleter } from '@home/components/widget/action/custom-action.models';
import { isDefinedAndNotNull } from '@core/utils';
import { MobileActionEditorComponent } from '@home/components/widget/action/mobile-action-editor.component';
import { widgetType } from '@shared/models/widget.models';
export interface WidgetActionDialogData {
isAdd: boolean;
callbacks: WidgetActionCallbacks;
actionsData: WidgetActionsData;
action?: WidgetActionDescriptorInfo;
widgetType: widgetType;
}
@Component({
@ -84,6 +86,7 @@ export class WidgetActionDialogComponent extends DialogComponent<WidgetActionDia
customActionEditorCompleter = CustomActionEditorCompleter;
submitted = false;
widgetType = widgetType;
constructor(protected store: Store<AppState>,
protected router: Router,
@ -141,7 +144,7 @@ export class WidgetActionDialogComponent extends DialogComponent<WidgetActionDia
);
this.actionTypeFormGroup.addControl(
'setEntityId',
this.fb.control(action ? action.setEntityId : true, [])
this.fb.control(this.data.widgetType === widgetType.static ? false : action ? action.setEntityId : true, [])
);
this.actionTypeFormGroup.addControl(
'stateEntityParamName',

14
ui-ngx/src/app/modules/home/components/widget/dialog/custom-dialog.component.ts

@ -15,13 +15,14 @@
///
import { MatDialogRef } from '@angular/material/dialog';
import { Directive, Inject, InjectionToken } from '@angular/core';
import { Directive, InjectionToken } from '@angular/core';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { Router } from '@angular/router';
import { PageComponent } from '@shared/components/page.component';
import { CustomDialogContainerComponent } from './custom-dialog-container.component';
import { FormBuilder, Validators } from '@angular/forms';
import { TbInject } from '@shared/decorators/tb-inject';
export const CUSTOM_DIALOG_DATA = new InjectionToken<any>('ConfigDialogData');
@ -31,15 +32,16 @@ export interface CustomDialogData {
}
@Directive()
// tslint:disable-next-line:directive-class-suffix
export class CustomDialogComponent extends PageComponent {
[key: string]: any;
constructor(protected store: Store<AppState>,
protected router: Router,
public dialogRef: MatDialogRef<CustomDialogContainerComponent>,
public fb: FormBuilder,
@Inject(CUSTOM_DIALOG_DATA) public data: CustomDialogData) {
constructor(@TbInject(Store) protected store: Store<AppState>,
@TbInject(Router) protected router: Router,
@TbInject(MatDialogRef) public dialogRef: MatDialogRef<CustomDialogContainerComponent>,
@TbInject(FormBuilder) public fb: FormBuilder,
@TbInject(CUSTOM_DIALOG_DATA) public data: CustomDialogData) {
super(store);
// @ts-ignore
this.validators = Validators;

16
ui-ngx/src/app/modules/home/components/widget/dynamic-widget.component.ts

@ -15,7 +15,7 @@
///
import { PageComponent } from '@shared/components/page.component';
import { Inject, Injector, OnDestroy, OnInit, Directive } from '@angular/core';
import { Directive, Injector, OnDestroy, OnInit } from '@angular/core';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { IDynamicWidgetComponent, WidgetContext } from '@home/models/widget-component.models';
@ -42,8 +42,10 @@ import { DatePipe } from '@angular/common';
import { TranslateService } from '@ngx-translate/core';
import { DomSanitizer } from '@angular/platform-browser';
import { Router } from '@angular/router';
import { TbInject } from '@shared/decorators/tb-inject';
@Directive()
// tslint:disable-next-line:directive-class-suffix
export class DynamicWidgetComponent extends PageComponent implements IDynamicWidgetComponent, OnInit, OnDestroy {
executingRpcRequest: boolean;
@ -55,12 +57,12 @@ export class DynamicWidgetComponent extends PageComponent implements IDynamicWid
validators = Validators;
constructor(@Inject(RafService) public raf: RafService,
@Inject(Store) protected store: Store<AppState>,
@Inject(FormBuilder) public fb: FormBuilder,
@Inject(Injector) public readonly $injector: Injector,
@Inject('widgetContext') public readonly ctx: WidgetContext,
@Inject('errorMessages') public readonly errorMessages: string[]) {
constructor(@TbInject(RafService) public raf: RafService,
@TbInject(Store) protected store: Store<AppState>,
@TbInject(FormBuilder) public fb: FormBuilder,
@TbInject(Injector) public readonly $injector: Injector,
@TbInject('widgetContext') public readonly ctx: WidgetContext,
@TbInject('errorMessages') public readonly errorMessages: string[]) {
super(store);
this.ctx.$injector = $injector;
this.ctx.deviceService = $injector.get(DeviceService);

4
ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator.component.ts

@ -74,9 +74,9 @@ export class DateRangeNavigatorWidgetComponent extends PageComponent implements
private firstUpdate = true;
private dashboardTimewindowChangedSubscription: Subscription;
originalOrder = (a: KeyValue<number,DateIntervalEntry>, b: KeyValue<number,DateIntervalEntry>): number => {
originalOrder = (a: KeyValue<string, DateIntervalEntry>, b: KeyValue<string, DateIntervalEntry>): number => {
return 0;
};
}
constructor(private utils: UtilsService,
private overlay: Overlay,

4
ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.ts

@ -654,7 +654,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni
entityName = entity.entityName;
entityLabel = entity.entityLabel;
}
this.ctx.actionsApi.handleWidgetAction($event, descriptors[0], entityId, entityName, null, entityLabel);
this.ctx.actionsApi.handleWidgetAction($event, descriptors[0], entityId, entityName, {entity}, entityLabel);
}
}
@ -670,7 +670,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni
entityName = entity.entityName;
entityLabel = entity.entityLabel;
}
this.ctx.actionsApi.handleWidgetAction($event, actionDescriptor, entityId, entityName, null, entityLabel);
this.ctx.actionsApi.handleWidgetAction($event, actionDescriptor, entityId, entityName, {entity}, entityLabel);
}
}

2
ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.html

@ -39,7 +39,7 @@
</mat-toolbar>
<mat-tab-group [ngClass]="{'tb-headless': sources.length === 1}" fxFlex
[(selectedIndex)]="sourceIndex" (selectedIndexChange)="onSourceIndexChanged()">
<mat-tab *ngFor="let source of sources; trackBy: trackBySourcesIndex; let index = index;" label="{{ source.datasource.name }}">
<mat-tab *ngFor="let source of sources; trackBy: trackBySourcesIndex; let index = index;" label="{{getTabLabel(source)}}">
<ng-template [ngIf]="isActiveTab(index)">
<div fxFlex class="table-container">
<table mat-table [dataSource]="source.timeseriesDatasource" [trackBy]="trackByRowTimestamp"

10
ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts

@ -129,6 +129,7 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI
private defaultSortOrder = '-0';
private hideEmptyLines = false;
public showTimestamp = true;
private useEntityLabel = false;
private dateFormatFilter: string;
private rowStylesInfo: RowStyleInfo;
@ -205,6 +206,7 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI
this.enableStickyHeader = isDefined(this.settings.enableStickyHeader) ? this.settings.enableStickyHeader : true;
this.enableStickyAction = isDefined(this.settings.enableStickyAction) ? this.settings.enableStickyAction : true;
this.hideEmptyLines = isDefined(this.settings.hideEmptyLines) ? this.settings.hideEmptyLines : false;
this.useEntityLabel = isDefined(this.widgetConfig.settings.useEntityLabel) ? this.widgetConfig.settings.useEntityLabel : false;
this.showTimestamp = this.settings.showTimestamp !== false;
this.dateFormatFilter = (this.settings.showMilliseconds !== true) ? 'yyyy-MM-dd HH:mm:ss' : 'yyyy-MM-dd HH:mm:ss.SSS';
@ -232,6 +234,14 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI
this.updateDatasources();
}
public getTabLabel(source: TimeseriesTableSource){
if(this.useEntityLabel){
return source.datasource.entityLabel || source.datasource.entityName;
} else {
return source.datasource.entityName;
}
}
private updateDatasources() {
this.sources = [];
this.sourceIndex = 0;

1
ui-ngx/src/app/modules/home/components/widget/widget-config.component.html

@ -443,6 +443,7 @@
<mat-tab label="{{ 'widget-config.actions' | translate }}" [formGroup]="actionsSettings">
<tb-manage-widget-actions
[callbacks]="widgetConfigCallbacks"
[widgetType] = "modelValue.widgetType"
formControlName="actionsData">
</tb-manage-widget-actions>
</mat-tab>

11
ui-ngx/src/app/modules/home/models/widget-component.models.ts

@ -27,7 +27,8 @@ import {
WidgetControllerDescriptor,
WidgetType,
widgetType,
WidgetTypeDescriptor, WidgetTypeDetails,
WidgetTypeDescriptor,
WidgetTypeDetails,
WidgetTypeParameters
} from '@shared/models/widget.models';
import { Timewindow, WidgetTimewindow } from '@shared/models/time/time.models';
@ -188,16 +189,16 @@ export class WidgetContext {
};
controlApi: RpcApi = {
sendOneWayCommand: (method, params, timeout) => {
sendOneWayCommand: (method, params, timeout, requestUUID) => {
if (this.defaultSubscription) {
return this.defaultSubscription.sendOneWayCommand(method, params, timeout);
return this.defaultSubscription.sendOneWayCommand(method, params, timeout, requestUUID);
} else {
return of(null);
}
},
sendTwoWayCommand: (method, params, timeout) => {
sendTwoWayCommand: (method, params, timeout, requestUUID) => {
if (this.defaultSubscription) {
return this.defaultSubscription.sendTwoWayCommand(method, params, timeout);
return this.defaultSubscription.sendTwoWayCommand(method, params, timeout, requestUUID);
} else {
return of(null);
}

5
ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.scss

@ -126,12 +126,13 @@
.mat-expansion-panel-header-title {
line-height: 48px;
height: 48px;
overflow: hidden;
.mat-icon {
min-width: 24px;
margin: auto 8px auto auto;
margin: auto 8px auto 0;
}
.tb-panel-title {
min-width: 140px;
min-width: 130px;
user-select: none;
}
}

29
ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts

@ -14,7 +14,17 @@
/// limitations under the License.
///
import { AfterViewInit, Component, ElementRef, forwardRef, Input, OnInit, ViewChild } from '@angular/core';
import {
AfterViewInit,
Component,
ElementRef,
EventEmitter,
forwardRef,
Input,
OnInit,
Output,
ViewChild
} from '@angular/core';
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms';
import { Observable } from 'rxjs';
import { map, mergeMap, share, tap } from 'rxjs/operators';
@ -28,6 +38,7 @@ import { EntityService } from '@core/http/entity.service';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { getCurrentAuthUser } from '@core/auth/auth.selectors';
import { Authority } from '@shared/models/authority.enum';
import { isEqual } from '@core/utils';
@Component({
selector: 'tb-entity-autocomplete',
@ -43,7 +54,7 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit
selectEntityFormGroup: FormGroup;
modelValue: string | null;
modelValue: string | EntityId | null;
entityTypeValue: EntityType | AliasEntityType;
@ -95,6 +106,9 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit
@Input()
disabled: boolean;
@Output()
entityChanged = new EventEmitter<BaseData<EntityId>>();
@ViewChild('entityInput', {static: true}) entityInput: ElementRef;
entityText: string;
@ -135,7 +149,7 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit
} else {
modelValue = value.id.id;
}
this.updateView(modelValue);
this.updateView(modelValue, value);
if (value === null) {
this.clear();
}
@ -265,10 +279,12 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit
(entity) => {
this.modelValue = entity.id.id;
this.selectEntityFormGroup.get('entity').patchValue(entity, {emitEvent: false});
this.entityChanged.emit(entity);
},
() => {
this.modelValue = null;
this.selectEntityFormGroup.get('entity').patchValue('', {emitEvent: false});
this.entityChanged.emit(null);
if (value !== null) {
this.propagateChange(this.modelValue);
}
@ -280,10 +296,12 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit
(entity) => {
this.modelValue = entity.id.id;
this.selectEntityFormGroup.get('entity').patchValue(entity, {emitEvent: false});
this.entityChanged.emit(entity);
},
() => {
this.modelValue = null;
this.selectEntityFormGroup.get('entity').patchValue('', {emitEvent: false});
this.entityChanged.emit(null);
if (value !== null) {
this.propagateChange(this.modelValue);
}
@ -311,10 +329,11 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit
this.selectEntityFormGroup.get('entity').patchValue('', {emitEvent: false});
}
updateView(value: string | null) {
if (this.modelValue !== value) {
updateView(value: string | EntityId | null, entity: BaseData<EntityId> | null) {
if (!isEqual(this.modelValue, value)) {
this.modelValue = value;
this.propagateChange(this.modelValue);
this.entityChanged.emit(entity);
}
}

6
ui-ngx/src/app/shared/components/firmware/firmware-autocomplete.component.ts

@ -47,7 +47,7 @@ export class FirmwareAutocompleteComponent implements ControlValueAccessor, OnIn
firmwareFormGroup: FormGroup;
modelValue: string | null;
modelValue: string | EntityId | null;
@Input()
type = FirmwareType.FIRMWARE;
@ -182,8 +182,8 @@ export class FirmwareAutocompleteComponent implements ControlValueAccessor, OnIn
if (firmwareId !== '') {
this.entityService.getEntity(EntityType.FIRMWARE, firmwareId, {ignoreLoading: true, ignoreErrors: true}).subscribe(
(entity) => {
this.modelValue = entity.id.id;
this.firmwareFormGroup.get('firmwareId').patchValue(entity);
this.modelValue = this.useFullEntityId ? entity.id : entity.id.id;
this.firmwareFormGroup.get('firmwareId').patchValue(entity, {emitEvent: false});
},
() => {
this.modelValue = null;

2
ui-ngx/src/app/shared/components/snack-bar-component.html

@ -25,5 +25,5 @@
'info-toast': notification.type === 'info'
}">
<div class="toast-text" [innerHTML]="notification.message"></div>
<button #actionButton type="button" mat-button (click)="action()">{{ 'action.close' | translate }}</button>
<button #actionButton type="button" mat-button (click)="action($event)">{{ 'action.close' | translate }}</button>
</div>

3
ui-ngx/src/app/shared/components/toast.directive.ts

@ -356,7 +356,8 @@ export class TbSnackBarComponent implements AfterViewInit, OnDestroy {
}
}
action(): void {
action(event: MouseEvent): void {
event.stopPropagation();
if (this.snackBarRef) {
this.snackBarRef.dismissWithAction();
} else {

23
ui-ngx/src/app/shared/decorators/tb-inject.ts

@ -0,0 +1,23 @@
///
/// Copyright © 2016-2021 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Inject, Type } from '@angular/core';
export function TbInject<T>(token: any): (target: Type<T>, key: any, paramIndex: number) => void {
return (target: Type<T>, key: any, paramIndex: number) => {
Inject(token)(target, key, paramIndex);
};
}

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

@ -412,6 +412,7 @@
"import": "Import assets",
"asset-file": "Asset file",
"label": "Label",
"search": "Search assets",
"assign-asset-to-edge": "Assign Asset(s) To Edge",
"unassign-asset-from-edge": "Unassign asset",
"unassign-asset-from-edge-title": "Are you sure you want to unassign the asset '{{assetName}}'?",

5376
ui-ngx/yarn.lock

File diff suppressed because it is too large
Loading…
Cancel
Save