Browse Source

UI: Sys Admin Home Page

pull/8359/head
Igor Kulikov 4 years ago
parent
commit
6041796e7c
  1. 3
      ui-ngx/src/app/modules/home/components/tokens.ts
  2. 77
      ui-ngx/src/app/modules/home/components/widget/lib/home-page/cluster-info-table.component.html
  3. 71
      ui-ngx/src/app/modules/home/components/widget/lib/home-page/cluster-info-table.component.scss
  4. 236
      ui-ngx/src/app/modules/home/components/widget/lib/home-page/cluster-info-table.component.ts
  5. 35
      ui-ngx/src/app/modules/home/components/widget/lib/home-page/home-page-widgets.module.ts
  6. 2
      ui-ngx/src/app/modules/home/components/widget/lib/markdown-widget.component.html
  7. 3
      ui-ngx/src/app/modules/home/components/widget/lib/markdown-widget.component.ts
  8. 7
      ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts
  9. 761
      ui-ngx/src/app/modules/home/pages/home-links/sys_admin_home_page.raw
  10. 23
      ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts
  11. BIN
      ui-ngx/src/assets/fonts/MaterialIconsRound-Regular.otf
  12. 16
      ui-ngx/src/assets/fonts/material-icons.css
  13. 1
      ui-ngx/src/index.html

3
ui-ngx/src/app/modules/home/components/tokens.ts

@ -23,6 +23,9 @@ export const SHARED_HOME_COMPONENTS_MODULE_TOKEN: InjectionToken<Type<any>> =
export const HOME_COMPONENTS_MODULE_TOKEN: InjectionToken<Type<any>> =
new InjectionToken<Type<any>>('HOME_COMPONENTS_MODULE_TOKEN');
export const WIDGET_COMPONENTS_MODULE_TOKEN: InjectionToken<Type<any>> =
new InjectionToken<Type<any>>('WIDGET_COMPONENTS_MODULE_TOKEN');
export const COMPLEX_FILTER_PREDICATE_DIALOG_COMPONENT_TOKEN: InjectionToken<ComponentType<any>> =
new InjectionToken<ComponentType<any>>('COMPLEX_FILTER_PREDICATE_DIALOG_COMPONENT_TOKEN');

77
ui-ngx/src/app/modules/home/components/widget/lib/home-page/cluster-info-table.component.html

@ -0,0 +1,77 @@
<!--
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.
-->
<table mat-table [dataSource]="dataSource" matSort [matSortActive]="pageLink.sortOrder?.property"
[matSortDirection]="pageLink.sortDirection()" matSortDisableClear>
<ng-container matColumnDef="serviceId">
<mat-header-cell *matHeaderCellDef style="width: 35%;" mat-sort-header>
Service id
</mat-header-cell>
<mat-cell *matCellDef="let serviceData">
{{ serviceData.serviceId }}
</mat-cell>
</ng-container>
<ng-container matColumnDef="serviceType">
<mat-header-cell *matHeaderCellDef style="width: 35%;" mat-sort-header>
Service type
</mat-header-cell>
<mat-cell *matCellDef="let serviceData">
{{ serviceData.serviceType }}
</mat-cell>
</ng-container>
<ng-container matColumnDef="cpuUsage">
<mat-header-cell *matHeaderCellDef style="width: 10%;" mat-sort-header>
CPU
</mat-header-cell>
<mat-cell *matCellDef="let serviceData"
[matTooltip]="infoTooltip(serviceData, 'cpu')"
matTooltipPosition="above"
class="tb-resource-usage"
[ngClass]="statusClass(serviceData.cpuUsage)">
{{ serviceData.cpuUsage }}%
</mat-cell>
</ng-container>
<ng-container matColumnDef="memoryUsage">
<mat-header-cell *matHeaderCellDef style="width: 10%;" mat-sort-header>
RAM
</mat-header-cell>
<mat-cell *matCellDef="let serviceData"
[matTooltip]="infoTooltip(serviceData, 'ram')"
matTooltipPosition="above"
class="tb-resource-usage"
[ngClass]="statusClass(serviceData.memoryUsage)">
{{ serviceData.memoryUsage }}%
</mat-cell>
</ng-container>
<ng-container matColumnDef="discUsage">
<mat-header-cell *matHeaderCellDef style="width: 10%;" mat-sort-header>
Disk
</mat-header-cell>
<mat-cell *matCellDef="let serviceData"
[matTooltip]="infoTooltip(serviceData, 'disc')"
matTooltipPosition="above"
class="tb-resource-usage"
[ngClass]="statusClass(serviceData.discUsage)">
{{ serviceData.discUsage }}%
</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></mat-header-row>
<mat-row *matRowDef="let serviceData; columns: displayedColumns;"></mat-row>
</table>
<span [fxShow]="dataSource.isEmpty() | async"
fxLayoutAlign="center center"
class="no-data-found">No data</span>

71
ui-ngx/src/app/modules/home/components/widget/lib/home-page/cluster-info-table.component.scss

@ -0,0 +1,71 @@
/**
* 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.
*/
:host {
span.no-data-found {
font-weight: 400;
font-size: 14px;
line-height: 20px;
letter-spacing: 0.2px;
color: rgba(0, 0, 0, 0.76);
}
.tb-resource-usage {
cursor: default;
&.tb-status-warn, &.tb-status-critical {
position: relative;
overflow: visible;
&:before {
position: absolute;
top: 15px;
left: -24px;
font-family: 'Material Icons Round';
font-size: 20px;
line-height: 1;
}
}
&.tb-status-warn {
color: #FAA405;
&:before {
content: "warning";
color: #FAA405;
}
}
&.tb-status-critical {
color: #D12730;
&:before {
content: "warning";
color: #D12730;
}
}
}
}
:host ::ng-deep {
.mdc-data-table__header-cell {
font-weight: 500;
font-size: 11px;
line-height: 20px;
letter-spacing: 0.25px;
color: rgba(0, 0, 0, 0.54);
}
.mdc-data-table__cell {
font-weight: 400;
font-size: 14px;
line-height: 20px;
letter-spacing: 0.2px;
color: rgba(0, 0, 0, 0.76);
}
}

236
ui-ngx/src/app/modules/home/components/widget/lib/home-page/cluster-info-table.component.ts

@ -0,0 +1,236 @@
///
/// 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 { AfterViewInit, ChangeDetectionStrategy, Component, NgZone, OnInit, ViewChild } from '@angular/core';
import { PageComponent } from '@shared/components/page.component';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { CollectionViewer, DataSource } from '@angular/cdk/collections';
import { BehaviorSubject, Observable, of } from 'rxjs';
import { emptyPageData, PageData } from '@shared/models/page/page-data';
import { MAX_SAFE_PAGE_SIZE, PageLink } from '@shared/models/page/page-link';
import { catchError, map, publishReplay, refCount, share } from 'rxjs/operators';
import { MatSort } from '@angular/material/sort';
import { Direction, SortOrder } from '@shared/models/page/sort-order';
import { TelemetryWebsocketService } from '@core/ws/telemetry-websocket.service';
import { EntityDataUpdate, TelemetrySubscriber } from '@shared/models/telemetry/telemetry.models';
import { AliasFilterType } from '@shared/models/alias.models';
import { EntityKeyType } from '@shared/models/query/query.models';
export interface SystemInfoData {
serviceId: string;
serviceType: string;
cpuUsage: number;
cpuCount: number;
memoryUsage: number;
totalMemory: number;
discUsage: number;
totalDiscSpace: number;
}
@Component({
selector: 'tb-cluster-info-table',
templateUrl: './cluster-info-table.component.html',
styleUrls: ['./cluster-info-table.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ClusterInfoTableComponent extends PageComponent implements OnInit, AfterViewInit {
@ViewChild(MatSort) sort: MatSort;
displayedColumns = ['serviceId', 'serviceType', 'cpuUsage', 'memoryUsage', 'discUsage'];
dataSource: SystemInfoDataSource;
pageLink: PageLink;
constructor(protected store: Store<AppState>,
private telemetryWsService: TelemetryWebsocketService,
private zone: NgZone) {
super(store);
}
ngOnInit() {
this.dataSource = new SystemInfoDataSource(this.telemetryWsService, this.zone);
const sortOrder: SortOrder = {
property: 'serviceId',
direction: Direction.ASC
};
this.pageLink = new PageLink(MAX_SAFE_PAGE_SIZE, 0, null, sortOrder);
this.dataSource.loadData(this.pageLink);
}
ngAfterViewInit() {
this.sort.sortChange.subscribe(() => this.updateData());
}
updateData() {
this.pageLink.sortOrder.property = this.sort.active;
this.pageLink.sortOrder.direction = Direction[this.sort.direction.toUpperCase()];
this.dataSource.loadData(this.pageLink);
}
statusClass(value: number): string {
let status = '';
if (value > 85) {
status = 'tb-status-critical';
} else if (value > 75) {
status = 'tb-status-warn';
}
return status;
}
infoTooltip(serviceData: SystemInfoData, type: 'cpu' | 'ram' | 'disc'): string {
let tooltip = '';
let value: number;
switch (type) {
case 'cpu':
value = serviceData.cpuUsage;
tooltip += value + ' / 100 %';
break;
case 'ram':
value = serviceData.memoryUsage;
const memoryUsage = serviceData.memoryUsage;
const totalMemory = serviceData.totalMemory / 1073741824;
const usedMemory = (totalMemory * (memoryUsage / 100)).toFixed(0);
tooltip += usedMemory + ' / ' + totalMemory.toFixed(0) + ' Gb';
break;
case 'disc':
value = serviceData.discUsage;
const discUsage = serviceData.discUsage;
const totalDiscSpace = serviceData.totalDiscSpace / 1073741824;
const usedDisc = (totalDiscSpace * (discUsage / 100)).toFixed(0);
tooltip += usedDisc + ' / ' + totalDiscSpace.toFixed(0) + ' Gb';
break;
}
if (value > 85) {
switch (type) {
case 'cpu':
tooltip += '\n\nCritically high CPU usage. To avoid system failure, optimize system performance.';
break;
case 'ram':
tooltip += '\n\nCritically low reserve of RAM. To avoid system failure, optimize system performance or increase the size of RAM.';
break;
case 'disc':
tooltip += '\n\nCritically low disk space. To avoid data loss, free up or expand the disk space.';
break;
}
} else if (value > 75) {
switch (type) {
case 'cpu':
tooltip += '\n\nRunning high on CPU usage. To avoid system failure, optimize system performance.';
break;
case 'ram':
tooltip += '\n\nRunning low on reserve of RAM. To avoid system failure, optimize system performance or increase the size of RAM.';
break;
case 'disc':
tooltip += '\n\nRunning low on disk space. To avoid data loss, free up or expand the disk space.';
break;
}
}
return tooltip;
}
}
export class SystemInfoDataSource implements DataSource<SystemInfoData> {
private systemDataSubject = new BehaviorSubject<SystemInfoData[]>([]);
private pageDataSubject = new BehaviorSubject<PageData<SystemInfoData>>(emptyPageData<SystemInfoData>());
public pageData$ = this.pageDataSubject.asObservable();
private allSystemData: Observable<Array<SystemInfoData>>;
private telemetrySubscriber: TelemetrySubscriber;
constructor(private telemetryWsService: TelemetryWebsocketService,
private zone: NgZone) {
}
connect(collectionViewer: CollectionViewer): Observable<SystemInfoData[] | ReadonlyArray<SystemInfoData>> {
return this.systemDataSubject.asObservable();
}
disconnect(collectionViewer: CollectionViewer): void {
this.systemDataSubject.complete();
this.pageDataSubject.complete();
if (this.telemetrySubscriber) {
this.telemetrySubscriber.unsubscribe();
this.telemetrySubscriber = null;
}
}
loadData(pageLink: PageLink): void {
this.getAllSystemData().pipe(
map((data) => pageLink.filterData(data)),
catchError(() => of(emptyPageData<SystemInfoData>())),
).subscribe(
(pageData) => {
this.onSystemData(pageData.data);
this.pageDataSubject.next(pageData);
}
);
}
getAllSystemData(): Observable<Array<SystemInfoData>> {
if (!this.allSystemData) {
this.telemetrySubscriber = TelemetrySubscriber.createEntityFilterLatestSubscription(
this.telemetryWsService, { type: AliasFilterType.apiUsageState }, this.zone,
[ { key: 'clusterSystemData', type: EntityKeyType.TIME_SERIES } ]);
this.telemetrySubscriber.subscribe();
this.allSystemData = this.telemetrySubscriber.entityData$.pipe(
map((update) => this.toSystemInfoData(update)),
publishReplay(1),
refCount()
);
}
return this.allSystemData;
}
private toSystemInfoData(entityDataUpdate: EntityDataUpdate): Array<SystemInfoData> {
const entityData = entityDataUpdate.data?.data ? entityDataUpdate.data?.data : entityDataUpdate.update;
if (entityData && entityData.length) {
const latest = entityData[0].latest;
if (latest && latest[EntityKeyType.TIME_SERIES]) {
const latestTs = latest[EntityKeyType.TIME_SERIES];
const tsValue = latestTs.clusterSystemData;
if (tsValue && tsValue.value) {
return JSON.parse(tsValue.value);
}
}
}
return [];
}
protected onSystemData(data: SystemInfoData[]) {
this.systemDataSubject.next(data);
}
isEmpty(): Observable<boolean> {
return this.systemDataSubject.pipe(
map((entities) => !entities.length),
share()
);
}
total(): Observable<number> {
return this.pageDataSubject.pipe(
map((pageData) => pageData.totalElements),
share()
);
}
}

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

@ -0,0 +1,35 @@
///
/// 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 { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SharedModule } from '@app/shared/shared.module';
import { ClusterInfoTableComponent } from '@home/components/widget/lib/home-page/cluster-info-table.component';
@NgModule({
declarations:
[
ClusterInfoTableComponent
],
imports: [
CommonModule,
SharedModule
],
exports: [
ClusterInfoTableComponent
]
})
export class HomePageWidgetsModule { }

2
ui-ngx/src/app/modules/home/components/widget/lib/markdown-widget.component.html

@ -15,7 +15,7 @@
limitations under the License.
-->
<tb-markdown [data]="markdownText" [additionalCompileModules]="[ homeComponentsModule ]"
<tb-markdown [data]="markdownText" [additionalCompileModules]="[ homeComponentsModule, widgetComponentsModule ]"
[additionalStyles]="additionalStyles"
[containerClass]="markdownClass"
[applyDefaultMarkdownStyle]="applyDefaultMarkdownStyle"

3
ui-ngx/src/app/modules/home/components/widget/lib/markdown-widget.component.ts

@ -33,7 +33,7 @@ import {
} from '@core/utils';
import cssjs from '@core/css/css';
import { UtilsService } from '@core/services/utils.service';
import { HOME_COMPONENTS_MODULE_TOKEN } from '@home/components/tokens';
import { HOME_COMPONENTS_MODULE_TOKEN, WIDGET_COMPONENTS_MODULE_TOKEN } from '@home/components/tokens';
import { EntityDataPageLink } from '@shared/models/query/query.models';
interface MarkdownWidgetSettings {
@ -69,6 +69,7 @@ export class MarkdownWidgetComponent extends PageComponent implements OnInit {
constructor(protected store: Store<AppState>,
private utils: UtilsService,
@Inject(HOME_COMPONENTS_MODULE_TOKEN) public homeComponentsModule: Type<any>,
@Inject(WIDGET_COMPONENTS_MODULE_TOKEN) public widgetComponentsModule: Type<any>,
private cd: ChangeDetectorRef) {
super(store);
}

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

@ -42,6 +42,8 @@ import { JsonInputWidgetComponent } from '@home/components/widget/lib/json-input
import { QrCodeWidgetComponent } from '@home/components/widget/lib/qrcode-widget.component';
import { MarkdownWidgetComponent } from '@home/components/widget/lib/markdown-widget.component';
import { SelectEntityDialogComponent } from '@home/components/widget/lib/maps/dialogs/select-entity-dialog.component';
import { HomePageWidgetsModule } from '@home/components/widget/lib/home-page/home-page-widgets.module';
import { WIDGET_COMPONENTS_MODULE_TOKEN } from '@home/components/tokens';
@NgModule({
declarations:
@ -70,6 +72,7 @@ import { SelectEntityDialogComponent } from '@home/components/widget/lib/maps/di
CommonModule,
SharedModule,
RpcWidgetsModule,
HomePageWidgetsModule,
SharedHomeComponentsModule
],
exports: [
@ -79,6 +82,7 @@ import { SelectEntityDialogComponent } from '@home/components/widget/lib/maps/di
EntitiesHierarchyWidgetComponent,
EdgesOverviewWidgetComponent,
RpcWidgetsModule,
HomePageWidgetsModule,
DateRangeNavigatorWidgetComponent,
JsonInputWidgetComponent,
MultipleInputWidgetComponent,
@ -92,7 +96,8 @@ import { SelectEntityDialogComponent } from '@home/components/widget/lib/maps/di
],
providers: [
CustomDialogService,
ImportExportService
ImportExportService,
{provide: WIDGET_COMPONENTS_MODULE_TOKEN, useValue: WidgetComponentsModule }
]
})
export class WidgetComponentsModule {

761
ui-ngx/src/app/modules/home/pages/home-links/sys_admin_home_page.raw

@ -868,7 +868,13 @@
"label": "clusterMode",
"color": "#2196f3",
"settings": {},
"_hash": 0.7272123990942316
"_hash": 0.7272123990942316,
"aggregationType": "NONE",
"units": null,
"decimals": null,
"funcBody": null,
"usePostProcessing": false,
"postFuncBody": ""
}
]
}
@ -903,7 +909,7 @@
"padding": "0px",
"settings": {
"useMarkdownTextFunction": true,
"markdownTextFunction": "let clusterMode = data[0]['clusterMode'];\nlet targetState = 'monolith_system_info';\nif (clusterMode === 'true') {\n targetState = 'cluster_system_info';\n}\nreturn '<div style=\"width: 100%; height: 100%;\"><tb-dashboard-state stateId=\"' + targetState + '\" [ctx]=\"ctx\"></tb-dashboard-state></div>';\n",
"markdownTextFunction": "let html = '<div style=\"width: 100%; height: 100%;\">';\nlet clusterMode = data[0]['clusterMode'];\nif (clusterMode === 'true') {\n html += '<tb-cluster-info-table></tb-cluster-info-table>';\n} else {\n html += '<tb-dashboard-state stateId=\"monolith_system_info\" [ctx]=\"ctx\"></tb-dashboard-state>';\n}\nhtml += '</div>';\nreturn html;",
"applyDefaultMarkdownStyle": false
},
"title": "Transport messages",
@ -915,7 +921,6 @@
"enableFullscreen": false,
"widgetStyle": {
"border": "none",
"borderRadius": "0",
"boxShadow": "none"
},
"titleStyle": {
@ -957,188 +962,42 @@
"color": "#2196f3",
"settings": {},
"_hash": 0.659021918423117
}
]
}
],
"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 class=\"tb-card-title\">\n <div class=\"tb-home-widget-title\">CPU</div>\n </div>\n <div class=\"tb-count\">${cpuUsage:0}%</div>\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\n.tb-count {\n font-style: normal;\n font-weight: 500;\n font-size: 26px;\n line-height: 36px;\n}\n"
},
"title": "CPU",
"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": ".tb-widget-container {\n display: block;\n height: 100%;\n margin-right: 6px;\n}",
"pageSize": 1024,
"noDataDisplayMessage": ""
},
"row": 0,
"col": 0,
"id": "76607897-dfd0-8946-7451-3de6a39e4bbf"
},
"8c2f02bc-ca69-2955-8730-4e945f77da28": {
"isSystemType": true,
"bundleAlias": "cards",
"typeAlias": "markdown_card",
"type": "latest",
"title": "New widget",
"image": null,
"description": null,
"sizeX": 5,
"sizeY": 3.5,
"config": {
"datasources": [
{
"type": "entity",
"name": null,
"entityAliasId": "d9229b29-3f46-de8d-7fe8-eb0c43c75079",
"filterId": null,
"dataKeys": [
},
{
"name": "memoryUsage",
"name": "cpuCount",
"type": "timeseries",
"label": "memoryUsage",
"color": "#2196f3",
"label": "cpuCount",
"color": "#4caf50",
"settings": {},
"_hash": 0.659021918423117,
"_hash": 0.5332753234169949
},
{
"name": "cpuUsage",
"type": "timeseries",
"label": "status",
"color": "#f44336",
"settings": {},
"_hash": 0.025084892962804473,
"aggregationType": "NONE",
"units": null,
"decimals": null,
"funcBody": null,
"usePostProcessing": null,
"postFuncBody": null
}
]
}
],
"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 class=\"tb-card-title\">\n <div class=\"tb-home-widget-title\">RAM</div>\n </div>\n <div class=\"tb-count\">${memoryUsage:0}%</div>\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\n.tb-count {\n font-style: normal;\n font-weight: 500;\n font-size: 26px;\n line-height: 36px;\n}\n"
},
"title": "RAM",
"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": ".tb-widget-container {\n display: block;\n height: 100%;\n margin-right: 6px;\n margin-left: 6px;\n}",
"pageSize": 1024,
"noDataDisplayMessage": ""
},
"row": 0,
"col": 0,
"id": "8c2f02bc-ca69-2955-8730-4e945f77da28"
},
"e3686199-5117-fa5a-d59c-aada6269653e": {
"isSystemType": true,
"bundleAlias": "cards",
"typeAlias": "markdown_card",
"type": "latest",
"title": "New widget",
"image": null,
"description": null,
"sizeX": 5,
"sizeY": 3.5,
"config": {
"datasources": [
{
"type": "entity",
"name": null,
"entityAliasId": "d9229b29-3f46-de8d-7fe8-eb0c43c75079",
"filterId": null,
"dataKeys": [
"usePostProcessing": true,
"postFuncBody": "var status = 'status-ok';\nif (value > 85) {\n status = 'status-critical';\n} else if (value > 75) {\n status = 'status-warn';\n}\nreturn status;"
},
{
"name": "discUsage",
"name": "cpuUsage",
"type": "timeseries",
"label": "discUsage",
"color": "#2196f3",
"label": "statusTooltip",
"color": "#ffc107",
"settings": {},
"_hash": 0.659021918423117,
"_hash": 0.8542003006820891,
"aggregationType": "NONE",
"units": null,
"decimals": null,
"funcBody": null,
"usePostProcessing": null,
"postFuncBody": null
"usePostProcessing": true,
"postFuncBody": "var text = '';\nif (value > 85) {\n text = 'Critically high CPU usage. To avoid system failure, optimize system performance.';\n} else if (value > 75) {\n text = 'Running high on CPU usage. To avoid system failure, optimize system performance.';\n}\nreturn text;"
}
]
}
@ -1173,11 +1032,11 @@
"padding": "16px",
"settings": {
"useMarkdownTextFunction": false,
"markdownTextPattern": "<div class=\"tb-card-content\">\n <div class=\"tb-card-title\">\n <div class=\"tb-home-widget-title\">Disk</div>\n </div>\n <div class=\"tb-count\">${discUsage:0}%</div>\n</div>",
"markdownTextPattern": "<div class=\"tb-card-content\">\n <div class=\"tb-card-title\">\n <div class=\"tb-home-widget-title\">CPU</div>\n <div matTooltip=\"${statusTooltip}\" matTooltipPosition=\"above\" class=\"tb-status ${status}\"></div>\n </div>\n <div class=\"tb-value-container\" matTooltip=\"${cpuUsage:0} / 100 %\" matTooltipPosition=\"above\">\n <span class=\"tb-count\">${cpuUsage:0}%</span>\n <span class=\"tb-total\">| ${cpuCount:0} cores</span>\n </div>\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\n.tb-count {\n font-style: normal;\n font-weight: 500;\n font-size: 26px;\n line-height: 36px;\n}\n"
"markdownCss": ".tb-card-content {\n width: 100%;\n height: 100%;\n display: flex;\n flex-direction: column;\n justify-content: space-between;\n align-items: flex-start;\n}\n\n.tb-card-title {\n width: 100%;\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n}\n\n.tb-status {\n width: 24px;\n height: 24px;\n position: relative;\n}\n\n.tb-status.status-ok {\n background: #F3F6FA;\n border-radius: 4px;\n}\n\n.tb-status:after {\n position: absolute;\n top: 2px;\n left: 2px;\n font-family: 'Material Icons Round';\n font-size: 20px;\n line-height: 1;\n}\n\n.tb-status.status-ok:after {\n content: \"check\";\n color: #198038;\n font-weight: 600;\n}\n\n.tb-status.status-warn:after {\n content: \"warning\";\n color: #FAA405;\n}\n\n.tb-status.status-critical:after {\n content: \"warning\";\n color: #D12730;\n}\n\n.tb-value-container {\n user-select: none;\n}\n\n.tb-count {\n font-style: normal;\n font-weight: 500;\n font-size: 26px;\n line-height: 36px;\n}\n\n.tb-total {\n padding-left: 4px;\n font-weight: 500;\n font-size: 14px;\n line-height: 20px;\n color: rgba(0, 0, 0, 0.54);\n}\n"
},
"title": "Disk",
"title": "CPU",
"showTitleIcon": false,
"iconColor": "rgba(0, 0, 0, 0.87)",
"iconSize": "24px",
@ -1191,13 +1050,13 @@
},
"showLegend": false,
"useDashboardTimewindow": true,
"widgetCss": ".tb-widget-container {\n display: block;\n height: 100%;\n margin-left: 6px;\n}",
"widgetCss": ".tb-widget-container {\n display: block;\n height: 100%;\n margin-right: 6px;\n}",
"pageSize": 1024,
"noDataDisplayMessage": ""
},
"row": 0,
"col": 0,
"id": "e3686199-5117-fa5a-d59c-aada6269653e"
"id": "76607897-dfd0-8946-7451-3de6a39e4bbf"
},
"eace9148-b02a-48fe-1a95-acd9928aa8c5": {
"isSystemType": true,
@ -1380,7 +1239,7 @@
"tickDecimals": 0,
"ticksFormatter": ""
},
"shadowSize": 4,
"shadowSize": 0,
"smoothLines": true,
"comparisonEnabled": false,
"timeForComparison": "previousInterval",
@ -1424,84 +1283,447 @@
"row": 0,
"col": 0,
"id": "eace9148-b02a-48fe-1a95-acd9928aa8c5"
}
},
"states": {
"default": {
"name": "System Administrator Home Page",
"root": true,
"layouts": {
"main": {
"widgets": {
"04a9afdf-fbb4-4eaf-0fcc-a190c6202c62": {
"sizeX": 10,
"sizeY": 4,
"row": 0,
"col": 0
},
"cf1b7a07-2a16-cab7-2528-ef5caba4ac38": {
"sizeX": 10,
"sizeY": 4,
"row": 0,
"col": 10
},
"551a27a0-cd23-a70c-0de2-c97ab6673faa": {
"sizeX": 5,
"sizeY": 3,
"row": 4,
"col": 0
},
"a41682b0-2f23-23e9-0807-e8b47c1c1795": {
"sizeX": 5,
"sizeY": 3,
"row": 4,
"col": 5
},
"1abf533c-aee7-080d-0274-d047e1d5dc63": {
"sizeX": 5,
"sizeY": 3,
"row": 4,
"col": 10
},
"5d02c63b-bcb1-aaf3-f35d-de4dbaca96c2": {
"sizeX": 5,
"sizeY": 3,
"row": 4,
"col": 15
},
"e70b51f8-04dd-985f-be81-d0c76c589d4a": {
"sizeX": 16,
"sizeY": 26,
"row": 0,
"col": 38
},
"d70cc256-4c7b-ee06-9905-b8c5e546605f": {
"sizeX": 18,
"sizeY": 8,
"row": 11,
"col": 20
},
"4b5e47ed-c197-a937-d727-041ba8decec2": {
"sizeX": 18,
"sizeY": 11,
"row": 0,
"col": 20
}
},
"gridSettings": {
"backgroundColor": "#eeeeee",
"columns": 54,
"margin": 12,
"backgroundSizeMode": "100%",
"autoFillHeight": true,
"backgroundImageUrl": null,
"mobileAutoFillHeight": false,
"mobileRowHeight": 70
}
}
}
},
"transport_messages": {
"d755208a-81cf-edb4-5db8-f4859f5359cc": {
"isSystemType": true,
"bundleAlias": "cards",
"typeAlias": "markdown_card",
"type": "latest",
"title": "New widget",
"image": null,
"description": null,
"sizeX": 5,
"sizeY": 3.5,
"config": {
"datasources": [
{
"type": "entity",
"name": null,
"entityAliasId": "d9229b29-3f46-de8d-7fe8-eb0c43c75079",
"filterId": null,
"dataKeys": [
{
"name": "memoryUsage",
"type": "timeseries",
"label": "memoryUsage",
"color": "#2196f3",
"settings": {},
"_hash": 0.659021918423117,
"aggregationType": "NONE",
"units": null,
"decimals": null,
"funcBody": null,
"usePostProcessing": null,
"postFuncBody": null
},
{
"name": "totalMemory",
"type": "timeseries",
"label": "totalMemory",
"color": "#4caf50",
"settings": {},
"_hash": 0.5332753234169949,
"aggregationType": "NONE",
"units": null,
"decimals": null,
"funcBody": null,
"usePostProcessing": true,
"postFuncBody": "return value / 1073741824;"
},
{
"name": "memoryUsage",
"type": "timeseries",
"label": "status",
"color": "#f44336",
"settings": {},
"_hash": 0.025084892962804473,
"aggregationType": "NONE",
"units": null,
"decimals": null,
"funcBody": null,
"usePostProcessing": true,
"postFuncBody": "var status = 'status-ok';\nif (value > 85) {\n status = 'status-critical';\n} else if (value > 75) {\n status = 'status-warn';\n}\nreturn status;"
},
{
"name": "memoryUsage",
"type": "timeseries",
"label": "statusTooltip",
"color": "#ffc107",
"settings": {},
"_hash": 0.8542003006820891,
"aggregationType": "NONE",
"units": null,
"decimals": null,
"funcBody": null,
"usePostProcessing": true,
"postFuncBody": "var text = '';\nif (value > 85) {\n text = 'Critically low reserve of RAM. To avoid system failure,
optimize system performance or increase the size of RAM.';\n} else if (value > 75) {\n text = 'Running low on reserve of RAM. To avoid system failure,
optimize system performance or increase the size of RAM.';\n}\nreturn text;"
}
]
}
],
"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": true,
"markdownTextFunction": "const memoryUsage = data[0]['memoryUsage'];\nconst totalMemory = data[0]['totalMemory'];\nconst usedMemory = (totalMemory * (memoryUsage / 100)).toFixed(0);\n\n\nreturn '<div class=\"tb-card-content\">' +\n '<div class=\"tb-card-title\">' +\n '<div class=\"tb-home-widget-title\">RAM</div>' +\n '<div matTooltip=\"${statusTooltip}\" matTooltipPosition=\"above\" class=\"tb-status ${status}\"></div>' +\n '</div>' +\n '<div class=\"tb-value-container\" matTooltip=\"' + usedMemory +' / ${totalMemory:0} Gb\" matTooltipPosition=\"above\">' +\n '<span class=\"tb-count\">${memoryUsage:0}%</span>' +\n '<span class=\"tb-total\">| ${totalMemory:0} Gb</span>' +\n '</div>' +\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 align-items: flex-start;\n}\n\n.tb-card-title {\n width: 100%;\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n}\n\n.tb-status {\n width: 24px;\n height: 24px;\n position: relative;\n}\n\n.tb-status.status-ok {\n background: #F3F6FA;\n border-radius: 4px;\n}\n\n.tb-status:after {\n position: absolute;\n top: 2px;\n left: 2px;\n font-family: 'Material Icons Round';\n font-size: 20px;\n line-height: 1;\n}\n\n.tb-status.status-ok:after {\n content: \"check\";\n color: #198038;\n font-weight: 600;\n}\n\n.tb-status.status-warn:after {\n content: \"warning\";\n color: #FAA405;\n}\n\n.tb-status.status-critical:after {\n content: \"warning\";\n color: #D12730;\n}\n\n.tb-value-container {\n user-select: none;\n}\n\n.tb-count {\n font-style: normal;\n font-weight: 500;\n font-size: 26px;\n line-height: 36px;\n}\n\n.tb-total {\n padding-left: 4px;\n font-weight: 500;\n font-size: 14px;\n line-height: 20px;\n color: rgba(0, 0, 0, 0.54);\n}\n"
},
"title": "RAM",
"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": ".tb-widget-container {\n display: block;\n height: 100%;\n margin-right: 6px;\n}",
"pageSize": 1024,
"noDataDisplayMessage": ""
},
"row": 0,
"col": 0,
"id": "d755208a-81cf-edb4-5db8-f4859f5359cc"
},
"6ab892b7-0cb8-cb1e-f5d3-679ef8f25bd7": {
"isSystemType": true,
"bundleAlias": "cards",
"typeAlias": "markdown_card",
"type": "latest",
"title": "New widget",
"image": null,
"description": null,
"sizeX": 5,
"sizeY": 3.5,
"config": {
"datasources": [
{
"type": "entity",
"name": null,
"entityAliasId": "d9229b29-3f46-de8d-7fe8-eb0c43c75079",
"filterId": null,
"dataKeys": [
{
"name": "discUsage",
"type": "timeseries",
"label": "discUsage",
"color": "#2196f3",
"settings": {},
"_hash": 0.659021918423117,
"aggregationType": "NONE",
"units": null,
"decimals": null,
"funcBody": null,
"usePostProcessing": null,
"postFuncBody": null
},
{
"name": "totalDiscSpace",
"type": "timeseries",
"label": "totalDiscSpace",
"color": "#4caf50",
"settings": {},
"_hash": 0.5332753234169949,
"aggregationType": "NONE",
"units": null,
"decimals": null,
"funcBody": null,
"usePostProcessing": true,
"postFuncBody": "return value / 1073741824;"
},
{
"name": "discUsage",
"type": "timeseries",
"label": "status",
"color": "#f44336",
"settings": {},
"_hash": 0.025084892962804473,
"aggregationType": "NONE",
"units": null,
"decimals": null,
"funcBody": null,
"usePostProcessing": true,
"postFuncBody": "var status = 'status-ok';\nif (value > 85) {\n status = 'status-critical';\n} else if (value > 75) {\n status = 'status-warn';\n}\nreturn status;"
},
{
"name": "discUsage",
"type": "timeseries",
"label": "statusTooltip",
"color": "#ffc107",
"settings": {},
"_hash": 0.8542003006820891,
"aggregationType": "NONE",
"units": null,
"decimals": null,
"funcBody": null,
"usePostProcessing": true,
"postFuncBody": "var text = '';\nif (value > 85) {\n text = 'Critically low disk space. To avoid data loss, free up or expand the disk space.';\n} else if (value > 75) {\n text = 'Running low on disk space. To avoid data loss, free up or expand the disk space.';\n}\nreturn text;"
}
]
}
],
"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": true,
"markdownTextFunction": "const discUsage = data[0]['discUsage'];\nconst totalDiscSpace = data[0]['totalDiscSpace'];\nconst usedDisc = (totalDiscSpace * (discUsage / 100)).toFixed(0);\n\n\nreturn '<div class=\"tb-card-content\">' +\n '<div class=\"tb-card-title\">' +\n '<div class=\"tb-home-widget-title\">Disk</div>' +\n '<div matTooltip=\"${statusTooltip}\" matTooltipPosition=\"above\" class=\"tb-status ${status}\"></div>' +\n '</div>' +\n '<div class=\"tb-value-container\" matTooltip=\"' + usedDisc +' / ${totalDiscSpace:0} Gb\" matTooltipPosition=\"above\">' +\n '<span class=\"tb-count\">${discUsage:0}%</span>' +\n '<span class=\"tb-total\">| ${totalDiscSpace:0} Gb</span>' +\n '</div>' +\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 align-items: flex-start;\n}\n\n.tb-card-title {\n width: 100%;\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n}\n\n.tb-status {\n width: 24px;\n height: 24px;\n position: relative;\n}\n\n.tb-status.status-ok {\n background: #F3F6FA;\n border-radius: 4px;\n}\n\n.tb-status:after {\n position: absolute;\n top: 2px;\n left: 2px;\n font-family: 'Material Icons Round';\n font-size: 20px;\n line-height: 1;\n}\n\n.tb-status.status-ok:after {\n content: \"check\";\n color: #198038;\n font-weight: 600;\n}\n\n.tb-status.status-warn:after {\n content: \"warning\";\n color: #FAA405;\n}\n\n.tb-status.status-critical:after {\n content: \"warning\";\n color: #D12730;\n}\n\n.tb-value-container {\n user-select: none;\n}\n\n.tb-count {\n font-style: normal;\n font-weight: 500;\n font-size: 26px;\n line-height: 36px;\n}\n\n.tb-total {\n padding-left: 4px;\n font-weight: 500;\n font-size: 14px;\n line-height: 20px;\n color: rgba(0, 0, 0, 0.54);\n}\n"
},
"title": "Disk",
"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": ".tb-widget-container {\n display: block;\n height: 100%;\n margin-right: 6px;\n}",
"pageSize": 1024,
"noDataDisplayMessage": ""
},
"row": 0,
"col": 0,
"id": "6ab892b7-0cb8-cb1e-f5d3-679ef8f25bd7"
},
"8acbf5df-f9fc-114d-216f-86f081aa4779": {
"isSystemType": true,
"bundleAlias": "cards",
"typeAlias": "markdown_card",
"type": "latest",
"title": "New widget",
"image": null,
"description": null,
"sizeX": 5,
"sizeY": 3.5,
"config": {
"datasources": [
{
"type": "entityCount",
"name": null,
"entityAliasId": "ae870700-071f-b3bc-406c-16ba554c5a55",
"filterId": null,
"dataKeys": [
{
"name": "count",
"type": "count",
"label": "tenantProfilesCount",
"color": "#2196f3",
"settings": {},
"_hash": 0.8491768696709192,
"aggregationType": null,
"units": null,
"decimals": null,
"funcBody": null,
"usePostProcessing": null,
"postFuncBody": null
}
]
}
],
"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 style=\"height: 100%;\" fxLayout=\"column\">\n <div class=\"tb-card-header\">\n <div class=\"tb-card-title\">\n <div class=\"tb-home-widget-title\">Functions</div>\n </div>\n <a mat-stroked-button color=\"primary\" \n href=\"https://thingsboard.io/pricing/?product=thingsboard-pe&solution=pe-pay-as-you-go\" target=\"_blank\">Switch to PE</a>\n </div>\n <div fxFlex class=\"tb-card-content\">\n <div fxFlex fxLayout=\"column\" fxLayoutAlign=\"space-around\">\n <div class=\"feature\">Alarms</div>\n <div class=\"feature\">Dashboards</div>\n <div class=\"feature\">Entities & Relations</div>\n <div class=\"feature\">Profiles</div>\n <div class=\"feature\">Advanced features</div>\n </div>\n <div fxFlex fxLayout=\"column\" fxLayoutAlign=\"space-around\">\n <div class=\"feature\">Notification center</div>\n <div class=\"feature\">API usage</div>\n <div class=\"feature\">Customers</div>\n <div [matTooltip]=\"'Only on ThingsBoard\\nProfessional Edition'\" matTooltipPosition=\"above\" class=\"feature pe\">Customers hierarchy</div>\n <div [matTooltip]=\"'Only on ThingsBoard\\nProfessional Edition'\" matTooltipPosition=\"above\" class=\"feature pe\">Roles & Permissions</div>\n </div>\n <div fxFlex fxLayout=\"column\" fxLayoutAlign=\"space-around\">\n <div [matTooltip]=\"'Only on ThingsBoard\\nProfessional Edition'\" matTooltipPosition=\"above\" class=\"feature pe\">Groups</div>\n <div [matTooltip]=\"'Only on ThingsBoard\\nProfessional Edition'\" matTooltipPosition=\"above\" class=\"feature pe\">Integrations</div>\n <div [matTooltip]=\"'Only on ThingsBoard\\nProfessional Edition'\" matTooltipPosition=\"above\" class=\"feature pe\">Solution templates</div>\n <div [matTooltip]=\"'Only on ThingsBoard\\nProfessional Edition'\" matTooltipPosition=\"above\" class=\"feature pe\">Scheduler</div>\n <div [matTooltip]=\"'Only on ThingsBoard\\nProfessional Edition'\" matTooltipPosition=\"above\" class=\"feature pe\">White-labeling</div>\n </div>\n </div>\n</div>",
"applyDefaultMarkdownStyle": false,
"markdownCss": ".tb-card-header {\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n}\n\n.tb-card-content {\n width: 100%;\n height: 100%;\n display: flex;\n flex-direction: row;\n margin-top: 18px;\n}\n\n.feature {\n position: relative;\n cursor: default;\n font-weight: 400;\n font-size: 14px;\n line-height: 20px;\n letter-spacing: 0.2px;\n color: rgba(0, 0, 0, 0.76);\n}\n\n.feature:before {\n content: \"check\";\n font-family: 'Material Icons Round';\n font-size: 20px;\n line-height: 1;\n color: #198038;\n font-weight: 600;\n position: relative;\n background: #F3F6FA;\n border-radius: 4px;\n width: 24px;\n height: 24px;\n margin-right: 8px;\n vertical-align: middle;\n}\n\n.feature.pe:before {\n content: \"close\";\n background: #F4F4F4;\n color: rgba(0, 0, 0, 0.12);\n}\n\n.feature.pe:after {\n position: relative;\n display: inline-block;\n margin-left: 4px;\n vertical-align: baseline;\n top: -0.5em;\n content: \"PE\";\n color: #305680;\n font-weight: 700;\n font-size: 12px;\n line-height: 12px;\n letter-spacing: 0.0015em;\n}\n\n.feature+.feature {\n margin-top: 10px;\n}\n"
},
"title": "Functions",
"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": "8acbf5df-f9fc-114d-216f-86f081aa4779"
}
},
"states": {
"default": {
"name": "System Administrator Home Page",
"root": true,
"layouts": {
"main": {
"widgets": {
"04a9afdf-fbb4-4eaf-0fcc-a190c6202c62": {
"sizeX": 10,
"sizeY": 4,
"row": 0,
"col": 0
},
"cf1b7a07-2a16-cab7-2528-ef5caba4ac38": {
"sizeX": 10,
"sizeY": 4,
"row": 0,
"col": 10
},
"551a27a0-cd23-a70c-0de2-c97ab6673faa": {
"sizeX": 5,
"sizeY": 3,
"row": 4,
"col": 0
},
"a41682b0-2f23-23e9-0807-e8b47c1c1795": {
"sizeX": 5,
"sizeY": 3,
"row": 4,
"col": 5
},
"1abf533c-aee7-080d-0274-d047e1d5dc63": {
"sizeX": 5,
"sizeY": 3,
"row": 4,
"col": 10
},
"5d02c63b-bcb1-aaf3-f35d-de4dbaca96c2": {
"sizeX": 5,
"sizeY": 3,
"row": 4,
"col": 15
},
"e70b51f8-04dd-985f-be81-d0c76c589d4a": {
"sizeX": 16,
"sizeY": 26,
"row": 0,
"col": 38
},
"d70cc256-4c7b-ee06-9905-b8c5e546605f": {
"sizeX": 18,
"sizeY": 8,
"row": 11,
"col": 20
},
"4b5e47ed-c197-a937-d727-041ba8decec2": {
"sizeX": 18,
"sizeY": 11,
"row": 0,
"col": 20
},
"8acbf5df-f9fc-114d-216f-86f081aa4779": {
"sizeX": 27,
"sizeY": 7,
"row": 19,
"col": 11
}
},
"gridSettings": {
"backgroundColor": "#eeeeee",
"columns": 54,
"margin": 12,
"backgroundSizeMode": "100%",
"autoFillHeight": true,
"backgroundImageUrl": null,
"mobileAutoFillHeight": false,
"mobileRowHeight": 70
}
}
}
},
"transport_messages": {
"name": "Transport messages",
"root": false,
"layouts": {
@ -1539,23 +1761,23 @@
"row": 0,
"col": 0
},
"8c2f02bc-ca69-2955-8730-4e945f77da28": {
"eace9148-b02a-48fe-1a95-acd9928aa8c5": {
"sizeX": 24,
"sizeY": 8,
"row": 3,
"col": 0
},
"d755208a-81cf-edb4-5db8-f4859f5359cc": {
"sizeX": 8,
"sizeY": 3,
"row": 0,
"col": 8
},
"e3686199-5117-fa5a-d59c-aada6269653e": {
"6ab892b7-0cb8-cb1e-f5d3-679ef8f25bd7": {
"sizeX": 8,
"sizeY": 3,
"row": 0,
"col": 16
},
"eace9148-b02a-48fe-1a95-acd9928aa8c5": {
"sizeX": 24,
"sizeY": 8,
"row": 3,
"col": 0
}
},
"gridSettings": {
@ -1570,21 +1792,6 @@
}
}
}
},
"cluster_system_info": {
"name": "Cluster System Info",
"root": false,
"layouts": {
"main": {
"widgets": {},
"gridSettings": {
"backgroundColor": "#eeeeee",
"columns": 24,
"margin": 10,
"backgroundSizeMode": "100%"
}
}
}
}
},
"entityAliases": {

23
ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts

@ -25,7 +25,7 @@ import {
AlarmData,
AlarmDataQuery, EntityCountQuery,
EntityData,
EntityDataQuery,
EntityDataQuery, EntityFilter,
EntityKey,
TsValue
} from '@shared/models/query/query.models';
@ -599,6 +599,27 @@ export class TelemetrySubscriber extends WsSubscriber {
return subscriber;
}
public static createEntityFilterLatestSubscription(telemetryService: TelemetryWebsocketService,
entityFilter: EntityFilter, zone: NgZone,
latestKeys: EntityKey[] = null): TelemetrySubscriber {
const entityDataQuery: EntityDataQuery = {
entityFilter,
pageLink: {
pageSize: 1,
page: 0
},
latestValues: latestKeys || []
};
const cmd = new EntityDataCmd();
cmd.query = entityDataQuery;
cmd.latestCmd = {
keys: latestKeys || []
};
const subscriber = new TelemetrySubscriber(telemetryService, zone);
subscriber.subscriptionCommands.push(cmd);
return subscriber;
}
constructor(private telemetryService: TelemetryWebsocketService, protected zone?: NgZone) {
super(telemetryService, zone);
}

BIN
ui-ngx/src/assets/fonts/MaterialIconsRound-Regular.otf

Binary file not shown.

16
ui-ngx/src/assets/fonts/material-icons.css

@ -22,8 +22,24 @@
url(MaterialIcons-Regular.ttf) format('truetype');
}
@font-face {
font-family: 'Material Icons Round';
font-style: normal;
font-weight: 400;
src: local('Material Icons Round'),
local('MaterialIconsRound-Regular'),
url(MaterialIconsRound-Regular.otf) format('truetype');
}
.material-icons {
font-family: 'Material Icons';
}
.material-icons-round {
font-family: 'Material Icons Round';
}
.material-icons, .material-icons-round {
font-weight: normal;
font-style: normal;
font-size: 24px; /* Preferred icon size */

1
ui-ngx/src/index.html

@ -25,6 +25,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="thingsboard.ico">
<link rel="preload" href="assets/fonts/MaterialIcons-Regular.ttf" as="font" type="font/ttf" crossorigin="anonymous" />
<link rel="preload" href="assets/fonts/MaterialIconsRound-Regular.otf" as="font" type="font/otf" crossorigin="anonymous" />
<link rel="stylesheet" href="assets/fonts/material-icons.css" />
<style type="text/css">

Loading…
Cancel
Save