Configure UI help assets base url.
@ -96,7 +96,7 @@ public class DashboardController extends BaseController {
|
|||||||
public static final String DASHBOARD_DEFINITION = "The Dashboard object is a heavyweight object that contains information about the dashboard (e.g. title, image, assigned customers) and also configuration JSON (e.g. layouts, widgets, entity aliases).";
|
public static final String DASHBOARD_DEFINITION = "The Dashboard object is a heavyweight object that contains information about the dashboard (e.g. title, image, assigned customers) and also configuration JSON (e.g. layouts, widgets, entity aliases).";
|
||||||
public static final String HIDDEN_FOR_MOBILE = "Exclude dashboards that are hidden for mobile";
|
public static final String HIDDEN_FOR_MOBILE = "Exclude dashboards that are hidden for mobile";
|
||||||
|
|
||||||
@Value("${dashboard.max_datapoints_limit}")
|
@Value("${ui.dashboard.max_datapoints_limit}")
|
||||||
private long maxDatapointsLimit;
|
private long maxDatapointsLimit;
|
||||||
|
|
||||||
@ApiOperation(value = "Get server time (getServerTime)",
|
@ApiOperation(value = "Get server time (getServerTime)",
|
||||||
|
|||||||
@ -0,0 +1,46 @@
|
|||||||
|
/**
|
||||||
|
* 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.controller;
|
||||||
|
|
||||||
|
import io.swagger.annotations.ApiOperation;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMethod;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.thingsboard.server.common.data.exception.ThingsboardException;
|
||||||
|
import org.thingsboard.server.queue.util.TbCoreComponent;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@TbCoreComponent
|
||||||
|
@RequestMapping("/api")
|
||||||
|
public class UiSettingsController extends BaseController {
|
||||||
|
|
||||||
|
@Value("${ui.help.base-url}")
|
||||||
|
private String helpBaseUrl;
|
||||||
|
|
||||||
|
@ApiOperation(value = "Get UI help base url (getHelpBaseUrl)",
|
||||||
|
notes = "Get UI help base url used to fetch help assets. " +
|
||||||
|
"The actual value of the base url is configurable in the system configuration file.")
|
||||||
|
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
|
||||||
|
@RequestMapping(value = "/uiSettings/helpBaseUrl", method = RequestMethod.GET)
|
||||||
|
@ResponseBody
|
||||||
|
public String getHelpBaseUrl() throws ThingsboardException {
|
||||||
|
return helpBaseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -129,10 +129,16 @@ usage:
|
|||||||
check:
|
check:
|
||||||
cycle: "${USAGE_STATS_CHECK_CYCLE:60000}"
|
cycle: "${USAGE_STATS_CHECK_CYCLE:60000}"
|
||||||
|
|
||||||
|
# UI parameters
|
||||||
|
ui:
|
||||||
# Dashboard parameters
|
# Dashboard parameters
|
||||||
dashboard:
|
dashboard:
|
||||||
# Maximum allowed datapoints fetched by widgets
|
# Maximum allowed datapoints fetched by widgets
|
||||||
max_datapoints_limit: "${DASHBOARD_MAX_DATAPOINTS_LIMIT:50000}"
|
max_datapoints_limit: "${DASHBOARD_MAX_DATAPOINTS_LIMIT:50000}"
|
||||||
|
# Help parameters
|
||||||
|
help:
|
||||||
|
# Base url for UI help assets
|
||||||
|
base-url: "${UI_HELP_BASE_URL:https://raw.githubusercontent.com/thingsboard/thingsboard/master/ui-ngx/src/assets}"
|
||||||
|
|
||||||
database:
|
database:
|
||||||
ts_max_intervals: "${DATABASE_TS_MAX_INTERVALS:700}" # Max number of DB queries generated by single API call to fetch telemetry records
|
ts_max_intervals: "${DATABASE_TS_MAX_INTERVALS:700}" # Max number of DB queries generated by single API call to fetch telemetry records
|
||||||
|
|||||||
@ -70,13 +70,12 @@ frontend http-in
|
|||||||
acl transport_http_acl path_beg /api/v1/
|
acl transport_http_acl path_beg /api/v1/
|
||||||
acl letsencrypt_http_acl path_beg /.well-known/acme-challenge/
|
acl letsencrypt_http_acl path_beg /.well-known/acme-challenge/
|
||||||
acl tb_api_acl path_beg /api/ /swagger /webjars /v2/ /static/rulenode/ /oauth2/ /login/oauth2/ /static/widgets/
|
acl tb_api_acl path_beg /api/ /swagger /webjars /v2/ /static/rulenode/ /oauth2/ /login/oauth2/ /static/widgets/
|
||||||
acl tb_rulenode_assets path_reg ^/assets/help/.*/rulenode/.*$
|
|
||||||
|
|
||||||
redirect scheme https if !letsencrypt_http_acl !transport_http_acl { env(FORCE_HTTPS_REDIRECT) -m str true }
|
redirect scheme https if !letsencrypt_http_acl !transport_http_acl { env(FORCE_HTTPS_REDIRECT) -m str true }
|
||||||
|
|
||||||
use_backend letsencrypt_http if letsencrypt_http_acl
|
use_backend letsencrypt_http if letsencrypt_http_acl
|
||||||
use_backend tb-http-backend if transport_http_acl
|
use_backend tb-http-backend if transport_http_acl
|
||||||
use_backend tb-api-backend if tb_api_acl or tb_rulenode_assets
|
use_backend tb-api-backend if tb_api_acl
|
||||||
|
|
||||||
default_backend tb-web-backend
|
default_backend tb-web-backend
|
||||||
|
|
||||||
@ -89,10 +88,9 @@ frontend https_in
|
|||||||
|
|
||||||
acl transport_http_acl path_beg /api/v1/
|
acl transport_http_acl path_beg /api/v1/
|
||||||
acl tb_api_acl path_beg /api/ /swagger /webjars /v2/ /static/rulenode/ /oauth2/ /login/oauth2/ /static/widgets/
|
acl tb_api_acl path_beg /api/ /swagger /webjars /v2/ /static/rulenode/ /oauth2/ /login/oauth2/ /static/widgets/
|
||||||
acl tb_rulenode_assets path_reg ^/assets/help/.*/rulenode/.*$
|
|
||||||
|
|
||||||
use_backend tb-http-backend if transport_http_acl
|
use_backend tb-http-backend if transport_http_acl
|
||||||
use_backend tb-api-backend if tb_api_acl or tb_rulenode_assets
|
use_backend tb-api-backend if tb_api_acl
|
||||||
|
|
||||||
default_backend tb-web-backend
|
default_backend tb-web-backend
|
||||||
|
|
||||||
|
|||||||
@ -26,10 +26,6 @@ const PROXY_CONFIG = {
|
|||||||
"target": ruleNodeUiforwardUrl,
|
"target": ruleNodeUiforwardUrl,
|
||||||
"secure": false,
|
"secure": false,
|
||||||
},
|
},
|
||||||
"/assets/help/*/rulenode/**": {
|
|
||||||
"target": ruleNodeUiforwardUrl,
|
|
||||||
"secure": false,
|
|
||||||
},
|
|
||||||
"/static/widgets": {
|
"/static/widgets": {
|
||||||
"target": forwardUrl,
|
"target": forwardUrl,
|
||||||
"secure": false,
|
"secure": false,
|
||||||
|
|||||||
43
ui-ngx/src/app/core/http/ui-settings.service.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
///
|
||||||
|
/// 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 { Injectable } from '@angular/core';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from '@core/http/http-utils';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { publishReplay, refCount } from 'rxjs/operators';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class UiSettingsService {
|
||||||
|
|
||||||
|
private helpBaseUrlObservable: Observable<string>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private http: HttpClient
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public getHelpBaseUrl(): Observable<string> {
|
||||||
|
if (!this.helpBaseUrlObservable) {
|
||||||
|
this.helpBaseUrlObservable = this.http.get('/api/uiSettings/helpBaseUrl', {responseType: 'text', ...defaultHttpOptions(true)}).pipe(
|
||||||
|
publishReplay(1),
|
||||||
|
refCount()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return this.helpBaseUrlObservable;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -18,23 +18,29 @@ import { Injectable } from '@angular/core';
|
|||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { Observable, of } from 'rxjs';
|
import { Observable, of } from 'rxjs';
|
||||||
import { catchError, mergeMap, tap } from 'rxjs/operators';
|
import { catchError, map, mergeMap, tap } from 'rxjs/operators';
|
||||||
import { helpBaseUrl } from '@shared/models/constants';
|
import { helpBaseUrl as siteBaseUrl } from '@shared/models/constants';
|
||||||
|
import { UiSettingsService } from '@core/http/ui-settings.service';
|
||||||
|
|
||||||
const NOT_FOUND_CONTENT = '## Not found';
|
const localHelpBaseUrl = '/assets';
|
||||||
|
|
||||||
|
const NOT_FOUND_CONTENT: HelpData = {
|
||||||
|
content: '## Not found',
|
||||||
|
helpBaseUrl: localHelpBaseUrl
|
||||||
|
};
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class HelpService {
|
export class HelpService {
|
||||||
|
|
||||||
private helpBaseUrl = helpBaseUrl;
|
private siteBaseUrl = siteBaseUrl;
|
||||||
|
|
||||||
private helpCache: {[lang: string]: {[key: string]: string}} = {};
|
private helpCache: {[lang: string]: {[key: string]: string}} = {};
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private translate: TranslateService,
|
private translate: TranslateService,
|
||||||
private http: HttpClient
|
private http: HttpClient,
|
||||||
|
private uiSettingsService: UiSettingsService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
getHelpContent(key: string): Observable<string> {
|
getHelpContent(key: string): Observable<string> {
|
||||||
@ -70,13 +76,38 @@ export class HelpService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private loadHelpContent(lang: string, key: string): Observable<string> {
|
private loadHelpContent(lang: string, key: string): Observable<HelpData> {
|
||||||
return this.http.get(`/assets/help/${lang}/${key}.md`, {responseType: 'text'} );
|
return this.uiSettingsService.getHelpBaseUrl().pipe(
|
||||||
|
mergeMap((helpBaseUrl) => {
|
||||||
|
return this.loadHelpContentFromBaseUrl(helpBaseUrl, lang, key).pipe(
|
||||||
|
catchError((e) => {
|
||||||
|
if (localHelpBaseUrl !== helpBaseUrl) {
|
||||||
|
return this.loadHelpContentFromBaseUrl(localHelpBaseUrl, lang, key);
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private processVariables(content: string): string {
|
private loadHelpContentFromBaseUrl(helpBaseUrl: string, lang: string, key: string): Observable<HelpData> {
|
||||||
const baseUrlReg = /\${baseUrl}/g;
|
return this.http.get(`${helpBaseUrl}/help/${lang}/${key}.md`, {responseType: 'text'} ).pipe(
|
||||||
return content.replace(baseUrlReg, this.helpBaseUrl);
|
map((content) => {
|
||||||
|
return {
|
||||||
|
content,
|
||||||
|
helpBaseUrl
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private processVariables(helpData: HelpData): string {
|
||||||
|
const baseUrlReg = /\${siteBaseUrl}/g;
|
||||||
|
helpData.content = helpData.content.replace(baseUrlReg, this.siteBaseUrl);
|
||||||
|
const helpBaseUrlReg = /\${helpBaseUrl}/g;
|
||||||
|
return helpData.content.replace(helpBaseUrlReg, helpData.helpBaseUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
private processIncludes(content: string): Observable<string> {
|
private processIncludes(content: string): Observable<string> {
|
||||||
@ -96,3 +127,8 @@ export class HelpService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface HelpData {
|
||||||
|
content: string;
|
||||||
|
helpBaseUrl: string;
|
||||||
|
}
|
||||||
|
|||||||
@ -58,11 +58,11 @@ return details;
|
|||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
More details about Alarms can be found in [this tutorial{:target="_blank"}](${baseUrl}/docs/user-guide/alarms/).
|
More details about Alarms can be found in [this tutorial{:target="_blank"}](${siteBaseUrl}/docs/user-guide/alarms/).
|
||||||
|
|
||||||
You can see the real life example, where this node is used, in the next tutorial:
|
You can see the real life example, where this node is used, in the next tutorial:
|
||||||
|
|
||||||
- [Create and Clear Alarms{:target="_blank"}](${baseUrl}/docs/user-guide/rule-engine-2-0/tutorials/create-clear-alarms/)
|
- [Create and Clear Alarms{:target="_blank"}](${siteBaseUrl}/docs/user-guide/rule-engine-2-0/tutorials/create-clear-alarms/)
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
@ -59,11 +59,11 @@ return details;
|
|||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
More details about Alarms can be found in [this tutorial{:target="_blank"}](${baseUrl}/docs/user-guide/alarms/).
|
More details about Alarms can be found in [this tutorial{:target="_blank"}](${siteBaseUrl}/docs/user-guide/alarms/).
|
||||||
|
|
||||||
You can see the real life example, where this node is used, in the next tutorial:
|
You can see the real life example, where this node is used, in the next tutorial:
|
||||||
|
|
||||||
- [Create and Clear Alarms{:target="_blank"}](${baseUrl}/docs/user-guide/rule-engine-2-0/tutorials/create-clear-alarms/)
|
- [Create and Clear Alarms{:target="_blank"}](${siteBaseUrl}/docs/user-guide/rule-engine-2-0/tutorials/create-clear-alarms/)
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
@ -61,8 +61,8 @@ return false;
|
|||||||
|
|
||||||
You can see real life example, how to use this node in those tutorials:
|
You can see real life example, how to use this node in those tutorials:
|
||||||
|
|
||||||
- [Create and Clear Alarms{:target="_blank"}](${baseUrl}/docs/user-guide/rule-engine-2-0/tutorials/create-clear-alarms/#node-a-filter-script)
|
- [Create and Clear Alarms{:target="_blank"}](${siteBaseUrl}/docs/user-guide/rule-engine-2-0/tutorials/create-clear-alarms/#node-a-filter-script)
|
||||||
- [Reply to RPC Calls{:target="_blank"}](${baseUrl}/docs/user-guide/rule-engine-2-0/tutorials/rpc-reply-tutorial#add-filter-script-node)
|
- [Reply to RPC Calls{:target="_blank"}](${siteBaseUrl}/docs/user-guide/rule-engine-2-0/tutorials/rpc-reply-tutorial#add-filter-script-node)
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
@ -31,7 +31,7 @@ return 'Incoming message:\n' + JSON.stringify(msg) +
|
|||||||
|
|
||||||
You can see real life example, how to use this node in this tutorial:
|
You can see real life example, how to use this node in this tutorial:
|
||||||
|
|
||||||
- [Reply to RPC Calls{:target="_blank"}](${baseUrl}/docs/user-guide/rule-engine-2-0/tutorials/rpc-reply-tutorial#log-unknown-request)
|
- [Reply to RPC Calls{:target="_blank"}](${siteBaseUrl}/docs/user-guide/rule-engine-2-0/tutorials/rpc-reply-tutorial#log-unknown-request)
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
@ -90,7 +90,7 @@ return [];
|
|||||||
|
|
||||||
You can see real life example, how to use this node in this tutorial:
|
You can see real life example, how to use this node in this tutorial:
|
||||||
|
|
||||||
- [Data function based on telemetry from 2 devices{:target="_blank"}](${baseUrl}/docs/user-guide/rule-engine-2-0/tutorials/function-based-on-telemetry-from-two-devices#delta-temperature-rule-chain)
|
- [Data function based on telemetry from 2 devices{:target="_blank"}](${siteBaseUrl}/docs/user-guide/rule-engine-2-0/tutorials/function-based-on-telemetry-from-two-devices#delta-temperature-rule-chain)
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
@ -52,8 +52,8 @@ return {msg: msg, metadata: metadata, msgType: newType};
|
|||||||
|
|
||||||
You can see real life example, how to use this node in those tutorials:
|
You can see real life example, how to use this node in those tutorials:
|
||||||
|
|
||||||
- [Transform incoming telemetry{:target="_blank"}](${baseUrl}/docs/user-guide/rule-engine-2-0/tutorials/transform-incoming-telemetry/)
|
- [Transform incoming telemetry{:target="_blank"}](${siteBaseUrl}/docs/user-guide/rule-engine-2-0/tutorials/transform-incoming-telemetry/)
|
||||||
- [Reply to RPC Calls{:target="_blank"}](${baseUrl}/docs/user-guide/rule-engine-2-0/tutorials/rpc-reply-tutorial#add-transform-script-node)
|
- [Reply to RPC Calls{:target="_blank"}](${siteBaseUrl}/docs/user-guide/rule-engine-2-0/tutorials/rpc-reply-tutorial#add-transform-script-node)
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
@ -20,6 +20,7 @@ A JavaScript function performing custom action.
|
|||||||
* Display alert dialog with entity information:
|
* Display alert dialog with entity information:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
{:code-style="max-height: 300px;"}
|
||||||
var title;
|
var title;
|
||||||
var content;
|
var content;
|
||||||
if (entityName) {
|
if (entityName) {
|
||||||
@ -52,6 +53,7 @@ function showAlertDialog(title, content) {
|
|||||||
* Delete device after confirmation:
|
* Delete device after confirmation:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
{:code-style="max-height: 300px;"}
|
||||||
var $injector = widgetContext.$scope.$injector;
|
var $injector = widgetContext.$scope.$injector;
|
||||||
var dialogs = $injector.get(widgetContext.servicesMap.get('dialogs'));
|
var dialogs = $injector.get(widgetContext.servicesMap.get('dialogs'));
|
||||||
var deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));
|
var deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
#### HTML template of dialog to create a device or an asset
|
#### HTML template of dialog to create a device or an asset
|
||||||
|
|
||||||
```html
|
```html
|
||||||
|
{:code-style="max-height: 400px;"}
|
||||||
<form #addEntityForm="ngForm" [formGroup]="addEntityFormGroup"
|
<form #addEntityForm="ngForm" [formGroup]="addEntityFormGroup"
|
||||||
(ngSubmit)="save()" class="add-entity-form">
|
(ngSubmit)="save()" class="add-entity-form">
|
||||||
<mat-toolbar fxLayout="row" color="primary">
|
<mat-toolbar fxLayout="row" color="primary">
|
||||||
@ -158,3 +159,6 @@
|
|||||||
</form>
|
</form>
|
||||||
{:copy-code}
|
{:copy-code}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
#### Function displaying dialog to create a device or an asset
|
#### Function displaying dialog to create a device or an asset
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
{:code-style="max-height: 400px;"}
|
||||||
let $injector = widgetContext.$scope.$injector;
|
let $injector = widgetContext.$scope.$injector;
|
||||||
let customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));
|
let customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));
|
||||||
let assetService = $injector.get(widgetContext.servicesMap.get('assetService'));
|
let assetService = $injector.get(widgetContext.servicesMap.get('assetService'));
|
||||||
@ -130,3 +131,6 @@ function AddEntityDialogController(instance) {
|
|||||||
}
|
}
|
||||||
{:copy-code}
|
{:copy-code}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
#### HTML template of dialog to edit a device or an asset
|
#### HTML template of dialog to edit a device or an asset
|
||||||
|
|
||||||
```html
|
```html
|
||||||
|
{:code-style="max-height: 400px;"}
|
||||||
<form #editEntityForm="ngForm" [formGroup]="editEntityFormGroup"
|
<form #editEntityForm="ngForm" [formGroup]="editEntityFormGroup"
|
||||||
(ngSubmit)="save()" class="edit-entity-form">
|
(ngSubmit)="save()" class="edit-entity-form">
|
||||||
<mat-toolbar fxLayout="row" color="primary">
|
<mat-toolbar fxLayout="row" color="primary">
|
||||||
@ -190,3 +191,6 @@
|
|||||||
</form>
|
</form>
|
||||||
{:copy-code}
|
{:copy-code}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
#### Function displaying dialog to edit a device or an asset
|
#### Function displaying dialog to edit a device or an asset
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
{:code-style="max-height: 400px;"}
|
||||||
let $injector = widgetContext.$scope.$injector;
|
let $injector = widgetContext.$scope.$injector;
|
||||||
let customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));
|
let customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));
|
||||||
let entityService = $injector.get(widgetContext.servicesMap.get('entityService'));
|
let entityService = $injector.get(widgetContext.servicesMap.get('entityService'));
|
||||||
@ -218,3 +219,6 @@ function EditEntityDialogController(instance) {
|
|||||||
}
|
}
|
||||||
{:copy-code}
|
{:copy-code}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
|||||||
@ -34,7 +34,7 @@ function showQrCodeDialog(title, code, format) {
|
|||||||
{:copy-code}
|
{:copy-code}
|
||||||
```
|
```
|
||||||
|
|
||||||
* Parse code as a device claiming info (in this case ```{deviceName: string, secretKey: string}```)<br>and then claim device (see [Claiming devices{:target="_blank"}](${baseUrl}/docs/user-guide/claiming-devices/) for details):
|
* Parse code as a device claiming info (in this case ```{deviceName: string, secretKey: string}```)<br>and then claim device (see [Claiming devices{:target="_blank"}](${siteBaseUrl}/docs/user-guide/claiming-devices/) for details):
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
var $scope = widgetContext.$scope;
|
var $scope = widgetContext.$scope;
|
||||||
|
|||||||
@ -127,7 +127,7 @@ self.onDataUpdated = function() {
|
|||||||
|
|
||||||
- Click the **Run** button on the **Widget Editor Toolbar** in order to see the result in **Widget preview** section.
|
- Click the **Run** button on the **Widget Editor Toolbar** in order to see the result in **Widget preview** section.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
In this example, the **alarmSource** and **alarms** properties of <span trigger-style="fontSize: 16px;" trigger-text="<b>subscription</b>" tb-help-popup="widget/editor/widget_js_subscription_object"></span> are assigned to **$scope** and become accessible within HTML template.
|
In this example, the **alarmSource** and **alarms** properties of <span trigger-style="fontSize: 16px;" trigger-text="<b>subscription</b>" tb-help-popup="widget/editor/widget_js_subscription_object"></span> are assigned to **$scope** and become accessible within HTML template.
|
||||||
|
|
||||||
|
|||||||
@ -57,7 +57,7 @@ self.onDataUpdated = function() {
|
|||||||
|
|
||||||
- Click the **Run** button on the **Widget Editor Toolbar** in order to see the result in **Widget preview** section.
|
- Click the **Run** button on the **Widget Editor Toolbar** in order to see the result in **Widget preview** section.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
In this example, the external JS library API was used that becomes available after injecting the corresponding URL in **Resources** section.
|
In this example, the external JS library API was used that becomes available after injecting the corresponding URL in **Resources** section.
|
||||||
|
|
||||||
|
|||||||
@ -98,7 +98,7 @@ self.onDataUpdated = function() {
|
|||||||
|
|
||||||
- Click the **Run** button on the **Widget Editor Toolbar** in order to see the result in **Widget preview** section.
|
- Click the **Run** button on the **Widget Editor Toolbar** in order to see the result in **Widget preview** section.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
In this example, the external JS library API was used that becomes available after injecting the corresponding URL in **Resources** section.
|
In this example, the external JS library API was used that becomes available after injecting the corresponding URL in **Resources** section.
|
||||||
|
|
||||||
|
|||||||
@ -37,7 +37,7 @@ The **Widget Editor** will open, pre-populated with the content of the default *
|
|||||||
|
|
||||||
- Click the **Run** button on the **Widget Editor Toolbar** in order to see the result in **Widget preview** section.
|
- Click the **Run** button on the **Widget Editor Toolbar** in order to see the result in **Widget preview** section.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
In this example, the **data** property of <span trigger-style="fontSize: 16px;" trigger-text="<b>subscription</b>" tb-help-popup="widget/editor/widget_js_subscription_object"></span> is assigned to the **$scope** and becomes accessible within the HTML template.
|
In this example, the **data** property of <span trigger-style="fontSize: 16px;" trigger-text="<b>subscription</b>" tb-help-popup="widget/editor/widget_js_subscription_object"></span> is assigned to the **$scope** and becomes accessible within the HTML template.
|
||||||
|
|
||||||
|
|||||||
@ -114,7 +114,7 @@ self.onInit = function() {
|
|||||||
- Click the **Run** button on the **Widget Editor Toolbar** in order to see the result in **Widget preview** section.
|
- Click the **Run** button on the **Widget Editor Toolbar** in order to see the result in **Widget preview** section.
|
||||||
- Click dashboard edit button on the preview section to change the size of the resulting widget. Then click dashboard apply button. The final widget should look like the image below.
|
- Click dashboard edit button on the preview section to change the size of the resulting widget. Then click dashboard apply button. The final widget should look like the image below.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
- Click the **Save** button on the **Widget Editor Toolbar** to save widget type.
|
- Click the **Save** button on the **Widget Editor Toolbar** to save widget type.
|
||||||
|
|
||||||
@ -123,13 +123,13 @@ To test how this widget performs RPC commands, we will need to place it in a das
|
|||||||
- Login as Tenant administrator.
|
- Login as Tenant administrator.
|
||||||
- Navigate to **Devices** and create new device with some name, for ex. "My RPC Device".
|
- Navigate to **Devices** and create new device with some name, for ex. "My RPC Device".
|
||||||
- Open device details and click "Copy Access Token" button to copy device access token to clipboard.
|
- Open device details and click "Copy Access Token" button to copy device access token to clipboard.
|
||||||
- Download [mqtt-js-rpc-from-server.sh{:target="_blank"}](${baseUrl}/docs/reference/resources/mqtt-js-rpc-from-server.sh) and [mqtt-js-rpc-from-server.js{:target="_blank"}](${baseUrl}/docs/reference/resources/mqtt-js-rpc-from-server.js). Place these files in a folder.
|
- Download [mqtt-js-rpc-from-server.sh{:target="_blank"}](${siteBaseUrl}/docs/reference/resources/mqtt-js-rpc-from-server.sh) and [mqtt-js-rpc-from-server.js{:target="_blank"}](${siteBaseUrl}/docs/reference/resources/mqtt-js-rpc-from-server.js). Place these files in a folder.
|
||||||
Edit **mqtt-js-rpc-from-server.sh** - replace **$ACCESS_TOKEN** with your device access token from the clipboard. And install mqtt client library.
|
Edit **mqtt-js-rpc-from-server.sh** - replace **$ACCESS_TOKEN** with your device access token from the clipboard. And install mqtt client library.
|
||||||
- Run **mqtt-js-rpc-from-server.sh** script. You should see a "connected" message in the console.
|
- Run **mqtt-js-rpc-from-server.sh** script. You should see a "connected" message in the console.
|
||||||
- Navigate to **Dashboards** and create a new dashboard with some name, for ex. "My first control dashboard". Open this dashboard.
|
- Navigate to **Dashboards** and create a new dashboard with some name, for ex. "My first control dashboard". Open this dashboard.
|
||||||
- Click dashboard "edit" button. In the dashboard edit mode, click the "Entity aliases" button located on the dashboard toolbar.
|
- Click dashboard "edit" button. In the dashboard edit mode, click the "Entity aliases" button located on the dashboard toolbar.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
- Inside **Entity aliases** popup click "Add alias".
|
- Inside **Entity aliases** popup click "Add alias".
|
||||||
- Fill "Alias name" field, for ex. "My RPC Device Alias".
|
- Fill "Alias name" field, for ex. "My RPC Device Alias".
|
||||||
@ -137,12 +137,12 @@ To test how this widget performs RPC commands, we will need to place it in a das
|
|||||||
- Choose "Device" in "Type" field.
|
- Choose "Device" in "Type" field.
|
||||||
- Select your device in "Entity list" field. In this example "My RPC Device".
|
- Select your device in "Entity list" field. In this example "My RPC Device".
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
- Click "Add" and then "Save" in **Entity aliases**.
|
- Click "Add" and then "Save" in **Entity aliases**.
|
||||||
- Click dashboard "+" button then click "Create new widget" button.
|
- Click dashboard "+" button then click "Create new widget" button.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
- Then select **Widget Bundle** where your RPC widget was saved. Select "Control widget" tab.
|
- Then select **Widget Bundle** where your RPC widget was saved. Select "Control widget" tab.
|
||||||
- Click your widget. In this example, "My first control widget".
|
- Click your widget. In this example, "My first control widget".
|
||||||
@ -152,7 +152,7 @@ To test how this widget performs RPC commands, we will need to place it in a das
|
|||||||
- Fill **RPC params** field with RPC params. For ex. "{ param1: "value1" }".
|
- Fill **RPC params** field with RPC params. For ex. "{ param1: "value1" }".
|
||||||
- Click **Send RPC command** button. You should see the following response in the widget.
|
- Click **Send RPC command** button. You should see the following response in the widget.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
The following output should be printed in the device console:
|
The following output should be printed in the device console:
|
||||||
|
|
||||||
@ -166,18 +166,18 @@ In order to test "Two way" RPC command mode, we need to change the corresponding
|
|||||||
- Click dashboard "edit" button. In dashboard edit mode, click **Edit widget** button located in the header of Control widget.
|
- Click dashboard "edit" button. In dashboard edit mode, click **Edit widget** button located in the header of Control widget.
|
||||||
- In the widget details, view select "Advanced" tab and uncheck "Is One Way Command" checkbox.
|
- In the widget details, view select "Advanced" tab and uncheck "Is One Way Command" checkbox.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
- Click **Apply changes** button on the widget details header. Close details and click dashboard **Apply changes** button.
|
- Click **Apply changes** button on the widget details header. Close details and click dashboard **Apply changes** button.
|
||||||
- Fill widget fields with RPC method name and params like in previous steps.
|
- Fill widget fields with RPC method name and params like in previous steps.
|
||||||
Click **Send RPC command** button. You should see the following response in the widget.
|
Click **Send RPC command** button. You should see the following response in the widget.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
- stop **mqtt-js-rpc-from-server.sh** script.
|
- stop **mqtt-js-rpc-from-server.sh** script.
|
||||||
Click **Send RPC command** button. You should see the following response in the widget.
|
Click **Send RPC command** button. You should see the following response in the widget.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
In this example, **controlApi** is used to send RPC commands. Additionally, custom widget settings were introduced in order to configure RPC command mode and RPC request timeout.
|
In this example, **controlApi** is used to send RPC commands. Additionally, custom widget settings were introduced in order to configure RPC command mode and RPC request timeout.
|
||||||
|
|
||||||
|
|||||||
@ -58,7 +58,7 @@ self.onInit = function() {
|
|||||||
|
|
||||||
- Click the **Run** button on the **Widget Editor Toolbar** to see the resulting **Widget preview** section.
|
- Click the **Run** button on the **Widget Editor Toolbar** to see the resulting **Widget preview** section.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
This is just a static HTML widget. There is no subscription data and no special widget API was used.
|
This is just a static HTML widget. There is no subscription data and no special widget API was used.
|
||||||
|
|
||||||
|
|||||||
@ -75,7 +75,7 @@ self.onDataUpdated = function() {
|
|||||||
|
|
||||||
- Click the **Run** button on the **Widget Editor Toolbar** in order to see the result in **Widget preview** section.
|
- Click the **Run** button on the **Widget Editor Toolbar** in order to see the result in **Widget preview** section.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
In this example, the <span trigger-style="fontSize: 16px;" trigger-text="<b>subscription</b>" tb-help-popup="widget/editor/widget_js_subscription_object"></span> **datasources** and **data** properties are assigned to **$scope** and become accessible within the HTML template.
|
In this example, the <span trigger-style="fontSize: 16px;" trigger-text="<b>subscription</b>" tb-help-popup="widget/editor/widget_js_subscription_object"></span> **datasources** and **data** properties are assigned to **$scope** and become accessible within the HTML template.
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
All widget related JavaScript code according to the [Widget API{:target="_blank"}](${baseUrl}/docs/user-guide/contribution/widgets-development/#basic-widget-api).
|
All widget related JavaScript code according to the [Widget API{:target="_blank"}](${siteBaseUrl}/docs/user-guide/contribution/widgets-development/#basic-widget-api).
|
||||||
The built-in variable **self** is a reference to the widget instance.<br>
|
The built-in variable **self** is a reference to the widget instance.<br>
|
||||||
Each widget function should be defined as a property of the **self** variable.
|
Each widget function should be defined as a property of the **self** variable.
|
||||||
**self** variable has property **ctx** of type [WidgetContext{:target="_blank"}](https://github.com/thingsboard/thingsboard/blob/5bb6403407aa4898084832d6698aa9ea6d484889/ui-ngx/src/app/modules/home/models/widget-component.models.ts#L107) - a reference to widget context that has all necessary API and data used by widget instance.
|
**self** variable has property **ctx** of type [WidgetContext{:target="_blank"}](https://github.com/thingsboard/thingsboard/blob/5bb6403407aa4898084832d6698aa9ea6d484889/ui-ngx/src/app/modules/home/models/widget-component.models.ts#L107) - a reference to widget context that has all necessary API and data used by widget instance.
|
||||||
@ -129,7 +129,7 @@ Browser debugger (if enabled) will automatically pause code execution at the deb
|
|||||||
|
|
||||||
##### Further reading
|
##### Further reading
|
||||||
|
|
||||||
For more information read [Widgets Development Guide{:target="_blank"}](${baseUrl}/docs/user-guide/contribution/widgets-development).
|
For more information read [Widgets Development Guide{:target="_blank"}](${siteBaseUrl}/docs/user-guide/contribution/widgets-development).
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
|
|||||||
@ -3,10 +3,10 @@
|
|||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
The widget subscription object is instance of [IWidgetSubscription{:target="_blank"}](https://github.com/thingsboard/thingsboard/blob/2627fe51d491055d4140f16617ed543f7f5bd8f6/ui-ngx/src/app/core/api/widget-api.models.ts#L264") and contains all subscription information, including current data, according to the [widget type{:target="_blank"}](${baseUrl}/docs/user-guide/ui/widget-library/#widget-types).
|
The widget subscription object is instance of [IWidgetSubscription{:target="_blank"}](https://github.com/thingsboard/thingsboard/blob/2627fe51d491055d4140f16617ed543f7f5bd8f6/ui-ngx/src/app/core/api/widget-api.models.ts#L264") and contains all subscription information, including current data, according to the [widget type{:target="_blank"}](${siteBaseUrl}/docs/user-guide/ui/widget-library/#widget-types).
|
||||||
|
|
||||||
Depending on widget type, subscription object provides different data structures.
|
Depending on widget type, subscription object provides different data structures.
|
||||||
For [Latest values{:target="_blank"}](${baseUrl}/docs/user-guide/ui/widget-library/#latest-values) and [Time-series{:target="_blank"}](${baseUrl}/docs/user-guide/ui/widget-library/#time-series) widget types, it provides the following properties:
|
For [Latest values{:target="_blank"}](${siteBaseUrl}/docs/user-guide/ui/widget-library/#latest-values) and [Time-series{:target="_blank"}](${siteBaseUrl}/docs/user-guide/ui/widget-library/#time-series) widget types, it provides the following properties:
|
||||||
|
|
||||||
- **datasources** - array of datasources (Array<[Datasource{:target="_blank"}](https://github.com/thingsboard/thingsboard/blob/2627fe51d491055d4140f16617ed543f7f5bd8f6/ui-ngx/src/app/shared/models/widget.models.ts#L279)>) used by this subscription, using the following structure:
|
- **datasources** - array of datasources (Array<[Datasource{:target="_blank"}](https://github.com/thingsboard/thingsboard/blob/2627fe51d491055d4140f16617ed543f7f5bd8f6/ui-ngx/src/app/shared/models/widget.models.ts#L279)>) used by this subscription, using the following structure:
|
||||||
|
|
||||||
@ -54,7 +54,7 @@ For [Latest values{:target="_blank"}](${baseUrl}/docs/user-guide/ui/widget-libra
|
|||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
For [Alarm widget{:target="_blank"}](${baseUrl}/docs/user-guide/ui/widget-library/#alarm-widget) type it provides the following properties:
|
For [Alarm widget{:target="_blank"}](${siteBaseUrl}/docs/user-guide/ui/widget-library/#alarm-widget) type it provides the following properties:
|
||||||
|
|
||||||
- **alarmSource** - ([Datasource{:target="_blank"}](https://github.com/thingsboard/thingsboard/blob/2627fe51d491055d4140f16617ed543f7f5bd8f6/ui-ngx/src/app/shared/models/widget.models.ts#L279)) information about entity for which alarms are fetched, using the following structure:
|
- **alarmSource** - ([Datasource{:target="_blank"}](https://github.com/thingsboard/thingsboard/blob/2627fe51d491055d4140f16617ed543f7f5bd8f6/ui-ngx/src/app/shared/models/widget.models.ts#L279)) information about entity for which alarms are fetched, using the following structure:
|
||||||
|
|
||||||
@ -110,4 +110,4 @@ For [Alarm widget{:target="_blank"}](${baseUrl}/docs/user-guide/ui/widget-librar
|
|||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
For [RPC{:target="_blank"}](${baseUrl}/docs/user-guide/ui/widget-library/#rpc-control-widget) or [Static{:target="_blank"}](${baseUrl}/docs/user-guide/ui/widget-library/#static) widget types, subscription object is optional and does not contain necessary information.
|
For [RPC{:target="_blank"}](${siteBaseUrl}/docs/user-guide/ui/widget-library/#rpc-control-widget) or [Static{:target="_blank"}](${siteBaseUrl}/docs/user-guide/ui/widget-library/#static) widget types, subscription object is optional and does not contain necessary information.
|
||||||
|
|||||||
@ -33,7 +33,7 @@ return '# Some title\n - Entity name: ' + data[0]['entityName'];
|
|||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
Display greetings for currently logged-in user.<br>
|
Display greetings for currently logged-in user.<br>
|
||||||
Let's assume widget has first datasource configured using <code>Current User</code> <a target="_blank" href="${baseUrl}/docs/user-guide/ui/aliases/#single-entity">Single entity</a> alias<br>
|
Let's assume widget has first datasource configured using <code>Current User</code> <a target="_blank" href="${siteBaseUrl}/docs/user-guide/ui/aliases/#single-entity">Single entity</a> alias<br>
|
||||||
and has data keys for <code>firstName</code>, <code>lastName</code> and <code>name</code> entity fields:
|
and has data keys for <code>firstName</code>, <code>lastName</code> and <code>name</code> entity fields:
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@ -34,7 +34,7 @@ return data[0] ? data[0]['entityName'] : '';
|
|||||||
<li>
|
<li>
|
||||||
Prepare QR code text to use as device claiming info (in this case <code>{deviceName: string, secretKey: string}</code>).<br>
|
Prepare QR code text to use as device claiming info (in this case <code>{deviceName: string, secretKey: string}</code>).<br>
|
||||||
Let's assume device has <code>claimingData</code> attribute with string JSON value containing <code>secretKey</code> field<br>
|
Let's assume device has <code>claimingData</code> attribute with string JSON value containing <code>secretKey</code> field<br>
|
||||||
(see <a target="_blank" href="${baseUrl}/docs/user-guide/claiming-devices/">Claiming devices</a>):
|
(see <a target="_blank" href="${siteBaseUrl}/docs/user-guide/claiming-devices/">Claiming devices</a>):
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|||||||
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 9.7 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 9.2 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 28 KiB |