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-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;">

View File

@ -83,13 +83,105 @@
color: rgba(0, 0, 0, 0.87);
}
.tb-get-started .mat-vertical-content p {
.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 {
.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;
}
}
}

View File

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

View File

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

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 { 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) {
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;
}
}

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;">
{{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>

View File

@ -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,6 +141,18 @@ export class TbMarkdownComponent implements OnChanges {
if (this.additionalStyles) {
styles = styles.concat(this.additionalStyles);
}
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 {
@ -180,11 +192,19 @@ export class TbMarkdownComponent implements OnChanges {
});
});
}
}
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) {
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) {
@ -200,9 +220,6 @@ export class TbMarkdownComponent implements OnChanges {
this.renderer.appendChild(element, styleElement);
}
return this.handleImages(element);
} else {
return of(null);
}
}
private handlePlugins(element: HTMLElement): void {

View File

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