Commit 641db71ce6b834ec7c194b94a560e32b25da45a2

Authored by Igor Kulikov
1 parent 9e9072de

Configure UI help assets base url.

Showing 46 changed files with 201 additions and 58 deletions
... ... @@ -96,7 +96,7 @@ public class DashboardController extends BaseController {
96 96 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).";
97 97 public static final String HIDDEN_FOR_MOBILE = "Exclude dashboards that are hidden for mobile";
98 98
99   - @Value("${dashboard.max_datapoints_limit}")
  99 + @Value("${ui.dashboard.max_datapoints_limit}")
100 100 private long maxDatapointsLimit;
101 101
102 102 @ApiOperation(value = "Get server time (getServerTime)",
... ...
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.controller;
  17 +
  18 +import io.swagger.annotations.ApiOperation;
  19 +import org.springframework.beans.factory.annotation.Value;
  20 +import org.springframework.security.access.prepost.PreAuthorize;
  21 +import org.springframework.web.bind.annotation.RequestMapping;
  22 +import org.springframework.web.bind.annotation.RequestMethod;
  23 +import org.springframework.web.bind.annotation.ResponseBody;
  24 +import org.springframework.web.bind.annotation.RestController;
  25 +import org.thingsboard.server.common.data.exception.ThingsboardException;
  26 +import org.thingsboard.server.queue.util.TbCoreComponent;
  27 +
  28 +@RestController
  29 +@TbCoreComponent
  30 +@RequestMapping("/api")
  31 +public class UiSettingsController extends BaseController {
  32 +
  33 + @Value("${ui.help.base-url}")
  34 + private String helpBaseUrl;
  35 +
  36 + @ApiOperation(value = "Get UI help base url (getHelpBaseUrl)",
  37 + notes = "Get UI help base url used to fetch help assets. " +
  38 + "The actual value of the base url is configurable in the system configuration file.")
  39 + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
  40 + @RequestMapping(value = "/uiSettings/helpBaseUrl", method = RequestMethod.GET)
  41 + @ResponseBody
  42 + public String getHelpBaseUrl() throws ThingsboardException {
  43 + return helpBaseUrl;
  44 + }
  45 +
  46 +}
... ...
... ... @@ -129,10 +129,16 @@ usage:
129 129 check:
130 130 cycle: "${USAGE_STATS_CHECK_CYCLE:60000}"
131 131
132   -# Dashboard parameters
133   -dashboard:
134   - # Maximum allowed datapoints fetched by widgets
135   - max_datapoints_limit: "${DASHBOARD_MAX_DATAPOINTS_LIMIT:50000}"
  132 +# UI parameters
  133 +ui:
  134 + # Dashboard parameters
  135 + dashboard:
  136 + # Maximum allowed datapoints fetched by widgets
  137 + max_datapoints_limit: "${DASHBOARD_MAX_DATAPOINTS_LIMIT:50000}"
  138 + # Help parameters
  139 + help:
  140 + # Base url for UI help assets
  141 + base-url: "${UI_HELP_BASE_URL:https://raw.githubusercontent.com/thingsboard/thingsboard/master/ui-ngx/src/assets}"
136 142
137 143 database:
138 144 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
70 70 acl transport_http_acl path_beg /api/v1/
71 71 acl letsencrypt_http_acl path_beg /.well-known/acme-challenge/
72 72 acl tb_api_acl path_beg /api/ /swagger /webjars /v2/ /static/rulenode/ /oauth2/ /login/oauth2/ /static/widgets/
73   - acl tb_rulenode_assets path_reg ^/assets/help/.*/rulenode/.*$
74 73
75 74 redirect scheme https if !letsencrypt_http_acl !transport_http_acl { env(FORCE_HTTPS_REDIRECT) -m str true }
76 75
77 76 use_backend letsencrypt_http if letsencrypt_http_acl
78 77 use_backend tb-http-backend if transport_http_acl
79   - use_backend tb-api-backend if tb_api_acl or tb_rulenode_assets
  78 + use_backend tb-api-backend if tb_api_acl
80 79
81 80 default_backend tb-web-backend
82 81
... ... @@ -89,10 +88,9 @@ frontend https_in
89 88
90 89 acl transport_http_acl path_beg /api/v1/
91 90 acl tb_api_acl path_beg /api/ /swagger /webjars /v2/ /static/rulenode/ /oauth2/ /login/oauth2/ /static/widgets/
92   - acl tb_rulenode_assets path_reg ^/assets/help/.*/rulenode/.*$
93 91
94 92 use_backend tb-http-backend if transport_http_acl
95   - use_backend tb-api-backend if tb_api_acl or tb_rulenode_assets
  93 + use_backend tb-api-backend if tb_api_acl
96 94
97 95 default_backend tb-web-backend
98 96
... ...
... ... @@ -26,10 +26,6 @@ const PROXY_CONFIG = {
26 26 "target": ruleNodeUiforwardUrl,
27 27 "secure": false,
28 28 },
29   - "/assets/help/*/rulenode/**": {
30   - "target": ruleNodeUiforwardUrl,
31   - "secure": false,
32   - },
33 29 "/static/widgets": {
34 30 "target": forwardUrl,
35 31 "secure": false,
... ...
  1 +///
  2 +/// Copyright © 2016-2021 The Thingsboard Authors
  3 +///
  4 +/// Licensed under the Apache License, Version 2.0 (the "License");
  5 +/// you may not use this file except in compliance with the License.
  6 +/// You may obtain a copy of the License at
  7 +///
  8 +/// http://www.apache.org/licenses/LICENSE-2.0
  9 +///
  10 +/// Unless required by applicable law or agreed to in writing, software
  11 +/// distributed under the License is distributed on an "AS IS" BASIS,
  12 +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +/// See the License for the specific language governing permissions and
  14 +/// limitations under the License.
  15 +///
  16 +
  17 +import { Injectable } from '@angular/core';
  18 +import { HttpClient } from '@angular/common/http';
  19 +import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from '@core/http/http-utils';
  20 +import { Observable } from 'rxjs';
  21 +import { publishReplay, refCount } from 'rxjs/operators';
  22 +
  23 +@Injectable({
  24 + providedIn: 'root'
  25 +})
  26 +export class UiSettingsService {
  27 +
  28 + private helpBaseUrlObservable: Observable<string>;
  29 +
  30 + constructor(
  31 + private http: HttpClient
  32 + ) { }
  33 +
  34 + public getHelpBaseUrl(): Observable<string> {
  35 + if (!this.helpBaseUrlObservable) {
  36 + this.helpBaseUrlObservable = this.http.get('/api/uiSettings/helpBaseUrl', {responseType: 'text', ...defaultHttpOptions(true)}).pipe(
  37 + publishReplay(1),
  38 + refCount()
  39 + );
  40 + }
  41 + return this.helpBaseUrlObservable;
  42 + }
  43 +}
... ...
... ... @@ -18,23 +18,29 @@ import { Injectable } from '@angular/core';
18 18 import { HttpClient } from '@angular/common/http';
19 19 import { TranslateService } from '@ngx-translate/core';
20 20 import { Observable, of } from 'rxjs';
21   -import { catchError, mergeMap, tap } from 'rxjs/operators';
22   -import { helpBaseUrl } from '@shared/models/constants';
  21 +import { catchError, map, mergeMap, tap } from 'rxjs/operators';
  22 +import { helpBaseUrl as siteBaseUrl } from '@shared/models/constants';
  23 +import { UiSettingsService } from '@core/http/ui-settings.service';
23 24
24   -const NOT_FOUND_CONTENT = '## Not found';
  25 +const localHelpBaseUrl = '/assets';
  26 +
  27 +const NOT_FOUND_CONTENT: HelpData = {
  28 + content: '## Not found',
  29 + helpBaseUrl: localHelpBaseUrl
  30 +};
25 31
26 32 @Injectable({
27 33 providedIn: 'root'
28 34 })
29 35 export class HelpService {
30 36
31   - private helpBaseUrl = helpBaseUrl;
32   -
  37 + private siteBaseUrl = siteBaseUrl;
33 38 private helpCache: {[lang: string]: {[key: string]: string}} = {};
34 39
35 40 constructor(
36 41 private translate: TranslateService,
37   - private http: HttpClient
  42 + private http: HttpClient,
  43 + private uiSettingsService: UiSettingsService
38 44 ) {}
39 45
40 46 getHelpContent(key: string): Observable<string> {
... ... @@ -70,13 +76,38 @@ export class HelpService {
70 76 }
71 77 }
72 78
73   - private loadHelpContent(lang: string, key: string): Observable<string> {
74   - return this.http.get(`/assets/help/${lang}/${key}.md`, {responseType: 'text'} );
  79 + private loadHelpContent(lang: string, key: string): Observable<HelpData> {
  80 + return this.uiSettingsService.getHelpBaseUrl().pipe(
  81 + mergeMap((helpBaseUrl) => {
  82 + return this.loadHelpContentFromBaseUrl(helpBaseUrl, lang, key).pipe(
  83 + catchError((e) => {
  84 + if (localHelpBaseUrl !== helpBaseUrl) {
  85 + return this.loadHelpContentFromBaseUrl(localHelpBaseUrl, lang, key);
  86 + } else {
  87 + throw e;
  88 + }
  89 + })
  90 + );
  91 + })
  92 + );
  93 + }
  94 +
  95 + private loadHelpContentFromBaseUrl(helpBaseUrl: string, lang: string, key: string): Observable<HelpData> {
  96 + return this.http.get(`${helpBaseUrl}/help/${lang}/${key}.md`, {responseType: 'text'} ).pipe(
  97 + map((content) => {
  98 + return {
  99 + content,
  100 + helpBaseUrl
  101 + };
  102 + })
  103 + );
75 104 }
76 105
77   - private processVariables(content: string): string {
78   - const baseUrlReg = /\${baseUrl}/g;
79   - return content.replace(baseUrlReg, this.helpBaseUrl);
  106 + private processVariables(helpData: HelpData): string {
  107 + const baseUrlReg = /\${siteBaseUrl}/g;
  108 + helpData.content = helpData.content.replace(baseUrlReg, this.siteBaseUrl);
  109 + const helpBaseUrlReg = /\${helpBaseUrl}/g;
  110 + return helpData.content.replace(helpBaseUrlReg, helpData.helpBaseUrl);
80 111 }
81 112
82 113 private processIncludes(content: string): Observable<string> {
... ... @@ -96,3 +127,8 @@ export class HelpService {
96 127 }
97 128
98 129 }
  130 +
  131 +interface HelpData {
  132 + content: string;
  133 + helpBaseUrl: string;
  134 +}
... ...
ui-ngx/src/assets/help/en_US/rulenode/clear_alarm_node_script_fn.md renamed from rule-engine/rule-engine-components/src/main/resources/public/assets/help/en_US/rulenode/clear_alarm_node_script_fn.md
... ... @@ -58,11 +58,11 @@ return details;
58 58
59 59 <br>
60 60
61   -More details about Alarms can be found in [this tutorial{:target="_blank"}](${baseUrl}/docs/user-guide/alarms/).
  61 +More details about Alarms can be found in [this tutorial{:target="_blank"}](${siteBaseUrl}/docs/user-guide/alarms/).
62 62
63 63 You can see the real life example, where this node is used, in the next tutorial:
64 64
65   -- [Create and Clear Alarms{:target="_blank"}](${baseUrl}/docs/user-guide/rule-engine-2-0/tutorials/create-clear-alarms/)
  65 +- [Create and Clear Alarms{:target="_blank"}](${siteBaseUrl}/docs/user-guide/rule-engine-2-0/tutorials/create-clear-alarms/)
66 66
67 67 <br>
68 68 <br>
... ...
ui-ngx/src/assets/help/en_US/rulenode/common_node_script_args.md renamed from rule-engine/rule-engine-components/src/main/resources/public/assets/help/en_US/rulenode/common_node_script_args.md
ui-ngx/src/assets/help/en_US/rulenode/create_alarm_node_script_fn.md renamed from rule-engine/rule-engine-components/src/main/resources/public/assets/help/en_US/rulenode/create_alarm_node_script_fn.md
... ... @@ -59,11 +59,11 @@ return details;
59 59
60 60 <br>
61 61
62   -More details about Alarms can be found in [this tutorial{:target="_blank"}](${baseUrl}/docs/user-guide/alarms/).
  62 +More details about Alarms can be found in [this tutorial{:target="_blank"}](${siteBaseUrl}/docs/user-guide/alarms/).
63 63
64 64 You can see the real life example, where this node is used, in the next tutorial:
65 65
66   -- [Create and Clear Alarms{:target="_blank"}](${baseUrl}/docs/user-guide/rule-engine-2-0/tutorials/create-clear-alarms/)
  66 +- [Create and Clear Alarms{:target="_blank"}](${siteBaseUrl}/docs/user-guide/rule-engine-2-0/tutorials/create-clear-alarms/)
67 67
68 68 <br>
69 69 <br>
... ...
ui-ngx/src/assets/help/en_US/rulenode/filter_node_script_fn.md renamed from rule-engine/rule-engine-components/src/main/resources/public/assets/help/en_US/rulenode/filter_node_script_fn.md
... ... @@ -61,8 +61,8 @@ return false;
61 61
62 62 You can see real life example, how to use this node in those tutorials:
63 63
64   -- [Create and Clear Alarms{:target="_blank"}](${baseUrl}/docs/user-guide/rule-engine-2-0/tutorials/create-clear-alarms/#node-a-filter-script)
65   -- [Reply to RPC Calls{:target="_blank"}](${baseUrl}/docs/user-guide/rule-engine-2-0/tutorials/rpc-reply-tutorial#add-filter-script-node)
  64 +- [Create and Clear Alarms{:target="_blank"}](${siteBaseUrl}/docs/user-guide/rule-engine-2-0/tutorials/create-clear-alarms/#node-a-filter-script)
  65 +- [Reply to RPC Calls{:target="_blank"}](${siteBaseUrl}/docs/user-guide/rule-engine-2-0/tutorials/rpc-reply-tutorial#add-filter-script-node)
66 66
67 67 <br>
68 68 <br>
... ...
ui-ngx/src/assets/help/en_US/rulenode/generator_node_script_fn.md renamed from rule-engine/rule-engine-components/src/main/resources/public/assets/help/en_US/rulenode/generator_node_script_fn.md
ui-ngx/src/assets/help/en_US/rulenode/log_node_script_fn.md renamed from rule-engine/rule-engine-components/src/main/resources/public/assets/help/en_US/rulenode/log_node_script_fn.md
... ... @@ -31,7 +31,7 @@ return 'Incoming message:\n' + JSON.stringify(msg) +
31 31
32 32 You can see real life example, how to use this node in this tutorial:
33 33
34   -- [Reply to RPC Calls{:target="_blank"}](${baseUrl}/docs/user-guide/rule-engine-2-0/tutorials/rpc-reply-tutorial#log-unknown-request)
  34 +- [Reply to RPC Calls{:target="_blank"}](${siteBaseUrl}/docs/user-guide/rule-engine-2-0/tutorials/rpc-reply-tutorial#log-unknown-request)
35 35
36 36 <br>
37 37 <br>
... ...
ui-ngx/src/assets/help/en_US/rulenode/switch_node_script_fn.md renamed from rule-engine/rule-engine-components/src/main/resources/public/assets/help/en_US/rulenode/switch_node_script_fn.md
... ... @@ -90,7 +90,7 @@ return [];
90 90
91 91 You can see real life example, how to use this node in this tutorial:
92 92
93   -- [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)
  93 +- [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)
94 94
95 95 <br>
96 96 <br>
... ...
ui-ngx/src/assets/help/en_US/rulenode/transformation_node_script_fn.md renamed from rule-engine/rule-engine-components/src/main/resources/public/assets/help/en_US/rulenode/transformation_node_script_fn.md
... ... @@ -52,8 +52,8 @@ return {msg: msg, metadata: metadata, msgType: newType};
52 52
53 53 You can see real life example, how to use this node in those tutorials:
54 54
55   -- [Transform incoming telemetry{:target="_blank"}](${baseUrl}/docs/user-guide/rule-engine-2-0/tutorials/transform-incoming-telemetry/)
56   -- [Reply to RPC Calls{:target="_blank"}](${baseUrl}/docs/user-guide/rule-engine-2-0/tutorials/rpc-reply-tutorial#add-transform-script-node)
  55 +- [Transform incoming telemetry{:target="_blank"}](${siteBaseUrl}/docs/user-guide/rule-engine-2-0/tutorials/transform-incoming-telemetry/)
  56 +- [Reply to RPC Calls{:target="_blank"}](${siteBaseUrl}/docs/user-guide/rule-engine-2-0/tutorials/rpc-reply-tutorial#add-transform-script-node)
57 57
58 58 <br>
59 59 <br>
... ...
... ... @@ -20,6 +20,7 @@ A JavaScript function performing custom action.
20 20 * Display alert dialog with entity information:
21 21
22 22 ```javascript
  23 +{:code-style="max-height: 300px;"}
23 24 var title;
24 25 var content;
25 26 if (entityName) {
... ... @@ -52,6 +53,7 @@ function showAlertDialog(title, content) {
52 53 * Delete device after confirmation:
53 54
54 55 ```javascript
  56 +{:code-style="max-height: 300px;"}
55 57 var $injector = widgetContext.$scope.$injector;
56 58 var dialogs = $injector.get(widgetContext.servicesMap.get('dialogs'));
57 59 var deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));
... ...
1 1 #### HTML template of dialog to create a device or an asset
2 2
3 3 ```html
  4 +{:code-style="max-height: 400px;"}
4 5 <form #addEntityForm="ngForm" [formGroup]="addEntityFormGroup"
5 6 (ngSubmit)="save()" class="add-entity-form">
6 7 <mat-toolbar fxLayout="row" color="primary">
... ... @@ -158,3 +159,6 @@
158 159 </form>
159 160 {:copy-code}
160 161 ```
  162 +
  163 +<br>
  164 +<br>
... ...
1 1 #### Function displaying dialog to create a device or an asset
2 2
3 3 ```javascript
  4 +{:code-style="max-height: 400px;"}
4 5 let $injector = widgetContext.$scope.$injector;
5 6 let customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));
6 7 let assetService = $injector.get(widgetContext.servicesMap.get('assetService'));
... ... @@ -130,3 +131,6 @@ function AddEntityDialogController(instance) {
130 131 }
131 132 {:copy-code}
132 133 ```
  134 +
  135 +<br>
  136 +<br>
... ...
1 1 #### HTML template of dialog to edit a device or an asset
2 2
3 3 ```html
  4 +{:code-style="max-height: 400px;"}
4 5 <form #editEntityForm="ngForm" [formGroup]="editEntityFormGroup"
5 6 (ngSubmit)="save()" class="edit-entity-form">
6 7 <mat-toolbar fxLayout="row" color="primary">
... ... @@ -190,3 +191,6 @@
190 191 </form>
191 192 {:copy-code}
192 193 ```
  194 +
  195 +<br>
  196 +<br>
... ...
1 1 #### Function displaying dialog to edit a device or an asset
2 2
3 3 ```javascript
  4 +{:code-style="max-height: 400px;"}
4 5 let $injector = widgetContext.$scope.$injector;
5 6 let customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));
6 7 let entityService = $injector.get(widgetContext.servicesMap.get('entityService'));
... ... @@ -218,3 +219,6 @@ function EditEntityDialogController(instance) {
218 219 }
219 220 {:copy-code}
220 221 ```
  222 +
  223 +<br>
  224 +<br>
... ...
... ... @@ -34,7 +34,7 @@ function showQrCodeDialog(title, code, format) {
34 34 {:copy-code}
35 35 ```
36 36
37   -* 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):
  37 +* 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):
38 38
39 39 ```javascript
40 40 var $scope = widgetContext.$scope;
... ...
... ... @@ -127,7 +127,7 @@ self.onDataUpdated = function() {
127 127
128 128 - Click the **Run** button on the **Widget Editor Toolbar** in order to see the result in **Widget preview** section.
129 129
130   -![image](${baseUrl}/images/user-guide/contribution/widgets/alarm-widget-sample.png)
  130 +![image](${helpBaseUrl}/help/images/widget/editor/examples/alarm-widget-sample.png)
131 131
132 132 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.
133 133
... ...
... ... @@ -57,7 +57,7 @@ self.onDataUpdated = function() {
57 57
58 58 - Click the **Run** button on the **Widget Editor Toolbar** in order to see the result in **Widget preview** section.
59 59
60   -![image](${baseUrl}/images/user-guide/contribution/widgets/external-js-widget-sample.png)
  60 +![image](${helpBaseUrl}/help/images/widget/editor/examples/external-js-widget-sample.png)
61 61
62 62 In this example, the external JS library API was used that becomes available after injecting the corresponding URL in **Resources** section.
63 63
... ...
... ... @@ -98,7 +98,7 @@ self.onDataUpdated = function() {
98 98
99 99 - Click the **Run** button on the **Widget Editor Toolbar** in order to see the result in **Widget preview** section.
100 100
101   -![image](${baseUrl}/images/user-guide/contribution/widgets/external-js-timeseries-widget-sample.png)
  101 +![image](${helpBaseUrl}/help/images/widget/editor/examples/external-js-timeseries-widget-sample.png)
102 102
103 103 In this example, the external JS library API was used that becomes available after injecting the corresponding URL in **Resources** section.
104 104
... ...
... ... @@ -37,7 +37,7 @@ The **Widget Editor** will open, pre-populated with the content of the default *
37 37
38 38 - Click the **Run** button on the **Widget Editor Toolbar** in order to see the result in **Widget preview** section.
39 39
40   -![image](${baseUrl}/images/user-guide/contribution/widgets/latest-values-widget-sample.png)
  40 +![image](${helpBaseUrl}/help/images/widget/editor/examples/latest-values-widget-sample.png)
41 41
42 42 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.
43 43
... ...
... ... @@ -114,7 +114,7 @@ self.onInit = function() {
114 114 - Click the **Run** button on the **Widget Editor Toolbar** in order to see the result in **Widget preview** section.
115 115 - 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.
116 116
117   -![image](${baseUrl}/images/user-guide/contribution/widgets/control-widget-sample.png)
  117 +![image](${helpBaseUrl}/help/images/widget/editor/examples/control-widget-sample.png)
118 118
119 119 - Click the **Save** button on the **Widget Editor Toolbar** to save widget type.
120 120
... ... @@ -123,13 +123,13 @@ To test how this widget performs RPC commands, we will need to place it in a das
123 123 - Login as Tenant administrator.
124 124 - Navigate to **Devices** and create new device with some name, for ex. "My RPC Device".
125 125 - Open device details and click "Copy Access Token" button to copy device access token to clipboard.
126   -- 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.
  126 +- 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.
127 127 Edit **mqtt-js-rpc-from-server.sh** - replace **$ACCESS_TOKEN** with your device access token from the clipboard. And install mqtt client library.
128 128 - Run **mqtt-js-rpc-from-server.sh** script. You should see a "connected" message in the console.
129 129 - Navigate to **Dashboards** and create a new dashboard with some name, for ex. "My first control dashboard". Open this dashboard.
130 130 - Click dashboard "edit" button. In the dashboard edit mode, click the "Entity aliases" button located on the dashboard toolbar.
131 131
132   -![image](${baseUrl}/images/user-guide/contribution/widgets/dashboard-toolbar-entity-aliases.png)
  132 +![image](${helpBaseUrl}/help/images/widget/editor/examples/dashboard-toolbar-entity-aliases.png)
133 133
134 134 - Inside **Entity aliases** popup click "Add alias".
135 135 - 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
137 137 - Choose "Device" in "Type" field.
138 138 - Select your device in "Entity list" field. In this example "My RPC Device".
139 139
140   -![image](${baseUrl}/images/user-guide/contribution/widgets/add-rpc-device-alias.png)
  140 +![image](${helpBaseUrl}/help/images/widget/editor/examples/add-rpc-device-alias.png)
141 141
142 142 - Click "Add" and then "Save" in **Entity aliases**.
143 143 - Click dashboard "+" button then click "Create new widget" button.
144 144
145   -![image](${baseUrl}/images/user-guide/contribution/widgets/dashboard-create-new-widget-button.png)
  145 +![image](${helpBaseUrl}/help/images/widget/editor/examples/dashboard-create-new-widget-button.png)
146 146
147 147 - Then select **Widget Bundle** where your RPC widget was saved. Select "Control widget" tab.
148 148 - 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
152 152 - Fill **RPC params** field with RPC params. For ex. "{ param1: "value1" }".
153 153 - Click **Send RPC command** button. You should see the following response in the widget.
154 154
155   -![image](${baseUrl}/images/user-guide/contribution/widgets/control-widget-sample-response-one-way.png)
  155 +![image](${helpBaseUrl}/help/images/widget/editor/examples/control-widget-sample-response-one-way.png)
156 156
157 157 The following output should be printed in the device console:
158 158
... ... @@ -166,18 +166,18 @@ In order to test "Two way" RPC command mode, we need to change the corresponding
166 166 - Click dashboard "edit" button. In dashboard edit mode, click **Edit widget** button located in the header of Control widget.
167 167 - In the widget details, view select "Advanced" tab and uncheck "Is One Way Command" checkbox.
168 168
169   -![image](${baseUrl}/images/user-guide/contribution/widgets/control-widget-sample-settings.png)
  169 +![image](${helpBaseUrl}/help/images/widget/editor/examples/control-widget-sample-settings.png)
170 170
171 171 - Click **Apply changes** button on the widget details header. Close details and click dashboard **Apply changes** button.
172 172 - Fill widget fields with RPC method name and params like in previous steps.
173 173 Click **Send RPC command** button. You should see the following response in the widget.
174 174
175   -![image](${baseUrl}/images/user-guide/contribution/widgets/control-widget-sample-response-two-way.png)
  175 +![image](${helpBaseUrl}/help/images/widget/editor/examples/control-widget-sample-response-two-way.png)
176 176
177 177 - stop **mqtt-js-rpc-from-server.sh** script.
178 178 Click **Send RPC command** button. You should see the following response in the widget.
179 179
180   -![image](${baseUrl}/images/user-guide/contribution/widgets/control-widget-sample-response-timeout.png)
  180 +![image](${helpBaseUrl}/help/images/widget/editor/examples/control-widget-sample-response-timeout.png)
181 181
182 182 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.
183 183
... ...
... ... @@ -58,7 +58,7 @@ self.onInit = function() {
58 58
59 59 - Click the **Run** button on the **Widget Editor Toolbar** to see the resulting **Widget preview** section.
60 60
61   -![image](${baseUrl}/images/user-guide/contribution/widgets/static-widget-sample.png)
  61 +![image](${helpBaseUrl}/help/images/widget/editor/examples/static-widget-sample.png)
62 62
63 63 This is just a static HTML widget. There is no subscription data and no special widget API was used.
64 64
... ...
... ... @@ -75,7 +75,7 @@ self.onDataUpdated = function() {
75 75
76 76 - Click the **Run** button on the **Widget Editor Toolbar** in order to see the result in **Widget preview** section.
77 77
78   -![image](${baseUrl}/images/user-guide/contribution/widgets/timeseries-widget-sample.png)
  78 +![image](${helpBaseUrl}/help/images/widget/editor/examples/timeseries-widget-sample.png)
79 79
80 80 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.
81 81
... ...
... ... @@ -3,7 +3,7 @@
3 3 <div class="divider"></div>
4 4 <br/>
5 5
6   -All widget related JavaScript code according to the [Widget API{:target="_blank"}](${baseUrl}/docs/user-guide/contribution/widgets-development/#basic-widget-api).
  6 +All widget related JavaScript code according to the [Widget API{:target="_blank"}](${siteBaseUrl}/docs/user-guide/contribution/widgets-development/#basic-widget-api).
7 7 The built-in variable **self** is a reference to the widget instance.<br>
8 8 Each widget function should be defined as a property of the **self** variable.
9 9 **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
129 129
130 130 ##### Further reading
131 131
132   -For more information read [Widgets Development Guide{:target="_blank"}](${baseUrl}/docs/user-guide/contribution/widgets-development).
  132 +For more information read [Widgets Development Guide{:target="_blank"}](${siteBaseUrl}/docs/user-guide/contribution/widgets-development).
133 133
134 134 <br>
135 135 <br>
... ...
... ... @@ -3,10 +3,10 @@
3 3 <div class="divider"></div>
4 4 <br/>
5 5
6   -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).
  6 +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).
7 7
8 8 Depending on widget type, subscription object provides different data structures.
9   -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:
  9 +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:
10 10
11 11 - **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:
12 12
... ... @@ -54,7 +54,7 @@ For [Latest values{:target="_blank"}](${baseUrl}/docs/user-guide/ui/widget-libra
54 54 ]
55 55 ```
56 56
57   -For [Alarm widget{:target="_blank"}](${baseUrl}/docs/user-guide/ui/widget-library/#alarm-widget) type it provides the following properties:
  57 +For [Alarm widget{:target="_blank"}](${siteBaseUrl}/docs/user-guide/ui/widget-library/#alarm-widget) type it provides the following properties:
58 58
59 59 - **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:
60 60
... ... @@ -110,4 +110,4 @@ For [Alarm widget{:target="_blank"}](${baseUrl}/docs/user-guide/ui/widget-librar
110 110 ]
111 111 ```
112 112
113   -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.
  113 +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'];
33 33 <ul>
34 34 <li>
35 35 Display greetings for currently logged-in user.<br>
36   -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>
  36 +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>
37 37 and has data keys for <code>firstName</code>, <code>lastName</code> and <code>name</code> entity fields:
38 38 </li>
39 39 </ul>
... ...
... ... @@ -34,7 +34,7 @@ return data[0] ? data[0]['entityName'] : '';
34 34 <li>
35 35 Prepare QR code text to use as device claiming info (in this case <code>{deviceName: string, secretKey: string}</code>).<br>
36 36 Let's assume device has <code>claimingData</code> attribute with string JSON value containing <code>secretKey</code> field<br>
37   -(see <a target="_blank" href="${baseUrl}/docs/user-guide/claiming-devices/">Claiming devices</a>):
  37 +(see <a target="_blank" href="${siteBaseUrl}/docs/user-guide/claiming-devices/">Claiming devices</a>):
38 38 </li>
39 39 </ul>
40 40
... ...