Browse Source

UI: Tenant admin home page initial implementation

feature/home-page
Igor Kulikov 3 years ago
parent
commit
bcc5eba8e6
  1. 112
      ui-ngx/src/app/modules/home/components/widget/lib/home-page/getting-started-widget.component.html
  2. 117
      ui-ngx/src/app/modules/home/components/widget/lib/home-page/getting-started-widget.component.scss
  3. 4
      ui-ngx/src/app/modules/home/components/widget/lib/home-page/getting-started-widget.component.ts
  4. 7
      ui-ngx/src/app/modules/home/components/widget/lib/home-page/home-page-widgets.module.ts
  5. 20
      ui-ngx/src/app/modules/home/components/widget/lib/home-page/toggle-header.component.html
  6. 87
      ui-ngx/src/app/modules/home/components/widget/lib/home-page/toggle-header.component.scss
  7. 82
      ui-ngx/src/app/modules/home/components/widget/lib/home-page/toggle-header.component.ts
  8. 15
      ui-ngx/src/app/modules/home/pages/home-links/home-links-routing.module.ts
  9. 667
      ui-ngx/src/app/modules/home/pages/home-links/tenant_admin_home_page.raw
  10. 2
      ui-ngx/src/app/shared/components/markdown.component.html
  11. 127
      ui-ngx/src/app/shared/components/markdown.component.ts
  12. 49
      ui-ngx/src/assets/locale/locale.constant-en_US.json

112
ui-ngx/src/app/modules/home/components/widget/lib/home-page/getting-started-widget.component.html

@ -91,6 +91,118 @@
<mat-icon>description</mat-icon>{{ 'widgets.getting-started.sys-admin.step6.how-to-configure-notifications' | translate }}</a>
</mat-step>
</ng-template>
<ng-template [ngSwitchCase]="authority.TENANT_ADMIN">
<mat-step [completed]="gettingStarted.maxSelectedIndex > -1">
<ng-template matStepLabel>
<div style="width: 100%;" fxLayout="row" fxLayoutAlign="space-between center">
<div translate>widgets.getting-started.tenant-admin.step1.title</div>
<a *ngIf="matStepper.selectedIndex === 0" mat-button color="primary" routerLink="/entities/devices">{{ 'device.devices' | translate }}</a>
</div>
</ng-template>
<div [innerHTML]="'widgets.getting-started.tenant-admin.step1.content' | translate | safe: 'html'"></div>
<a mat-stroked-button color="primary" href="https://thingsboard.io/docs/getting-started-guides/helloworld/#step-1-provision-device" target="_blank">
<mat-icon>description</mat-icon>{{ 'widgets.getting-started.tenant-admin.step1.how-to-create-device' | translate }}</a>
</mat-step>
<mat-step [completed]="gettingStarted.maxSelectedIndex > 0">
<ng-template matStepLabel>
<div style="width: 100%;" fxLayout="row" fxLayoutAlign="space-between center">
<div translate>widgets.getting-started.tenant-admin.step2.title</div>
</div>
</ng-template>
<div [innerHTML]="'widgets.getting-started.tenant-admin.step2.content-before' | translate | safe: 'html'"></div>
<div class="tb-bordered-content">
<tb-toggle-header #publishCommandSteps value="ubuntu" name="publishCommandSteps">
<mat-button-toggle value="ubuntu">Ubuntu</mat-button-toggle>
<mat-button-toggle value="macos">MacOS</mat-button-toggle>
<mat-button-toggle value="windows">Windows</mat-button-toggle>
</tb-toggle-header>
<ng-container [ngSwitch]="publishCommandSteps.value">
<ng-template [ngSwitchCase]="'ubuntu'">
<p [innerHTML]="'widgets.getting-started.tenant-admin.step2.ubuntu.install-curl' | translate | safe: 'html'"></p>
<tb-markdown usePlainMarkdown containerClass="tb-getting-started-code" data="
```bash
sudo apt-get install curl
{:copy-code}
```
"></tb-markdown>
</ng-template>
<ng-template [ngSwitchCase]="'macos'">
<p [innerHTML]="'widgets.getting-started.tenant-admin.step2.macos.install-curl' | translate | safe: 'html'"></p>
<tb-markdown usePlainMarkdown containerClass="tb-getting-started-code" data="
```bash
brew install curl
{:copy-code}
```
"></tb-markdown>
</ng-template>
<ng-template [ngSwitchCase]="'windows'">
<p [innerHTML]="'widgets.getting-started.tenant-admin.step2.windows.install-curl' | translate | safe: 'html'"></p>
</ng-template>
</ng-container>
<p [innerHTML]="'widgets.getting-started.tenant-admin.step2.replace-access-token' | translate | safe: 'html'"></p>
<tb-markdown usePlainMarkdown containerClass="tb-getting-started-code" data="
```bash
curl -v -X POST -d &quot;{\&quot;temperature\&quot;: 25}&quot; {{baseUrl}}/api/v1/$ACCESS_TOKEN/telemetry --header &quot;Content-Type:application/json&quot;
{:copy-code}
```
"></tb-markdown>
</div>
<div [innerHTML]="'widgets.getting-started.tenant-admin.step2.content-after' | translate | safe: 'html'"></div>
<a mat-stroked-button color="primary" href="https://thingsboard.io/docs/getting-started-guides/helloworld/#step-2-connect-device" target="_blank">
<mat-icon>description</mat-icon>{{ 'widgets.getting-started.tenant-admin.step2.how-to-connect-device' | translate }}</a>
</mat-step>
<mat-step [completed]="gettingStarted.maxSelectedIndex > 1">
<ng-template matStepLabel>
<div style="width: 100%;" fxLayout="row" fxLayoutAlign="space-between center">
<div translate>widgets.getting-started.tenant-admin.step3.title</div>
<a *ngIf="matStepper.selectedIndex === 2" mat-button color="primary" routerLink="/dashboards">{{ 'dashboard.dashboards' | translate }}</a>
</div>
</ng-template>
<div [innerHTML]="'widgets.getting-started.tenant-admin.step3.content' | translate | safe: 'html'"></div>
<a mat-stroked-button color="primary" href="https://thingsboard.io/docs/getting-started-guides/helloworld/#step-3-create-dashboard" target="_blank">
<mat-icon>description</mat-icon>{{ 'widgets.getting-started.tenant-admin.step3.how-to-create-dashboard' | translate }}</a>
</mat-step>
<mat-step [completed]="gettingStarted.maxSelectedIndex > 2">
<ng-template matStepLabel>
<div style="width: 100%;" fxLayout="row" fxLayoutAlign="space-between center">
<div translate>widgets.getting-started.tenant-admin.step4.title</div>
<a *ngIf="matStepper.selectedIndex === 3" mat-button color="primary" routerLink="/profiles/deviceProfiles">{{ 'widgets.getting-started.tenant-admin.step4.alarm-rules' | translate }}</a>
</div>
</ng-template>
<div [innerHTML]="'widgets.getting-started.tenant-admin.step4.content' | translate | safe: 'html'"></div>
<a mat-stroked-button color="primary" href="https://thingsboard.io/docs/getting-started-guides/helloworld/#step-4-configure-alarm-rules" target="_blank">
<mat-icon>description</mat-icon>{{ 'widgets.getting-started.tenant-admin.step4.how-to-configure-alarm-rules' | translate }}</a>
</mat-step>
<mat-step [completed]="gettingStarted.maxSelectedIndex > 3">
<ng-template matStepLabel>
<div style="width: 100%;" fxLayout="row" fxLayoutAlign="space-between center">
<div translate>widgets.getting-started.tenant-admin.step5.title</div>
</div>
</ng-template>
<div [innerHTML]="'widgets.getting-started.tenant-admin.step5.content-before' | translate | safe: 'html'"></div>
<p [innerHTML]="'widgets.getting-started.tenant-admin.step5.replace-access-token' | translate | safe: 'html'"></p>
<tb-markdown usePlainMarkdown containerClass="tb-getting-started-code" data="
```bash
curl -v -X POST -d &quot;{\&quot;temperature\&quot;: 26}&quot; {{baseUrl}}/api/v1/$ACCESS_TOKEN/telemetry --header &quot;Content-Type:application/json&quot;
{:copy-code}
```
"></tb-markdown>
<div [innerHTML]="'widgets.getting-started.tenant-admin.step5.content-after' | translate | safe: 'html'"></div>
<a mat-stroked-button color="primary" href="https://thingsboard.io/docs/getting-started-guides/helloworld/#step-5-create-alarm" target="_blank">
<mat-icon>description</mat-icon>{{ 'widgets.getting-started.tenant-admin.step5.how-to-create-alarm' | translate }}</a>
</mat-step>
<mat-step [completed]="gettingStarted.maxSelectedIndex > 4">
<ng-template matStepLabel>
<div style="width: 100%;" fxLayout="row" fxLayoutAlign="space-between center">
<div translate>widgets.getting-started.tenant-admin.step6.title</div>
<a *ngIf="matStepper.selectedIndex === 5" mat-button color="primary" routerLink="/customers">{{ 'customer.customers' | translate }}</a>
</div>
</ng-template>
<div [innerHTML]="'widgets.getting-started.tenant-admin.step6.content' | translate | safe: 'html'"></div>
<a mat-stroked-button color="primary" href="https://thingsboard.io/docs/getting-started-guides/helloworld/#step-7-assign-device-and-dashboard-to-customer" target="_blank">
<mat-icon>description</mat-icon>{{ 'widgets.getting-started.tenant-admin.step6.how-to-create-customer-and-assign-dashboard' | translate }}</a>
</mat-step>
</ng-template>
</ng-container>
</mat-stepper>
<div *ngIf="allCompleted" fxLayout="column" fxLayoutAlign="center center" style="padding-top: 24px;">

117
ui-ngx/src/app/modules/home/components/widget/lib/home-page/getting-started-widget.component.scss

@ -83,13 +83,105 @@
color: rgba(0, 0, 0, 0.87);
}
.tb-get-started .mat-vertical-content p {
font-style: normal;
font-weight: 400;
font-size: 14px;
line-height: 20px;
letter-spacing: 0.2px;
color: rgba(0, 0, 0, 0.54);
.tb-get-started .mat-vertical-content {
p, li {
font-style: normal;
font-weight: 400;
font-size: 14px;
line-height: 20px;
letter-spacing: 0.2px;
color: rgba(0, 0, 0, 0.54);
em {
font-style: normal;
font-weight: 500;
font-size: 13px;
color: rgba(0, 0, 0, 0.38);
}
}
ul {
padding-inline-start: 20px;
}
.tb-bordered-content {
display: flex;
flex-direction: column;
align-items: stretch;
border: 1px solid rgba(0, 0, 0, 0.05);
border-radius: 8px;
padding: 16px;
p {
margin-top: 16px;
margin-bottom: 8px;
}
}
.tb-markdown-view {
.tb-getting-started-code {
.code-wrapper {
padding: 0;
pre[class*=language-] {
margin: 0;
padding: 9px 38px 9px 16px;
background: rgba(0, 0, 0, 0.03);
border-radius: 6px;
border: none;
}
code[class*="language-"], pre[class*="language-"] {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
font-size: 12px;
line-height: 16px;
letter-spacing: 0.25px;
color: rgba(0, 0, 0, 0.38);
overflow: hidden;
white-space: break-spaces;
word-break: break-all;
& * {
color: rgba(0, 0, 0, 0.38);
cursor: inherit;
background: transparent;
}
}
button.clipboard-btn {
right: 0;
height: 34px;
p, div {
background: transparent;
}
p {
margin: 0;
padding: 7px;
color: #305680;
}
div {
top: 0;
padding: 8px;
height: 34px;
width: 34px;
img {
display: none;
}
&:after {
content: "";
position: initial;
display: block;
width: 18px;
height: 18px;
background: #305680;
-webkit-mask-image: url(/assets/copy-code-icon.svg);
-webkit-mask-repeat: no-repeat;
mask-image: url(/assets/copy-code-icon.svg);
mask-repeat: no-repeat;
}
}
}
}
}
}
@media #{$mat-md-lg} {
.tb-bordered-content {
padding: 4px;
}
}
}
@media #{$mat-md-lg} {
@ -107,15 +199,16 @@
line-height: 16px;
}
.tb-get-started .mat-vertical-content p {
font-size: 12px;
line-height: 16px;
letter-spacing: 0.25px;
.tb-get-started .mat-vertical-content {
p, li {
font-size: 12px;
line-height: 16px;
letter-spacing: 0.25px;
}
}
.tb-get-started .mat-vertical-content {
padding: 0 16px 16px 16px;
}
}
}

4
ui-ngx/src/app/modules/home/components/widget/lib/home-page/getting-started-widget.component.ts

@ -27,7 +27,7 @@ import {
} from '@home/components/widget/lib/home-page/getting-started-completed-dialog.component';
import { GettingStarted } from '@shared/models/user-settings.models';
import { CdkStep, StepperSelectionEvent } from '@angular/cdk/stepper';
import { isUndefined } from '@core/utils';
import { baseUrl, isUndefined } from '@core/utils';
import { MatStepper } from '@angular/material/stepper';
import { first } from 'rxjs/operators';
import { Authority } from '@shared/models/authority.enum';
@ -54,6 +54,8 @@ export class GettingStartedWidgetComponent extends PageComponent implements OnIn
};
allCompleted = false;
baseUrl = baseUrl();
constructor(protected store: Store<AppState>,
private cd: ChangeDetectorRef,
private userSettingsService: UserSettingsService,

7
ui-ngx/src/app/modules/home/components/widget/lib/home-page/home-page-widgets.module.ts

@ -28,6 +28,7 @@ import { GettingStartedWidgetComponent } from '@home/components/widget/lib/home-
import {
GettingStartedCompletedDialogComponent
} from '@home/components/widget/lib/home-page/getting-started-completed-dialog.component';
import { ToggleHeaderComponent } from '@home/components/widget/lib/home-page/toggle-header.component';
@NgModule({
declarations:
@ -40,7 +41,8 @@ import {
AddDocLinkDialogComponent,
EditDocLinksDialogComponent,
GettingStartedWidgetComponent,
GettingStartedCompletedDialogComponent
GettingStartedCompletedDialogComponent,
ToggleHeaderComponent
],
imports: [
CommonModule,
@ -55,7 +57,8 @@ import {
AddDocLinkDialogComponent,
EditDocLinksDialogComponent,
GettingStartedWidgetComponent,
GettingStartedCompletedDialogComponent
GettingStartedCompletedDialogComponent,
ToggleHeaderComponent
]
})
export class HomePageWidgetsModule { }

20
ui-ngx/src/app/modules/home/components/widget/lib/home-page/toggle-header.component.html

@ -0,0 +1,20 @@
<!--
Copyright © 2016-2023 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.
-->
<mat-button-toggle-group class="tb-toggle-header" #toggleGroup="matButtonToggleGroup" [name]="name">
<ng-content></ng-content>
</mat-button-toggle-group>

87
ui-ngx/src/app/modules/home/components/widget/lib/home-page/toggle-header.component.scss

@ -0,0 +1,87 @@
/**
* Copyright © 2016-2023 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 "../../../../../../../scss/constants";
:host ::ng-deep {
.mat-button-toggle-group.mat-button-toggle-group-appearance-standard.tb-toggle-header {
width: 100%;
border-radius: 100px;
height: 32px;
padding: 2px;
border: none;
background: rgba(0, 0, 0, 0.06);
margin-bottom: 8px;
.mat-button-toggle + .mat-button-toggle {
border-left: none;
}
.mat-button-toggle.mat-button-toggle-appearance-standard {
flex: 1;
color: rgba(0, 0, 0, 0.38);
background: transparent;
.mat-button-toggle-focus-overlay, .mat-button-toggle-ripple {
border-radius: 20px;
}
.mat-button-toggle-button {
height: 28px;
.mat-button-toggle-label-content {
line-height: 26px;
font-weight: 400;
font-size: 14px;
letter-spacing: 0.2px;
}
}
&.mat-button-toggle-checked {
.mat-button-toggle-button {
background: #F3F6FA;
color: #305680;
border: 1px solid #305680;
border-radius: 20px;
.mat-button-toggle-label-content {
font-weight: 500;
line-height: 24px;
}
}
}
}
}
@media #{$mat-md-lg} {
.mat-button-toggle-group.mat-button-toggle-group-appearance-standard.tb-toggle-header {
height: 24px;
margin-bottom: 0;
.mat-button-toggle.mat-button-toggle-appearance-standard {
.mat-button-toggle-button {
height: 20px;
display: grid;
.mat-button-toggle-label-content {
line-height: 20px;
font-size: 10px;
padding: 0 2px;
}
}
&.mat-button-toggle-checked {
.mat-button-toggle-button {
.mat-button-toggle-label-content {
line-height: 18px;
}
}
}
}
}
}
}

82
ui-ngx/src/app/modules/home/components/widget/lib/home-page/toggle-header.component.ts

@ -0,0 +1,82 @@
///
/// Copyright © 2016-2023 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 {
AfterContentInit,
AfterViewInit,
ChangeDetectorRef,
Component,
ContentChildren,
Input,
OnInit,
QueryList,
ViewChild
} from '@angular/core';
import { PageComponent } from '@shared/components/page.component';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { AdminService } from '@core/http/admin.service';
import { UpdateMessage } from '@shared/models/settings.models';
import { getCurrentAuthUser } from '@core/auth/auth.selectors';
import { Authority } from '@shared/models/authority.enum';
import { of } from 'rxjs';
import { MatStepper } from '@angular/material/stepper';
import { MatButtonToggle, MatButtonToggleGroup } from '@angular/material/button-toggle';
@Component({
selector: 'tb-toggle-header',
templateUrl: './toggle-header.component.html',
styleUrls: ['./toggle-header.component.scss']
})
export class ToggleHeaderComponent extends PageComponent implements OnInit, AfterViewInit {
@ViewChild('toggleGroup')
toggleGroup: MatButtonToggleGroup;
@ContentChildren(MatButtonToggle)
_buttonToggles: QueryList<MatButtonToggle>;
innerValue: any;
@Input()
set value(value: any) {
this.innerValue = value;
}
get value(): any {
return this.toggleGroup?.value;
}
@Input()
name: string;
constructor(protected store: Store<AppState>) {
super(store);
}
ngOnInit() {
}
ngAfterViewInit() {
for (const toggle of this._buttonToggles) {
toggle.buttonToggleGroup = this.toggleGroup;
if (this.innerValue === toggle.value) {
toggle.checked = true;
}
}
}
}

15
ui-ngx/src/app/modules/home/pages/home-links/home-links-routing.module.ts

@ -27,6 +27,7 @@ import { AppState } from '@core/core.state';
import { map } from 'rxjs/operators';
import { getCurrentAuthUser } from '@core/auth/auth.selectors';
import sysAdminHomePageDashboardJson from '!raw-loader!./sys_admin_home_page.raw';
import tenantAdminHomePageDashboardJson from '!raw-loader!./tenant_admin_home_page.raw';
@Injectable()
export class HomeDashboardResolver implements Resolve<HomeDashboard> {
@ -39,8 +40,18 @@ export class HomeDashboardResolver implements Resolve<HomeDashboard> {
return this.dashboardService.getHomeDashboard().pipe(
map((dashboard) => {
if (!dashboard) {
if (getCurrentAuthUser(this.store).authority === Authority.SYS_ADMIN) {
dashboard = JSON.parse(sysAdminHomePageDashboardJson);
const authority = getCurrentAuthUser(this.store).authority;
switch (authority) {
case Authority.SYS_ADMIN:
dashboard = JSON.parse(sysAdminHomePageDashboardJson);
break;
case Authority.TENANT_ADMIN:
dashboard = JSON.parse(tenantAdminHomePageDashboardJson);
break;
case Authority.CUSTOMER_USER:
break;
}
if (dashboard) {
dashboard.hideDashboardToolbar = true;
}
}

667
ui-ngx/src/app/modules/home/pages/home-links/tenant_admin_home_page.raw

@ -0,0 +1,667 @@
{
"title": "Tenant Administrator Home Page",
"image": null,
"mobileHide": false,
"mobileOrder": null,
"configuration": {
"description": "",
"widgets": {
"d70cc256-4c7b-ee06-9905-b8c5e546605f": {
"isSystemType": true,
"bundleAlias": "cards",
"typeAlias": "markdown_card",
"type": "latest",
"title": "New widget",
"image": null,
"description": null,
"sizeX": 5,
"sizeY": 3.5,
"config": {
"datasources": [],
"timewindow": {
"displayValue": "",
"selectedTab": 0,
"realtime": {
"realtimeType": 1,
"interval": 1000,
"timewindowMs": 60000,
"quickInterval": "CURRENT_DAY"
},
"history": {
"historyType": 0,
"interval": 1000,
"timewindowMs": 60000,
"fixedTimewindow": {
"startTimeMs": 1680168340431,
"endTimeMs": 1680254740431
},
"quickInterval": "CURRENT_DAY"
},
"aggregation": {
"type": "AVG",
"limit": 25000
}
},
"showTitle": false,
"backgroundColor": "#fff",
"color": "rgba(0, 0, 0, 0.87)",
"padding": "16px",
"settings": {
"useMarkdownTextFunction": false,
"markdownTextPattern": "<div class=\"tb-card-content\">\n <div fxLayout=\"row\" fxLayoutAlign=\"space-between start\">\n <div class=\"tb-home-widget-title\">{{ 'widgets.activity.title' | translate }}</div>\n <tb-toggle-header #activityStates value=\"devices\" name=\"activityStates\">\n <mat-button-toggle value=\"devices\">{{ 'device.devices' | translate }}</mat-button-toggle>\n <mat-button-toggle value=\"transportMessages\">{{ 'widgets.transport-messages.title' | translate }}</mat-button-toggle>\n </tb-toggle-header>\n </div>\n <ng-container [ngSwitch]=\"activityStates.value\">\n <ng-template [ngSwitchCase]=\"'devices'\">\n <tb-dashboard-state fxFlex stateId=\"devices_activity\" [ctx]=\"ctx\"></tb-dashboard-state>\n </ng-template>\n <ng-template [ngSwitchCase]=\"'transportMessages'\">\n <tb-dashboard-state fxFlex stateId=\"transport_messages\" [ctx]=\"ctx\"></tb-dashboard-state>\n </ng-template>\n </ng-container>\n</div>",
"applyDefaultMarkdownStyle": false,
"markdownCss": ".tb-card-content {\n width: 100%;\n height: 100%;\n display: flex;\n flex-direction: column;\n justify-content: space-between;\n}\n"
},
"title": "Transport messages",
"showTitleIcon": false,
"iconColor": "rgba(0, 0, 0, 0.87)",
"iconSize": "24px",
"titleTooltip": "",
"dropShadow": false,
"enableFullscreen": false,
"widgetStyle": {},
"titleStyle": {
"fontSize": "16px",
"fontWeight": 400
},
"showLegend": false,
"useDashboardTimewindow": true,
"widgetCss": "",
"pageSize": 1024,
"noDataDisplayMessage": ""
},
"row": 0,
"col": 0,
"id": "d70cc256-4c7b-ee06-9905-b8c5e546605f"
},
"8ee72d43-678c-4e25-e9a8-7d4cfd7a5f8e": {
"isSystemType": true,
"bundleAlias": "charts",
"typeAlias": "timeseries_bars_flot",
"type": "timeseries",
"title": "New widget",
"image": null,
"description": null,
"sizeX": 8,
"sizeY": 5,
"config": {
"datasources": [
{
"type": "entity",
"name": null,
"entityAliasId": "d9229b29-3f46-de8d-7fe8-eb0c43c75079",
"filterId": null,
"dataKeys": [
{
"name": "transportMsgCountHourly",
"type": "timeseries",
"label": "{i18n:widgets.transport-messages.title}",
"color": "#305680",
"settings": {},
"_hash": 0.2880464219129071,
"aggregationType": null,
"units": null,
"decimals": null,
"funcBody": null,
"usePostProcessing": null,
"postFuncBody": null
}
],
"latestDataKeys": []
}
],
"timewindow": {
"hideInterval": false,
"hideLastInterval": false,
"hideQuickInterval": false,
"hideAggregation": true,
"hideAggInterval": false,
"hideTimezone": false,
"selectedTab": 1,
"history": {
"historyType": 0,
"timewindowMs": 2592000000,
"interval": 86400000,
"fixedTimewindow": {
"startTimeMs": 1680443065451,
"endTimeMs": 1680529465451
},
"quickInterval": "CURRENT_DAY"
},
"aggregation": {
"type": "SUM",
"limit": 50000
}
},
"showTitle": false,
"backgroundColor": "#fff",
"color": "rgba(0, 0, 0, 0.87)",
"padding": "0px",
"settings": {
"stack": true,
"fontSize": 10,
"fontColor": "#545454",
"showTooltip": true,
"tooltipIndividual": false,
"tooltipCumulative": false,
"hideZeros": false,
"grid": {
"verticalLines": false,
"horizontalLines": false,
"outlineWidth": 0,
"color": "#545454",
"backgroundColor": null,
"tickColor": "#DDDDDD"
},
"xaxis": {
"title": null,
"showLabels": true,
"color": "#545454"
},
"yaxis": {
"min": 0,
"max": null,
"title": null,
"showLabels": true,
"color": "#545454",
"tickSize": null,
"tickDecimals": 0,
"ticksFormatter": "return value % 1000 === 0 ? ((value / 1000) + 'k') : '';"
},
"defaultBarWidth": 1800000,
"barAlignment": "left",
"comparisonEnabled": false,
"timeForComparison": "previousInterval",
"comparisonCustomIntervalValue": 7200000,
"xaxisSecond": {
"axisPosition": "top",
"title": null,
"showLabels": true
},
"customLegendEnabled": false,
"dataKeysListForLabels": []
},
"title": "Transport messages",
"dropShadow": false,
"enableFullscreen": false,
"titleStyle": {
"fontSize": "16px",
"fontWeight": 400
},
"widgetStyle": {
"padding": "0"
},
"useDashboardTimewindow": false,
"showLegend": false,
"actions": {},
"displayTimewindow": true,
"showTitleIcon": false,
"titleTooltip": "",
"widgetCss": ".tb-widget-container > .tb-widget {\n border: none !important;\n border-radius: 0 !important;\n box-shadow: none !important;\n}\n\n.tb-widget-container > .tb-widget .flot-base {\n opacity: 0.48;\n}\n",
"pageSize": 1024,
"noDataDisplayMessage": "",
"legendConfig": {
"direction": "column",
"position": "bottom",
"sortDataKeys": false,
"showMin": false,
"showMax": false,
"showAvg": true,
"showTotal": false,
"showLatest": false
}
},
"row": 0,
"col": 0,
"id": "8ee72d43-678c-4e25-e9a8-7d4cfd7a5f8e"
},
"867f82cf-ecf2-2d5c-35cb-08c6f2edc3a4": {
"isSystemType": true,
"bundleAlias": "home_page_widgets",
"typeAlias": "documentation_links",
"type": "static",
"title": "New widget",
"image": null,
"description": null,
"sizeX": 7.5,
"sizeY": 3,
"config": {
"datasources": [],
"timewindow": {
"realtime": {
"timewindowMs": 60000
}
},
"showTitle": false,
"backgroundColor": "rgb(255, 255, 255)",
"color": "rgba(0, 0, 0, 0.87)",
"padding": "16px",
"settings": {
"columns": 2
},
"title": "Documentation",
"dropShadow": false,
"enableFullscreen": false,
"widgetStyle": {},
"widgetCss": "",
"pageSize": 1024,
"noDataDisplayMessage": "",
"showLegend": false
},
"row": 0,
"col": 0,
"id": "867f82cf-ecf2-2d5c-35cb-08c6f2edc3a4"
},
"a23185ad-dc46-806c-0e50-5b21fb080ace": {
"isSystemType": true,
"bundleAlias": "home_page_widgets",
"typeAlias": "getting_started",
"type": "static",
"title": "New widget",
"image": null,
"description": null,
"sizeX": 7.5,
"sizeY": 6.5,
"config": {
"datasources": [],
"timewindow": {
"realtime": {
"timewindowMs": 60000
}
},
"showTitle": false,
"backgroundColor": "rgb(255, 255, 255)",
"color": "rgba(0, 0, 0, 0.87)",
"padding": "16px",
"settings": {
"columns": 3
},
"title": "Getting started",
"dropShadow": false,
"enableFullscreen": false,
"widgetStyle": {},
"widgetCss": "",
"pageSize": 1024,
"noDataDisplayMessage": "",
"showLegend": false
},
"row": 0,
"col": 0,
"id": "a23185ad-dc46-806c-0e50-5b21fb080ace"
},
"d26e5cd7-75ef-d475-00c7-1a2d1114efe8": {
"isSystemType": true,
"bundleAlias": "charts",
"typeAlias": "basic_timeseries",
"type": "timeseries",
"title": "New widget",
"image": null,
"description": null,
"sizeX": 8,
"sizeY": 5,
"config": {
"datasources": [
{
"type": "entity",
"name": null,
"entityAliasId": "d9229b29-3f46-de8d-7fe8-eb0c43c75079",
"filterId": null,
"dataKeys": [
{
"name": "activeDevicesCount",
"type": "timeseries",
"label": "{i18n:device.devices}",
"color": "#305680",
"settings": {
"hideDataByDefault": false,
"disableDataHiding": false,
"removeFromLegend": false,
"excludeFromStacking": false,
"showLines": true,
"lineWidth": 3,
"fillLines": true,
"showPoints": false,
"showPointsLineWidth": 5,
"showPointsRadius": 3,
"showPointShape": "circle",
"pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);",
"showSeparateAxis": false,
"axisPosition": "left",
"comparisonSettings": {
"showValuesForComparison": true,
"comparisonValuesLabel": "",
"color": ""
},
"thresholds": []
},
"_hash": 0.9688095820365725,
"aggregationType": null,
"units": null,
"decimals": null,
"funcBody": null,
"usePostProcessing": null,
"postFuncBody": null
}
],
"latestDataKeys": []
}
],
"timewindow": {
"hideInterval": false,
"hideLastInterval": false,
"hideQuickInterval": false,
"hideAggregation": true,
"hideAggInterval": true,
"hideTimezone": false,
"selectedTab": 1,
"history": {
"historyType": 0,
"timewindowMs": 2592000000,
"interval": 7200000,
"fixedTimewindow": {
"startTimeMs": 1681400576338,
"endTimeMs": 1681486976338
},
"quickInterval": "CURRENT_DAY"
},
"aggregation": {
"type": "NONE",
"limit": 25000
}
},
"showTitle": false,
"backgroundColor": "#fff",
"color": "rgba(0, 0, 0, 0.87)",
"padding": "0px",
"settings": {
"stack": false,
"fontSize": 10,
"fontColor": "#545454",
"showTooltip": true,
"tooltipIndividual": false,
"tooltipCumulative": false,
"hideZeros": false,
"grid": {
"verticalLines": false,
"horizontalLines": false,
"outlineWidth": 0,
"color": "#545454",
"backgroundColor": null,
"tickColor": "#DDDDDD"
},
"xaxis": {
"title": null,
"showLabels": true,
"color": "#545454"
},
"yaxis": {
"min": null,
"max": null,
"title": null,
"showLabels": true,
"color": "#545454",
"tickSize": null,
"tickDecimals": 0,
"ticksFormatter": ""
},
"shadowSize": 0,
"smoothLines": true,
"comparisonEnabled": false,
"timeForComparison": "previousInterval",
"comparisonCustomIntervalValue": 7200000,
"xaxisSecond": {
"axisPosition": "top",
"title": null,
"showLabels": true
},
"customLegendEnabled": false,
"dataKeysListForLabels": []
},
"title": "Devices activity",
"dropShadow": false,
"enableFullscreen": false,
"titleStyle": {
"fontSize": "16px",
"fontWeight": 400
},
"useDashboardTimewindow": false,
"displayTimewindow": true,
"showTitleIcon": false,
"titleTooltip": "",
"widgetStyle": {
"padding": "0"
},
"widgetCss": ".tb-widget-container > .tb-widget {\n border: none !important;\n border-radius: 0 !important;\n box-shadow: none !important;\n}",
"pageSize": 1024,
"noDataDisplayMessage": "",
"showLegend": false,
"legendConfig": {
"direction": "column",
"position": "bottom",
"sortDataKeys": false,
"showMin": false,
"showMax": false,
"showAvg": true,
"showTotal": false,
"showLatest": false
}
},
"row": 0,
"col": 0,
"id": "d26e5cd7-75ef-d475-00c7-1a2d1114efe8"
}
},
"states": {
"default": {
"name": "Tenant Administrator Home Page",
"root": true,
"layouts": {
"main": {
"widgets": {
"d70cc256-4c7b-ee06-9905-b8c5e546605f": {
"sizeX": 41,
"sizeY": 16,
"row": 26,
"col": 44,
"mobileOrder": 7,
"mobileHeight": 8
},
"867f82cf-ecf2-2d5c-35cb-08c6f2edc3a4": {
"sizeX": 31,
"sizeY": 16,
"row": 42,
"col": 26,
"mobileHide": true
},
"a23185ad-dc46-806c-0e50-5b21fb080ace": {
"sizeX": 35,
"sizeY": 58,
"row": 0,
"col": 85,
"mobileHide": true
}
},
"gridSettings": {
"backgroundColor": "#eeeeee",
"columns": 120,
"margin": 12,
"backgroundSizeMode": "100%",
"autoFillHeight": true,
"backgroundImageUrl": null,
"mobileAutoFillHeight": false,
"mobileRowHeight": 20,
"outerMargin": true
}
}
}
},
"transport_messages": {
"name": "Transport messages",
"root": false,
"layouts": {
"main": {
"widgets": {
"8ee72d43-678c-4e25-e9a8-7d4cfd7a5f8e": {
"sizeX": 24,
"sizeY": 11,
"row": 0,
"col": 0
}
},
"gridSettings": {
"backgroundColor": "#ffffff",
"columns": 24,
"margin": 0,
"backgroundSizeMode": "100%",
"autoFillHeight": true,
"backgroundImageUrl": null,
"mobileAutoFillHeight": true,
"mobileRowHeight": 70,
"outerMargin": true
}
}
}
},
"devices_activity": {
"name": "Devices activity",
"root": false,
"layouts": {
"main": {
"widgets": {
"d26e5cd7-75ef-d475-00c7-1a2d1114efe8": {
"sizeX": 24,
"sizeY": 11,
"row": 0,
"col": 0
}
},
"gridSettings": {
"backgroundColor": "#ffffff",
"columns": 24,
"margin": 0,
"outerMargin": true,
"backgroundSizeMode": "100%",
"autoFillHeight": true,
"backgroundImageUrl": null,
"mobileAutoFillHeight": true,
"mobileRowHeight": 70
}
}
}
}
},
"entityAliases": {
"ae870700-071f-b3bc-406c-16ba554c5a55": {
"id": "ae870700-071f-b3bc-406c-16ba554c5a55",
"alias": "Tenants",
"filter": {
"type": "entityType",
"resolveMultiple": true,
"entityType": "TENANT"
}
},
"ca4d90e4-f9ae-6fca-6b09-85815a48d52b": {
"id": "ca4d90e4-f9ae-6fca-6b09-85815a48d52b",
"alias": "TenantProfiles",
"filter": {
"type": "entityType",
"resolveMultiple": true,
"entityType": "TENANT"
}
},
"a1ddb8fa-90ff-5598-e7f2-e254194d055d": {
"id": "a1ddb8fa-90ff-5598-e7f2-e254194d055d",
"alias": "Devices",
"filter": {
"type": "entityType",
"resolveMultiple": true,
"entityType": "DEVICE"
}
},
"619cdf00-a042-3b55-124e-194c1b28c236": {
"id": "619cdf00-a042-3b55-124e-194c1b28c236",
"alias": "Assets",
"filter": {
"type": "entityType",
"resolveMultiple": true,
"entityType": "ASSET"
}
},
"1d97ff7f-8b42-5882-f87b-16f3d0dee4f2": {
"id": "1d97ff7f-8b42-5882-f87b-16f3d0dee4f2",
"alias": "Users",
"filter": {
"type": "entityType",
"resolveMultiple": true,
"entityType": "USER"
}
},
"0dd2b154-59f4-0f97-da1a-d85f5b5cfe31": {
"id": "0dd2b154-59f4-0f97-da1a-d85f5b5cfe31",
"alias": "Customers",
"filter": {
"type": "entityType",
"resolveMultiple": true,
"entityType": "CUSTOMER"
}
},
"d9229b29-3f46-de8d-7fe8-eb0c43c75079": {
"id": "d9229b29-3f46-de8d-7fe8-eb0c43c75079",
"alias": "Api Usage State",
"filter": {
"type": "apiUsageState",
"resolveMultiple": true
}
}
},
"filters": {},
"timewindow": {
"displayValue": "",
"hideInterval": false,
"hideLastInterval": false,
"hideQuickInterval": false,
"hideAggregation": false,
"hideAggInterval": false,
"hideTimezone": false,
"selectedTab": 0,
"realtime": {
"realtimeType": 0,
"interval": 1000,
"timewindowMs": 60000,
"quickInterval": "CURRENT_DAY"
},
"history": {
"historyType": 0,
"interval": 1000,
"timewindowMs": 60000,
"fixedTimewindow": {
"startTimeMs": 1680168326072,
"endTimeMs": 1680254726072
},
"quickInterval": "CURRENT_DAY"
},
"aggregation": {
"type": "AVG",
"limit": 25000
}
},
"settings": {
"stateControllerId": "entity",
"showTitle": false,
"showDashboardsSelect": true,
"showEntitiesSelect": true,
"showDashboardTimewindow": true,
"showDashboardExport": true,
"toolbarAlwaysOpen": true,
"titleColor": "rgba(0,0,0,0.870588)",
"showDashboardLogo": false,
"dashboardLogoUrl": null,
"hideToolbar": true,
"showFilters": true,
"showUpdateDashboardImage": true,
"dashboardCss": ".tb-widget-container > .tb-widget {\n border: 1px solid rgba(0, 0, 0, 0.05);\n box-shadow: 0px 5px 16px rgba(0, 0, 0, 0.04);\n border-radius: 12px;\n}\n\n.tb-widget-container > .tb-widget:not([style*=\"padding: 0\"]) {\n padding: 16px !important;\n}\n\n.tb-card-title {\n display: grid;\n}\n\n.tb-home-widget-title {\n font-style: normal;\n font-weight: 500;\n font-size: 14px;\n line-height: 20px;\n letter-spacing: 0.25px;\n color: rgba(0, 0, 0, 0.54);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.tb-home-widget-link {\n position: relative;\n border-bottom: none;\n}\n\n.tb-home-widget-link:hover {\n border-bottom: none;\n}\n\n.tb-home-widget-link:focus {\n border-bottom: none;\n}\n\n.tb-home-widget-link::after {\n content: 'arrow_forward';\n display: inline-block;\n transform: rotate(315deg);\n font-family: 'Material Icons';\n font-weight: normal;\n font-style: normal;\n font-size: 18px;\n color: rgba(0, 0, 0, 0.12);\n vertical-align: bottom;\n margin-left: 6px; \n}\n\n.tb-home-widget-link:hover::after {\n color: inherit;\n}\n\n.tb-home-widget-info-icon {\n color: rgba(0, 0, 0, 0.12);\n font-size: 16px;\n width: 16px;\n height: 16px;\n line-height: 15px;\n vertical-align: middle;\n}\n\n.tb-widget-container > .tb-widget .tb-timewindow {\n font-size: 14px;\n line-height: 20px;\n letter-spacing: 0.25px;\n color: rgba(0, 0, 0, 0.54);\n padding: 0;\n}\n\n.tb-widget-container > .tb-widget .tb-legend-keys .tb-legend-label {\n cursor: pointer;\n user-select: none;\n font-weight: 400;\n font-size: 14px;\n line-height: 20px;\n letter-spacing: 0.2px;\n color: rgba(0, 0, 0, 0.54);\n}\n\n@media screen and (min-width: 960px) and (max-width: 1279px) {\n .tb-widget-container > .tb-widget {\n border-radius: 4px;\n }\n .tb-widget-container > .tb-widget:not([style*=\"padding: 0\"]) {\n padding: 2px !important;\n }\n .tb-hide-md {\n display: none;\n }\n}\n\n@media screen and (min-width: 1280px) and (max-width: 1819px) {\n .tb-widget-container > .tb-widget:not([style*=\"padding: 0\"]) {\n padding: 8px !important;\n }\n .tb-hide-lg {\n display: none;\n }\n}\n\n@media screen and (min-width: 960px) and (max-width: 1819px) {\n .tb-hide-md-lg {\n display: none;\n }\n\n .tb-home-widget-title {\n font-size: 12px;\n line-height: 16px;\n }\n \n .tb-widget-container > .tb-widget .tb-widget-title {\n padding: 0;\n }\n\n .tb-widget-container > .tb-widget .tb-timewindow {\n font-size: 12px;\n line-height: 16px;\n min-height: 24px;\n padding: 0;\n }\n\n .tb-widget-container > .tb-widget .tb-timewindow .mat-mdc-icon-button.tb-mat-32 {\n width: 24px;\n height: 24px;\n line-height: 24px;\n }\n\n .tb-widget-container > .tb-widget .tb-timewindow .mat-mdc-icon-button.tb-mat-32 .mat-icon {\n width: 18px;\n height: 18px;\n font-size: 18px;\n }\n \n .tb-widget-container > .tb-widget tb-legend {\n padding-bottom: 0 !important;\n }\n \n .tb-widget-container > .tb-widget .tb-legend-keys .tb-legend-label {\n font-size: 11px;\n line-height: 16px;\n letter-spacing: 0.25px;\n }\n}\n\n@media screen and (max-width: 959px), screen and (min-width: 1820px) {\n .tb-hide-not-md-lg {\n display: none;\n }\n}\n"
}
},
"externalId": null,
"name": "Tenant Administrator Home Page"
}

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

@ -22,5 +22,5 @@
background: #efefef;">
{{error}}
</div>
<div #fallbackElement [fxShow]="error && fallbackToPlainMarkdown" class="tb-markdown-view" [ngClass]="markdownClass" [ngStyle]="style">
<div #fallbackElement [fxShow]="usePlainMarkdown || (error && fallbackToPlainMarkdown)" class="tb-markdown-view" [ngClass]="markdownClass" [ngStyle]="style">
</div>

127
ui-ngx/src/app/shared/components/markdown.component.ts

@ -39,6 +39,7 @@ import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { SHARED_MODULE_TOKEN } from '@shared/components/tokens';
import { deepClone, guid, isDefinedAndNotNull } from '@core/utils';
import { Observable, of, ReplaySubject } from 'rxjs';
import { coerceBoolean } from '@shared/decorators/coerce-boolean';
let defaultMarkdownStyle;
@ -76,6 +77,10 @@ export class TbMarkdownComponent implements OnChanges {
get fallbackToPlainMarkdown(): boolean { return this.fallbackToPlainMarkdownValue; }
set fallbackToPlainMarkdown(value: boolean) { this.fallbackToPlainMarkdownValue = coerceBooleanProperty(value); }
@Input()
@coerceBoolean()
usePlainMarkdown = false;
@Output() ready = new EventEmitter<void>();
private lineNumbersValue = false;
@ -124,13 +129,8 @@ export class TbMarkdownComponent implements OnChanges {
}
template = this.sanitizeCurlyBraces(template);
this.markdownContainer.clear();
const parent = this;
let readyObservable: Observable<void>;
let compileModules = [this.sharedModule];
if (this.additionalCompileModules) {
compileModules = compileModules.concat(this.additionalCompileModules);
}
let styles: string[] = [];
let readyObservable: Observable<void>;
if (this.applyDefaultMarkdownStyle) {
if (!defaultMarkdownStyle) {
defaultMarkdownStyle = deepClone(TbMarkdownComponent['ɵcmp'].styles)[0].replace(/\[_nghost\-%COMP%\]/g, '')
@ -141,70 +141,87 @@ export class TbMarkdownComponent implements OnChanges {
if (this.additionalStyles) {
styles = styles.concat(this.additionalStyles);
}
this.dynamicComponentFactoryService.createDynamicComponentFactory(
class TbMarkdownInstance {
ngOnDestroy(): void {
parent.destroyMarkdownInstanceResources();
}
},
template,
compileModules,
true, 1, styles
).subscribe((factory) => {
this.tbMarkdownInstanceComponentFactory = factory;
const injector: Injector = Injector.create({providers: [], parent: this.markdownContainer.injector});
try {
this.tbMarkdownInstanceComponentRef =
this.markdownContainer.createComponent(this.tbMarkdownInstanceComponentFactory, 0, injector);
if (this.context) {
for (const propName of Object.keys(this.context)) {
this.tbMarkdownInstanceComponentRef.instance[propName] = this.context[propName];
}
}
this.tbMarkdownInstanceComponentRef.instance.style = this.style;
readyObservable = this.handleImages(this.tbMarkdownInstanceComponentRef.location.nativeElement);
this.cd.detectChanges();
this.error = null;
} catch (error) {
readyObservable = this.handleError(template, error, styles);
}
readyObservable.subscribe(() => {
this.ready.emit();
});
},
(error) => {
readyObservable = this.handleError(template, error, styles);
if (this.usePlainMarkdown) {
readyObservable = this.plainMarkdown(template, styles);
this.cd.detectChanges();
readyObservable.subscribe(() => {
this.ready.emit();
});
});
} else {
const parent = this;
let compileModules = [this.sharedModule];
if (this.additionalCompileModules) {
compileModules = compileModules.concat(this.additionalCompileModules);
}
this.dynamicComponentFactoryService.createDynamicComponentFactory(
class TbMarkdownInstance {
ngOnDestroy(): void {
parent.destroyMarkdownInstanceResources();
}
},
template,
compileModules,
true, 1, styles
).subscribe((factory) => {
this.tbMarkdownInstanceComponentFactory = factory;
const injector: Injector = Injector.create({providers: [], parent: this.markdownContainer.injector});
try {
this.tbMarkdownInstanceComponentRef =
this.markdownContainer.createComponent(this.tbMarkdownInstanceComponentFactory, 0, injector);
if (this.context) {
for (const propName of Object.keys(this.context)) {
this.tbMarkdownInstanceComponentRef.instance[propName] = this.context[propName];
}
}
this.tbMarkdownInstanceComponentRef.instance.style = this.style;
readyObservable = this.handleImages(this.tbMarkdownInstanceComponentRef.location.nativeElement);
this.cd.detectChanges();
this.error = null;
} catch (error) {
readyObservable = this.handleError(template, error, styles);
}
readyObservable.subscribe(() => {
this.ready.emit();
});
},
(error) => {
readyObservable = this.handleError(template, error, styles);
this.cd.detectChanges();
readyObservable.subscribe(() => {
this.ready.emit();
});
});
}
}
private handleError(template: string, error, styles?: string[]): Observable<void> {
this.error = (error ? error + '' : 'Failed to render markdown!').replace(/\n/g, '<br>');
this.markdownContainer.clear();
if (this.fallbackToPlainMarkdownValue) {
const element = this.fallbackElement.nativeElement;
let styleElement;
if (styles?.length) {
const markdownClass = 'tb-markdown-view-' + guid();
let innerStyle = styles.join('\n');
innerStyle = innerStyle.replace(/\.tb-markdown-view/g, '.' + markdownClass);
template = template.replace(/tb-markdown-view/g, markdownClass);
styleElement = this.renderer.createElement('style');
styleElement.innerHTML = innerStyle;
}
element.innerHTML = template;
if (styleElement) {
this.renderer.appendChild(element, styleElement);
}
return this.handleImages(element);
return this.plainMarkdown(template, styles);
} else {
return of(null);
}
}
private plainMarkdown(template: string, styles?: string[]): Observable<void> {
const element = this.fallbackElement.nativeElement;
let styleElement;
if (styles?.length) {
const markdownClass = 'tb-markdown-view-' + guid();
let innerStyle = styles.join('\n');
innerStyle = innerStyle.replace(/\.tb-markdown-view/g, '.' + markdownClass);
template = template.replace(/tb-markdown-view/g, markdownClass);
styleElement = this.renderer.createElement('style');
styleElement.innerHTML = innerStyle;
}
element.innerHTML = template;
if (styleElement) {
this.renderer.appendChild(element, styleElement);
}
return this.handleImages(element);
}
private handlePlugins(element: HTMLElement): void {
if (this.lineNumbers) {
this.setPluginClass(element, PrismPlugin.LineNumbers);

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

@ -5114,6 +5114,9 @@
"title": "Transport messages",
"info": "All the messages that came from devices"
},
"activity": {
"title": "Activity"
},
"documentation": {
"title": "Documentation",
"add-link": "Add link",
@ -5201,6 +5204,52 @@
"content": "<p>Some text</p><p>Follow the documentation on how to do it:</p>",
"how-to-configure-notifications": "How to configure Notifications"
}
},
"tenant-admin": {
"step1": {
"title": "Create device",
"content": "<p>We will manually provision the device using the UI. Follow the documentation on how to do it:</p>",
"how-to-create-device": "How to create Device"
},
"step2": {
"title": "Connect device",
"content-before": "<p>To connect the device you need to get the device credentials. But we recommend using the default auto-generated one.</p><ul><li>Go to device table</li><li>Click on the device row to open device details</li><li>Press the button \"Copy access token\"</li></ul><p>Use simple commands to publish data over HTTP:</p>",
"ubuntu": {
"install-curl": "Install cURL for Ubuntu:"
},
"macos": {
"install-curl": "Install cURL for MacOS:"
},
"windows": {
"install-curl": "Starting Windows 10 b17063, cURL is available by default."
},
"replace-access-token": "Replace <em>$ACCESS_TOKEN</em> with your device's token:",
"content-after": "<p>You can also use other protocols such as MQTT, CoAP, etc.</p><p>Follow the documentation on how to do it:</p>",
"how-to-connect-device": "How to connect Device"
},
"step3": {
"title": "Create dashboard",
"content": "<p>Create a dashboard to visualize data from entities such as assets, devices, etc.</p><p>Follow the documentation on how to do it:</p>",
"how-to-create-dashboard": "How to create Dashboard"
},
"step4": {
"title": "Configure alarm rules",
"alarm-rules": "Alarm rules",
"content": "<p>When the temperature reaches 25°C, we will raise an alarm. Follow the documentation on how to do it:</p>",
"how-to-configure-alarm-rules": "How to configure Alarm rules"
},
"step5": {
"title": "Create alarm",
"content-before": "<p>To trigger the alarm, send a new telemetry value of 26°C or higher.</p>",
"replace-access-token": "Replace <em>$ACCESS_TOKEN</em> with your device's token:",
"content-after": "<p>Follow the documentation on how to do it:</p>",
"how-to-create-alarm": "How to create Alarm"
},
"step6": {
"title": "Create customer and assign dashboard",
"content": "<p>By creating end-user dashboards, a customer user can only see his own devices, and data from another customer will be hidden.</p><p>Follow the documentation on how to do it:</p>",
"how-to-create-customer-and-assign-dashboard": "How to create Customer and assign Dashboard"
}
}
}
},

Loading…
Cancel
Save