UI: Tenant admin home page initial implementation

This commit is contained in:
Igor Kulikov 2023-04-14 19:18:35 +03:00
parent 0f5f8ee714
commit bcc5eba8e6
12 changed files with 1216 additions and 73 deletions

View File

@ -91,6 +91,118 @@
<mat-icon>description</mat-icon>{{ 'widgets.getting-started.sys-admin.step6.how-to-configure-notifications' | translate }}</a> <mat-icon>description</mat-icon>{{ 'widgets.getting-started.sys-admin.step6.how-to-configure-notifications' | translate }}</a>
</mat-step> </mat-step>
</ng-template> </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> </ng-container>
</mat-stepper> </mat-stepper>
<div *ngIf="allCompleted" fxLayout="column" fxLayoutAlign="center center" style="padding-top: 24px;"> <div *ngIf="allCompleted" fxLayout="column" fxLayoutAlign="center center" style="padding-top: 24px;">

View File

@ -83,13 +83,105 @@
color: rgba(0, 0, 0, 0.87); color: rgba(0, 0, 0, 0.87);
} }
.tb-get-started .mat-vertical-content p { .tb-get-started .mat-vertical-content {
font-style: normal; p, li {
font-weight: 400; font-style: normal;
font-size: 14px; font-weight: 400;
line-height: 20px; font-size: 14px;
letter-spacing: 0.2px; line-height: 20px;
color: rgba(0, 0, 0, 0.54); 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} { @media #{$mat-md-lg} {
@ -107,15 +199,16 @@
line-height: 16px; line-height: 16px;
} }
.tb-get-started .mat-vertical-content p { .tb-get-started .mat-vertical-content {
font-size: 12px; p, li {
line-height: 16px; font-size: 12px;
letter-spacing: 0.25px; line-height: 16px;
letter-spacing: 0.25px;
}
} }
.tb-get-started .mat-vertical-content { .tb-get-started .mat-vertical-content {
padding: 0 16px 16px 16px; padding: 0 16px 16px 16px;
} }
} }
} }

View File

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

View File

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

View File

@ -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>

View File

@ -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;
}
}
}
}
}
}
}

View File

@ -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;
}
}
}
}

View File

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

View File

@ -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"
}

View File

@ -22,5 +22,5 @@
background: #efefef;"> background: #efefef;">
{{error}} {{error}}
</div> </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> </div>

View File

@ -39,6 +39,7 @@ import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { SHARED_MODULE_TOKEN } from '@shared/components/tokens'; import { SHARED_MODULE_TOKEN } from '@shared/components/tokens';
import { deepClone, guid, isDefinedAndNotNull } from '@core/utils'; import { deepClone, guid, isDefinedAndNotNull } from '@core/utils';
import { Observable, of, ReplaySubject } from 'rxjs'; import { Observable, of, ReplaySubject } from 'rxjs';
import { coerceBoolean } from '@shared/decorators/coerce-boolean';
let defaultMarkdownStyle; let defaultMarkdownStyle;
@ -76,6 +77,10 @@ export class TbMarkdownComponent implements OnChanges {
get fallbackToPlainMarkdown(): boolean { return this.fallbackToPlainMarkdownValue; } get fallbackToPlainMarkdown(): boolean { return this.fallbackToPlainMarkdownValue; }
set fallbackToPlainMarkdown(value: boolean) { this.fallbackToPlainMarkdownValue = coerceBooleanProperty(value); } set fallbackToPlainMarkdown(value: boolean) { this.fallbackToPlainMarkdownValue = coerceBooleanProperty(value); }
@Input()
@coerceBoolean()
usePlainMarkdown = false;
@Output() ready = new EventEmitter<void>(); @Output() ready = new EventEmitter<void>();
private lineNumbersValue = false; private lineNumbersValue = false;
@ -124,13 +129,8 @@ export class TbMarkdownComponent implements OnChanges {
} }
template = this.sanitizeCurlyBraces(template); template = this.sanitizeCurlyBraces(template);
this.markdownContainer.clear(); 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 styles: string[] = [];
let readyObservable: Observable<void>;
if (this.applyDefaultMarkdownStyle) { if (this.applyDefaultMarkdownStyle) {
if (!defaultMarkdownStyle) { if (!defaultMarkdownStyle) {
defaultMarkdownStyle = deepClone(TbMarkdownComponent['ɵcmp'].styles)[0].replace(/\[_nghost\-%COMP%\]/g, '') defaultMarkdownStyle = deepClone(TbMarkdownComponent['ɵcmp'].styles)[0].replace(/\[_nghost\-%COMP%\]/g, '')
@ -141,70 +141,87 @@ export class TbMarkdownComponent implements OnChanges {
if (this.additionalStyles) { if (this.additionalStyles) {
styles = styles.concat(this.additionalStyles); styles = styles.concat(this.additionalStyles);
} }
this.dynamicComponentFactoryService.createDynamicComponentFactory( if (this.usePlainMarkdown) {
class TbMarkdownInstance { readyObservable = this.plainMarkdown(template, styles);
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(); this.cd.detectChanges();
readyObservable.subscribe(() => { readyObservable.subscribe(() => {
this.ready.emit(); 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> { private handleError(template: string, error, styles?: string[]): Observable<void> {
this.error = (error ? error + '' : 'Failed to render markdown!').replace(/\n/g, '<br>'); this.error = (error ? error + '' : 'Failed to render markdown!').replace(/\n/g, '<br>');
this.markdownContainer.clear(); this.markdownContainer.clear();
if (this.fallbackToPlainMarkdownValue) { if (this.fallbackToPlainMarkdownValue) {
const element = this.fallbackElement.nativeElement; return this.plainMarkdown(template, styles);
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);
} else { } else {
return of(null); 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 { private handlePlugins(element: HTMLElement): void {
if (this.lineNumbers) { if (this.lineNumbers) {
this.setPluginClass(element, PrismPlugin.LineNumbers); this.setPluginClass(element, PrismPlugin.LineNumbers);

View File

@ -5114,6 +5114,9 @@
"title": "Transport messages", "title": "Transport messages",
"info": "All the messages that came from devices" "info": "All the messages that came from devices"
}, },
"activity": {
"title": "Activity"
},
"documentation": { "documentation": {
"title": "Documentation", "title": "Documentation",
"add-link": "Add link", "add-link": "Add link",
@ -5201,6 +5204,52 @@
"content": "<p>Some text</p><p>Follow the documentation on how to do it:</p>", "content": "<p>Some text</p><p>Follow the documentation on how to do it:</p>",
"how-to-configure-notifications": "How to configure Notifications" "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"
}
} }
} }
}, },