Browse Source

UI: Update sys admin home page. Add translations.

pull/8359/head
Igor Kulikov 4 years ago
parent
commit
855c69e0d6
  1. 21
      application/src/main/data/json/system/widget_bundles/home_page_widgets.json
  2. 2
      common/data/src/main/java/org/thingsboard/server/common/data/settings/UserSettingsType.java
  3. 11
      ui-ngx/src/app/core/http/user-settings.service.ts
  4. 2
      ui-ngx/src/app/modules/home/components/filter/filters-dialog.component.html
  5. 2
      ui-ngx/src/app/modules/home/components/widget/lib/home-page/add-doc-link-dialog.component.html
  6. 12
      ui-ngx/src/app/modules/home/components/widget/lib/home-page/cluster-info-table.component.html
  7. 14
      ui-ngx/src/app/modules/home/components/widget/lib/home-page/cluster-info-table.component.ts
  8. 14
      ui-ngx/src/app/modules/home/components/widget/lib/home-page/configured-features.component.html
  9. 6
      ui-ngx/src/app/modules/home/components/widget/lib/home-page/configured-features.component.ts
  10. 8
      ui-ngx/src/app/modules/home/components/widget/lib/home-page/doc-link.component.html
  11. 6
      ui-ngx/src/app/modules/home/components/widget/lib/home-page/doc-links-widget.component.html
  12. 4
      ui-ngx/src/app/modules/home/components/widget/lib/home-page/edit-doc-links-dialog.component.html
  13. 34
      ui-ngx/src/app/modules/home/components/widget/lib/home-page/getting-started-completed-dialog.component.html
  14. 62
      ui-ngx/src/app/modules/home/components/widget/lib/home-page/getting-started-completed-dialog.component.scss
  15. 46
      ui-ngx/src/app/modules/home/components/widget/lib/home-page/getting-started-completed-dialog.component.ts
  16. 101
      ui-ngx/src/app/modules/home/components/widget/lib/home-page/getting-started-widget.component.html
  17. 118
      ui-ngx/src/app/modules/home/components/widget/lib/home-page/getting-started-widget.component.scss
  18. 134
      ui-ngx/src/app/modules/home/components/widget/lib/home-page/getting-started-widget.component.ts
  19. 12
      ui-ngx/src/app/modules/home/components/widget/lib/home-page/home-page-widgets.module.ts
  20. 16
      ui-ngx/src/app/modules/home/components/widget/lib/home-page/version-info.component.html
  21. 3
      ui-ngx/src/app/modules/home/components/widget/lib/home-page/version-info.component.scss
  22. 2
      ui-ngx/src/app/modules/home/components/widget/lib/settings/home-page/doc-links-widget-settings.component.html
  23. 164
      ui-ngx/src/app/modules/home/pages/home-links/sys_admin_home_page.raw
  24. 8
      ui-ngx/src/app/shared/models/user-settings.models.ts
  25. 109
      ui-ngx/src/assets/locale/locale.constant-en_US.json

21
application/src/main/data/json/system/widget_bundles/home_page_widgets.json

@ -24,7 +24,26 @@
"settingsSchema": "", "settingsSchema": "",
"dataKeySettingsSchema": "", "dataKeySettingsSchema": "",
"settingsDirective": "tb-doc-links-widget-settings", "settingsDirective": "tb-doc-links-widget-settings",
"defaultConfig": "{\"datasources\":[{\"type\":\"static\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"columns\":3},\"title\":\"Documentation links\",\"dropShadow\":true}" "defaultConfig": "{\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"columns\":3},\"title\":\"Documentation links\",\"dropShadow\":true}"
}
},
{
"alias": "getting_started",
"name": "Getting started",
"image": null,
"description": null,
"descriptor": {
"type": "static",
"sizeX": 7.5,
"sizeY": 6.5,
"resources": [],
"templateHtml": "<tb-getting-started-widget\n [ctx]=\"ctx\">\n</tb-getting-started-widget>\n",
"templateCss": "",
"controllerScript": "self.onInit = function() {\n}",
"settingsSchema": "",
"dataKeySettingsSchema": "",
"settingsDirective": "",
"defaultConfig": "{\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Getting started\",\"dropShadow\":true}"
} }
} }
] ]

2
common/data/src/main/java/org/thingsboard/server/common/data/settings/UserSettingsType.java

@ -19,7 +19,7 @@ import lombok.Getter;
public enum UserSettingsType { public enum UserSettingsType {
GENERAL, VISITED_DASHBOARDS(true), QUICK_LINKS, DOC_LINKS, DASHBOARDS; GENERAL, VISITED_DASHBOARDS(true), QUICK_LINKS, DOC_LINKS, DASHBOARDS, GETTING_STARTED;
@Getter @Getter
private final boolean reserved; private final boolean reserved;

11
ui-ngx/src/app/core/http/user-settings.service.ts

@ -18,6 +18,7 @@ import { Injectable } from '@angular/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { import {
DocumentationLink, DocumentationLinks, DocumentationLink, DocumentationLinks,
GettingStarted,
initialUserSettings, initialUserSettings,
UserSettings, UserSettings,
UserSettingsType UserSettingsType
@ -66,4 +67,14 @@ export class UserSettingsService {
defaultHttpOptionsFromConfig(config)); defaultHttpOptionsFromConfig(config));
} }
public getGettingStarted(config?: RequestConfig): Observable<GettingStarted> {
return this.http.get<GettingStarted>(`/api/user/settings/${UserSettingsType.GETTING_STARTED}`,
defaultHttpOptionsFromConfig(config));
}
public updateGettingStarted(gettingStarted: GettingStarted, config?: RequestConfig): Observable<void> {
return this.http.put<void>(`/api/user/settings/${UserSettingsType.GETTING_STARTED}`, gettingStarted,
defaultHttpOptionsFromConfig(config));
}
} }

2
ui-ngx/src/app/modules/home/components/filter/filters-dialog.component.html

@ -69,7 +69,7 @@
style="min-width: 48px;" style="min-width: 48px;"
type="button" type="button"
(click)="duplicateFilter($index)" (click)="duplicateFilter($index)"
matTooltip="{{ 'filter.duplicate-filter' | translate }}" matTooltip="{{ 'filter.duplicate-filter-action' | translate }}"
matTooltipPosition="above"> matTooltipPosition="above">
<mat-icon>content_copy</mat-icon> <mat-icon>content_copy</mat-icon>
</button> </button>

2
ui-ngx/src/app/modules/home/components/widget/lib/home-page/add-doc-link-dialog.component.html

@ -17,7 +17,7 @@
--> -->
<form class="tb-add-doc-link-dialog" [formGroup]="addDocLinkFormGroup"> <form class="tb-add-doc-link-dialog" [formGroup]="addDocLinkFormGroup">
<mat-toolbar style="background: transparent;"> <mat-toolbar style="background: transparent;">
<h2>Add documentation link</h2> <h2 translate>widgets.documentation.add-link-title</h2>
<span fxFlex></span> <span fxFlex></span>
<button mat-icon-button <button mat-icon-button
(click)="cancel()" (click)="cancel()"

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

@ -19,7 +19,7 @@
[matSortDirection]="pageLink.sortDirection()" matSortDisableClear> [matSortDirection]="pageLink.sortDirection()" matSortDisableClear>
<ng-container matColumnDef="serviceId"> <ng-container matColumnDef="serviceId">
<mat-header-cell *matHeaderCellDef style="width: 35%;" mat-sort-header> <mat-header-cell *matHeaderCellDef style="width: 35%;" mat-sort-header>
Service id {{ 'widgets.cluster-info.service-id' | translate }}
</mat-header-cell> </mat-header-cell>
<mat-cell *matCellDef="let serviceData"> <mat-cell *matCellDef="let serviceData">
{{ serviceData.serviceId }} {{ serviceData.serviceId }}
@ -27,7 +27,7 @@
</ng-container> </ng-container>
<ng-container matColumnDef="serviceType"> <ng-container matColumnDef="serviceType">
<mat-header-cell *matHeaderCellDef style="width: 35%;" mat-sort-header> <mat-header-cell *matHeaderCellDef style="width: 35%;" mat-sort-header>
Service type {{ 'widgets.cluster-info.service-type' | translate }}
</mat-header-cell> </mat-header-cell>
<mat-cell *matCellDef="let serviceData"> <mat-cell *matCellDef="let serviceData">
{{ serviceData.serviceType }} {{ serviceData.serviceType }}
@ -35,7 +35,7 @@
</ng-container> </ng-container>
<ng-container matColumnDef="cpuUsage"> <ng-container matColumnDef="cpuUsage">
<mat-header-cell *matHeaderCellDef style="width: 10%;" mat-sort-header> <mat-header-cell *matHeaderCellDef style="width: 10%;" mat-sort-header>
CPU {{ 'widgets.system-info.cpu' | translate }}
</mat-header-cell> </mat-header-cell>
<mat-cell *matCellDef="let serviceData" <mat-cell *matCellDef="let serviceData"
[matTooltip]="infoTooltip(serviceData, 'cpu')" [matTooltip]="infoTooltip(serviceData, 'cpu')"
@ -47,7 +47,7 @@
</ng-container> </ng-container>
<ng-container matColumnDef="memoryUsage"> <ng-container matColumnDef="memoryUsage">
<mat-header-cell *matHeaderCellDef style="width: 10%;" mat-sort-header> <mat-header-cell *matHeaderCellDef style="width: 10%;" mat-sort-header>
RAM {{ 'widgets.system-info.ram' | translate }}
</mat-header-cell> </mat-header-cell>
<mat-cell *matCellDef="let serviceData" <mat-cell *matCellDef="let serviceData"
[matTooltip]="infoTooltip(serviceData, 'ram')" [matTooltip]="infoTooltip(serviceData, 'ram')"
@ -59,7 +59,7 @@
</ng-container> </ng-container>
<ng-container matColumnDef="discUsage"> <ng-container matColumnDef="discUsage">
<mat-header-cell *matHeaderCellDef style="width: 10%;" mat-sort-header> <mat-header-cell *matHeaderCellDef style="width: 10%;" mat-sort-header>
Disk {{ 'widgets.system-info.disk' | translate }}
</mat-header-cell> </mat-header-cell>
<mat-cell *matCellDef="let serviceData" <mat-cell *matCellDef="let serviceData"
[matTooltip]="infoTooltip(serviceData, 'disc')" [matTooltip]="infoTooltip(serviceData, 'disc')"
@ -74,4 +74,4 @@
</table> </table>
<span [fxShow]="dataSource.isEmpty() | async" <span [fxShow]="dataSource.isEmpty() | async"
fxLayoutAlign="center center" fxLayoutAlign="center center"
class="no-data-found">No data</span> class="no-data-found" translate>widgets.cluster-info.no-data</span>

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

@ -29,6 +29,7 @@ import { TelemetryWebsocketService } from '@core/ws/telemetry-websocket.service'
import { EntityDataUpdate, TelemetrySubscriber } from '@shared/models/telemetry/telemetry.models'; import { EntityDataUpdate, TelemetrySubscriber } from '@shared/models/telemetry/telemetry.models';
import { AliasFilterType } from '@shared/models/alias.models'; import { AliasFilterType } from '@shared/models/alias.models';
import { EntityKeyType } from '@shared/models/query/query.models'; import { EntityKeyType } from '@shared/models/query/query.models';
import { TranslateService } from '@ngx-translate/core';
export interface SystemInfoData { export interface SystemInfoData {
serviceId: string; serviceId: string;
@ -58,6 +59,7 @@ export class ClusterInfoTableComponent extends PageComponent implements OnInit,
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
private telemetryWsService: TelemetryWebsocketService, private telemetryWsService: TelemetryWebsocketService,
private translate: TranslateService,
private zone: NgZone) { private zone: NgZone) {
super(store); super(store);
} }
@ -118,25 +120,25 @@ export class ClusterInfoTableComponent extends PageComponent implements OnInit,
if (value > 85) { if (value > 85) {
switch (type) { switch (type) {
case 'cpu': case 'cpu':
tooltip += '\n\nCritically high CPU usage. To avoid system failure, optimize system performance.'; tooltip += '\n\n' + this.translate.instant('widgets.system-info.cpu-critical-text');
break; break;
case 'ram': case 'ram':
tooltip += '\n\nCritically low reserve of RAM. To avoid system failure, optimize system performance or increase the size of RAM.'; tooltip += '\n\n' + this.translate.instant('widgets.system-info.ram-critical-text');
break; break;
case 'disc': case 'disc':
tooltip += '\n\nCritically low disk space. To avoid data loss, free up or expand the disk space.'; tooltip += '\n\n' + this.translate.instant('widgets.system-info.disk-critical-text');
break; break;
} }
} else if (value > 75) { } else if (value > 75) {
switch (type) { switch (type) {
case 'cpu': case 'cpu':
tooltip += '\n\nRunning high on CPU usage. To avoid system failure, optimize system performance.'; tooltip += '\n\n' + this.translate.instant('widgets.system-info.cpu-warning-text');
break; break;
case 'ram': case 'ram':
tooltip += '\n\nRunning low on reserve of RAM. To avoid system failure, optimize system performance or increase the size of RAM.'; tooltip += '\n\n' + this.translate.instant('widgets.system-info.ram-warning-text');
break; break;
case 'disc': case 'disc':
tooltip += '\n\nRunning low on disk space. To avoid data loss, free up or expand the disk space.'; tooltip += '\n\n' + this.translate.instant('widgets.system-info.disk-warning-text');
break; break;
} }
} }

14
ui-ngx/src/app/modules/home/components/widget/lib/home-page/configured-features.component.html

@ -16,9 +16,9 @@
--> -->
<div class="tb-card-content" fxLayout="column" fxLayoutGap="8px"> <div class="tb-card-content" fxLayout="column" fxLayoutGap="8px">
<div class="tb-title">Configured features <div class="tb-title">{{ 'widgets.configured-features.title' | translate }}
<mat-icon <mat-icon
matTooltip="Status of features that require configuration" matTooltip="{{ 'widgets.configured-features.info' | translate }}"
matTooltipPosition="above" matTooltipPosition="above"
class="tb-info-icon">info</mat-icon> class="tb-info-icon">info</mat-icon>
</div> </div>
@ -29,7 +29,7 @@
matTooltipPosition="above" matTooltipPosition="above"
[ngClass]="{'configured': featuresInfo?.emailEnabled}" [ngClass]="{'configured': featuresInfo?.emailEnabled}"
routerLink="/settings/outgoing-mail"> routerLink="/settings/outgoing-mail">
<div class="tb-feature-text">Email</div> <div class="tb-feature-text" translate>widgets.configured-features.email-feature</div>
</a> </a>
</mat-grid-tile> </mat-grid-tile>
<mat-grid-tile class="tb-feature-tile" colspan="3"> <mat-grid-tile class="tb-feature-tile" colspan="3">
@ -38,7 +38,7 @@
matTooltipPosition="above" matTooltipPosition="above"
[ngClass]="{'configured': featuresInfo?.smsEnabled}" [ngClass]="{'configured': featuresInfo?.smsEnabled}"
routerLink="/settings/notifications"> routerLink="/settings/notifications">
<div class="tb-feature-text">SMS</div> <div class="tb-feature-text" translate>widgets.configured-features.sms-feature</div>
</a> </a>
</mat-grid-tile> </mat-grid-tile>
<mat-grid-tile class="tb-feature-tile" colspan="2"> <mat-grid-tile class="tb-feature-tile" colspan="2">
@ -47,7 +47,7 @@
matTooltipPosition="above" matTooltipPosition="above"
[ngClass]="{'configured': featuresInfo?.notificationEnabled}" [ngClass]="{'configured': featuresInfo?.notificationEnabled}"
routerLink="/settings/notifications"> routerLink="/settings/notifications">
<div class="tb-feature-text">Slack</div> <div class="tb-feature-text" translate>widgets.configured-features.slack-feature</div>
</a> </a>
</mat-grid-tile> </mat-grid-tile>
<mat-grid-tile class="tb-feature-tile" colspan="2"> <mat-grid-tile class="tb-feature-tile" colspan="2">
@ -56,7 +56,7 @@
matTooltipPosition="above" matTooltipPosition="above"
[ngClass]="{'configured': featuresInfo?.oauthEnabled}" [ngClass]="{'configured': featuresInfo?.oauthEnabled}"
routerLink="/security-settings/oauth2"> routerLink="/security-settings/oauth2">
<div class="tb-feature-text">OAuth 2</div> <div class="tb-feature-text" translate>widgets.configured-features.oauth2-feature</div>
</a> </a>
</mat-grid-tile> </mat-grid-tile>
<mat-grid-tile class="tb-feature-tile" colspan="2"> <mat-grid-tile class="tb-feature-tile" colspan="2">
@ -65,7 +65,7 @@
matTooltipPosition="above" matTooltipPosition="above"
[ngClass]="{'configured': featuresInfo?.twoFaEnabled}" [ngClass]="{'configured': featuresInfo?.twoFaEnabled}"
routerLink="/security-settings/2fa"> routerLink="/security-settings/2fa">
<div class="tb-feature-text">2FA</div> <div class="tb-feature-text" translate>widgets.configured-features.2fa-feature</div>
</a> </a>
</mat-grid-tile> </mat-grid-tile>
</mat-grid-list> </mat-grid-list>

6
ui-ngx/src/app/modules/home/components/widget/lib/home-page/configured-features.component.ts

@ -24,6 +24,7 @@ import { getCurrentAuthUser } from '@core/auth/auth.selectors';
import { Authority } from '@shared/models/authority.enum'; import { Authority } from '@shared/models/authority.enum';
import { of, Subscription } from 'rxjs'; import { of, Subscription } from 'rxjs';
import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout'; import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout';
import { TranslateService } from '@ngx-translate/core';
@Component({ @Component({
selector: 'tb-configured-features', selector: 'tb-configured-features',
@ -42,6 +43,7 @@ export class ConfiguredFeaturesComponent extends PageComponent implements OnInit
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
private cd: ChangeDetectorRef, private cd: ChangeDetectorRef,
private adminService: AdminService, private adminService: AdminService,
private translate: TranslateService,
private breakpointObserver: BreakpointObserver) { private breakpointObserver: BreakpointObserver) {
super(store); super(store);
} }
@ -81,9 +83,9 @@ export class ConfiguredFeaturesComponent extends PageComponent implements OnInit
featureTooltip(configured: boolean): string { featureTooltip(configured: boolean): string {
if (configured) { if (configured) {
return 'Feature is configured.\nClick to setup'; return this.translate.instant('widgets.configured-features.feature-configured');
} else { } else {
return 'Feature is not configured.\nClick to setup'; return this.translate.instant('widgets.configured-features.feature-not-configured');
} }
} }
} }

8
ui-ngx/src/app/modules/home/components/widget/lib/home-page/doc-link.component.html

@ -19,17 +19,17 @@
<div fxFlex class="tb-edit-doc-link" [ngClass]="{'edit-mode': editMode}" [formGroup]="editDocLinkFormGroup" fxLayout="column"> <div fxFlex class="tb-edit-doc-link" [ngClass]="{'edit-mode': editMode}" [formGroup]="editDocLinkFormGroup" fxLayout="column">
<tb-material-icon-select required formControlName="icon"></tb-material-icon-select> <tb-material-icon-select required formControlName="icon"></tb-material-icon-select>
<mat-form-field fxFlex class="mat-block"> <mat-form-field fxFlex class="mat-block">
<mat-label>Name</mat-label> <mat-label translate>widgets.documentation.name</mat-label>
<input required matInput formControlName="name"> <input required matInput formControlName="name">
<mat-error *ngIf="editDocLinkFormGroup.get('name').hasError('required')"> <mat-error *ngIf="editDocLinkFormGroup.get('name').hasError('required')">
Name is required. {{ 'widgets.documentation.name-required' | translate }}
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
<mat-form-field fxFlex class="mat-block"> <mat-form-field fxFlex class="mat-block">
<mat-label>Link</mat-label> <mat-label translate>widgets.documentation.link</mat-label>
<input required matInput formControlName="link"> <input required matInput formControlName="link">
<mat-error *ngIf="editDocLinkFormGroup.get('link').hasError('required')"> <mat-error *ngIf="editDocLinkFormGroup.get('link').hasError('required')">
Link is required. {{ 'widgets.documentation.link-required' | translate }}
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
<div *ngIf="addMode" fxLayout="row" fxLayoutAlign="end" fxLayoutGap="8px"> <div *ngIf="addMode" fxLayout="row" fxLayoutAlign="end" fxLayoutGap="8px">

6
ui-ngx/src/app/modules/home/components/widget/lib/home-page/doc-links-widget.component.html

@ -17,9 +17,9 @@
--> -->
<div class="tb-card-content" fxLayout="column" fxLayoutGap="8px"> <div class="tb-card-content" fxLayout="column" fxLayoutGap="8px">
<div class="tb-card-header"> <div class="tb-card-header">
<a class="tb-title-link" href="https://thingsboard.io/docs/" target="_blank">Documentation</a> <a class="tb-title-link" href="https://thingsboard.io/docs/" target="_blank">{{ 'widgets.documentation.title' | translate }}</a>
<button class="tb-title-icon" <button class="tb-title-icon"
matTooltip="Edit" matTooltip="{{ 'action.edit' | translate }}"
matTooltipPosition="above" matTooltipPosition="above"
mat-icon-button mat-icon-button
(click)="edit()"> (click)="edit()">
@ -40,7 +40,7 @@
</mat-grid-tile> </mat-grid-tile>
<mat-grid-tile class="tb-docs-tile"> <mat-grid-tile class="tb-docs-tile">
<div fxFlex class="tb-add-doc-button" <div fxFlex class="tb-add-doc-button"
matTooltip="Add link" matTooltip="{{ 'widgets.documentation.add-link' | translate }}"
matTooltipPosition="above" matTooltipPosition="above"
(click)="addLink()"> (click)="addLink()">
<mat-icon class="tb-add-icon">add</mat-icon> <mat-icon class="tb-add-icon">add</mat-icon>

4
ui-ngx/src/app/modules/home/components/widget/lib/home-page/edit-doc-links-dialog.component.html

@ -17,7 +17,7 @@
--> -->
<form class="tb-edit-doc-links-dialog" [formGroup]="editDocLinksFormGroup"> <form class="tb-edit-doc-links-dialog" [formGroup]="editDocLinksFormGroup">
<mat-toolbar style="background: transparent;"> <mat-toolbar style="background: transparent;">
<h2>Documentation</h2> <h2 translate>widgets.documentation.title</h2>
<span fxFlex></span> <span fxFlex></span>
<button mat-icon-button <button mat-icon-button
(click)="close()" (click)="close()"
@ -49,7 +49,7 @@
</div> </div>
<div mat-dialog-actions> <div mat-dialog-actions>
<div *ngIf="!editMode && !addMode" fxFlex class="tb-add-doc-button" <div *ngIf="!editMode && !addMode" fxFlex class="tb-add-doc-button"
matTooltip="Add link" matTooltip="{{ 'widgets.documentation.add-link' | translate }}"
matTooltipPosition="above" matTooltipPosition="above"
(click)="addLink()"> (click)="addLink()">
<mat-icon class="tb-add-icon">add</mat-icon> <mat-icon class="tb-add-icon">add</mat-icon>

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

@ -0,0 +1,34 @@
<!--
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.
-->
<div class="tb-getting-started-completed-dialog-container" fxLayout="column">
<div fxLayout="row" fxLayoutAlign="start center" style="height: 64px; padding: 8px;">
<span fxFlex></span>
<button mat-icon-button (click)="close()" type="button">
<mat-icon class="material-icons">close</mat-icon>
</button>
</div>
<div fxLayout="row" fxLayoutAlign="center center" style="height: 70px;">
<div class="tb-done-container" fxLayout="row" fxLayoutAlign="center center">
<mat-icon class="tb-done-icon" color="primary">done</mat-icon>
</div>
</div>
<div fxLayout="column" fxLayoutAlign="start center" style="padding-top: 30px;">
<div class="tb-done-welcome-title" translate>widgets.getting-started.done-welcome-title</div>
<div class="tb-done-welcome-text" translate>widgets.getting-started.done-welcome-text</div>
</div>
</div>

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

@ -0,0 +1,62 @@
/**
* 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.
*/
.tb-getting-started-completed-dialog {
.mat-mdc-dialog-container .mdc-dialog__surface {
border-radius: 8px;
background-image: url(/assets/home/greetings_bg.svg);
background-repeat: no-repeat;
}
.tb-getting-started-completed-dialog-container {
width: 500px;
height: 290px;
}
.tb-done-container {
width: 70px;
height: 70px;
border-radius: 50%;
background: rgba(0, 0, 0, 0.05);
}
.tb-done-icon {
width: 62px;
height: 62px;
line-height: 56px;
font-size: 36px;
text-align: center;
border-radius: 50%;
border: 2px solid #305680;
background: #FFFFFF;
}
.tb-done-welcome-title {
font-weight: 600;
font-size: 20px;
line-height: 24px;
letter-spacing: 0.1px;
color: rgba(0, 0, 0, 0.87);
padding-bottom: 8px;
}
.tb-done-welcome-text {
font-weight: 400;
font-size: 16px;
line-height: 24px;
letter-spacing: 0.15px;
color: rgba(0, 0, 0, 0.54);
}
}

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

@ -0,0 +1,46 @@
///
/// 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 { Component, ViewEncapsulation } from '@angular/core';
import { MatDialogRef } from '@angular/material/dialog';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { UntypedFormGroup } from '@angular/forms';
import { DialogComponent } from '@shared/components/dialog.component';
import { Router } from '@angular/router';
@Component({
selector: 'tb-getting-started-completed-dialog',
templateUrl: './getting-started-completed-dialog.component.html',
styleUrls: ['./getting-started-completed-dialog.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class GettingStartedCompletedDialogComponent extends
DialogComponent<GettingStartedCompletedDialogComponent, void> {
addDocLinkFormGroup: UntypedFormGroup;
constructor(protected store: Store<AppState>,
protected router: Router,
public dialogRef: MatDialogRef<GettingStartedCompletedDialogComponent, void>) {
super(store, router, dialogRef);
dialogRef.addPanelClass('tb-getting-started-completed-dialog');
}
close(): void {
this.dialogRef.close();
}
}

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

@ -0,0 +1,101 @@
<!--
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.
-->
<div class="tb-title" translate>widgets.getting-started.get-started</div>
<div class="tb-get-started-container">
<mat-stepper #matStepper class="tb-get-started" orientation="vertical" [linear]="false"
[selectedIndex]="gettingStarted.lastSelectedIndex"
(selectionChange)="updateSelectedIndex($event)">
<ng-template matStepperIcon="edit" let-index="index">
{{ index + 1 }}
</ng-template>
<ng-container [ngSwitch]="authUser.authority">
<ng-template [ngSwitchCase]="authority.SYS_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.sys-admin.step1.title</div>
<a *ngIf="matStepper.selectedIndex === 0" mat-button color="primary" routerLink="/tenants">{{ 'tenant.tenants' | translate }}</a>
</div>
</ng-template>
<div [innerHTML]="'widgets.getting-started.sys-admin.step1.content' | translate | safe: 'html'"></div>
<a mat-stroked-button color="primary" href="https://thingsboard.io/docs/user-guide/ui/tenants/#adding-a-tenant" target="_blank">
<mat-icon>description</mat-icon>{{ 'widgets.getting-started.sys-admin.step1.how-to-create-tenant' | 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.sys-admin.step2.title</div>
<a *ngIf="matStepper.selectedIndex === 1" mat-button color="primary" routerLink="/settings/outgoing-mail">{{ 'admin.settings' | translate }}</a>
</div>
</ng-template>
<div [innerHTML]="'widgets.getting-started.sys-admin.step2.content' | translate | safe: 'html'"></div>
<a mat-stroked-button color="primary" href="https://thingsboard.io/docs/user-guide/ui/mail-settings/" target="_blank">
<mat-icon>description</mat-icon>{{ 'widgets.getting-started.sys-admin.step2.how-to-configure-mail-server' | 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.sys-admin.step3.title</div>
<a *ngIf="matStepper.selectedIndex === 2" mat-button color="primary" routerLink="/settings/notifications">{{ 'admin.settings' | translate }}</a>
</div>
</ng-template>
<div [innerHTML]="'widgets.getting-started.sys-admin.step3.content' | translate | safe: 'html'"></div>
<a mat-stroked-button color="primary" href="https://thingsboard.io/docs/user-guide/ui/sms-provider-settings/" target="_blank">
<mat-icon>description</mat-icon>{{ 'widgets.getting-started.sys-admin.step3.how-to-configure-sms-provider' | 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.sys-admin.step4.title</div>
<a *ngIf="matStepper.selectedIndex === 3" mat-button color="primary" routerLink="/security-settings/2fa">{{ 'admin.settings' | translate }}</a>
</div>
</ng-template>
<div [innerHTML]="'widgets.getting-started.sys-admin.step4.content' | translate | safe: 'html'"></div>
<a mat-stroked-button color="primary" href="https://thingsboard.io/docs/user-guide/two-factor-authentication/" target="_blank">
<mat-icon>description</mat-icon>{{ 'widgets.getting-started.sys-admin.step4.how-to-configure-2fa' | 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.sys-admin.step5.title</div>
<a *ngIf="matStepper.selectedIndex === 4" mat-button color="primary" routerLink="/security-settings/oauth2">{{ 'admin.settings' | translate }}</a>
</div>
</ng-template>
<div [innerHTML]="'widgets.getting-started.sys-admin.step5.content' | translate | safe: 'html'"></div>
<a mat-stroked-button color="primary" href="https://thingsboard.io/docs/user-guide/oauth-2-support/" target="_blank">
<mat-icon>description</mat-icon>{{ 'widgets.getting-started.sys-admin.step5.how-to-configure-oauth2' | 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.sys-admin.step6.title</div>
<a *ngIf="matStepper.selectedIndex === 5" mat-button color="primary" routerLink="/notification/rules">{{ 'admin.settings' | translate }}</a>
</div>
</ng-template>
<div [innerHTML]="'widgets.getting-started.sys-admin.step6.content' | translate | safe: 'html'"></div>
<a mat-stroked-button color="primary" href="https://thingsboard.io/docs/user-guide/to-do/" target="_blank">
<mat-icon>description</mat-icon>{{ 'widgets.getting-started.sys-admin.step6.how-to-configure-notifications' | translate }}</a>
</mat-step>
</ng-template>
</ng-container>
</mat-stepper>
<div *ngIf="allCompleted" fxLayout="column" fxLayoutAlign="center center" style="padding-top: 24px;">
<button (click)="gettingStartedCompleted()" mat-flat-button color="primary">
<mat-icon>rocket_launch</mat-icon>{{ 'widgets.getting-started.finish' | translate }}</button>
</div>
</div>

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

@ -0,0 +1,118 @@
/**
* 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 {
display: flex;
flex-direction: column;
height: 100%;
.tb-title {
font-weight: 600;
font-size: 20px;
line-height: 24px;
letter-spacing: 0.1px;
color: rgba(0, 0, 0, 0.76);
padding-bottom: 24px;
@media screen and (min-width: 960px) and (max-width: 1819px) {
font-weight: 500;
font-size: 14px;
line-height: 20px;
letter-spacing: 0.25px;
padding-bottom: 20px;
}
}
.tb-get-started-container {
flex: 1;
overflow: auto;
}
}
:host ::ng-deep {
.tb-get-started .mat-vertical-stepper-header {
border-radius: 12px;
padding: 12px;
height: auto;
}
.tb-get-started .mat-vertical-stepper-header[aria-selected="true"] {
background: rgba(0, 0, 0, 0.03);
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
.tb-get-started .mat-vertical-content-container {
margin-left: 0;
border-bottom-left-radius: 12px;
border-bottom-right-radius: 12px;
background: rgba(0, 0, 0, 0.03);
}
.tb-get-started .mat-stepper-vertical-line::before {
content: none;
}
.tb-get-started .mat-step-label {
width: 100%;
white-space: inherit;
font-weight: 400;
font-size: 14px;
line-height: 20px;
color: rgba(0, 0, 0, 0.76);
}
.tb-get-started .mat-step-label.mat-step-label-selected {
font-weight: 500;
font-size: 16px;
line-height: 24px;
color: rgba(0, 0, 0, 0.87);
}
.tb-get-started .mat-vertical-content p {
font-style: normal;
font-weight: 400;
font-size: 14px;
line-height: 20px;
letter-spacing: 0.2px;
color: rgba(0, 0, 0, 0.54);
}
@media screen and (min-width: 960px) and (max-width: 1819px) {
.tb-get-started .mat-step-label {
font-size: 12px;
line-height: 16px;
}
.tb-get-started .mat-step-label.mat-step-label-selected {
font-size: 13px;
line-height: 16px;
}
.tb-get-started .mdc-button {
font-size: 12px;
line-height: 16px;
}
.tb-get-started .mat-vertical-content p {
font-size: 12px;
line-height: 16px;
letter-spacing: 0.25px;
}
.tb-get-started .mat-vertical-content {
padding: 0 16px 16px 16px;
}
}
}

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

@ -0,0 +1,134 @@
///
/// 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 { ChangeDetectorRef, Component, Input, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { PageComponent } from '@shared/components/page.component';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { UserSettingsService } from '@core/http/user-settings.service';
import { getCurrentAuthUser } from '@core/auth/auth.selectors';
import { WidgetContext } from '@home/models/widget-component.models';
import { MatDialog } from '@angular/material/dialog';
import {
GettingStartedCompletedDialogComponent
} 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 { MatStepper } from '@angular/material/stepper';
import { first } from 'rxjs/operators';
import { Authority } from '@shared/models/authority.enum';
@Component({
selector: 'tb-getting-started-widget',
templateUrl: './getting-started-widget.component.html',
styleUrls: ['./getting-started-widget.component.scss']
})
export class GettingStartedWidgetComponent extends PageComponent implements OnInit, OnDestroy {
@ViewChild('matStepper')
matStepper: MatStepper;
@Input()
ctx: WidgetContext;
authority = Authority;
authUser = getCurrentAuthUser(this.store);
gettingStarted: GettingStarted = {
lastSelectedIndex: 0,
maxSelectedIndex: 0
};
allCompleted = false;
constructor(protected store: Store<AppState>,
private cd: ChangeDetectorRef,
private userSettingsService: UserSettingsService,
private dialog: MatDialog) {
super(store);
}
ngOnInit() {
this.userSettingsService.getGettingStarted().subscribe(
(gettingStarted) => {
if (gettingStarted) {
this.gettingStarted = gettingStarted;
if (isUndefined(this.gettingStarted.lastSelectedIndex)) {
this.gettingStarted.lastSelectedIndex = 0;
}
if (isUndefined(this.gettingStarted.maxSelectedIndex)) {
this.gettingStarted.maxSelectedIndex = 0;
}
if (this.gettingStarted.lastSelectedIndex > 0 && this.gettingStarted.lastSelectedIndex < this.matStepper.steps.length) {
const animationDuration = this.matStepper.animationDuration;
this.matStepper.animationDuration = '0';
this.matStepper.selectedIndex = this.gettingStarted.lastSelectedIndex;
this.matStepper.animationDone.pipe(first()).subscribe(() => {
setTimeout(() => {this.matStepper.animationDuration = animationDuration;}, 0);
});
}
if (this.matStepper.steps.length) {
this.updateCompletedSteps();
this.cd.markForCheck();
} else {
this.matStepper.steps.changes.subscribe(
() => {
this.updateCompletedSteps();
this.cd.markForCheck();
}
);
}
}
}
);
}
ngOnDestroy() {
super.ngOnDestroy();
}
isSelected(step: CdkStep) {
return this.matStepper?.selected === step;
}
gettingStartedCompleted() {
this.dialog.open<GettingStartedCompletedDialogComponent, any,
void>(GettingStartedCompletedDialogComponent, {
disableClose: true,
autoFocus: false,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog']
}).afterClosed().subscribe();
}
updateSelectedIndex(event: StepperSelectionEvent) {
if (this.gettingStarted.lastSelectedIndex !== event.selectedIndex) {
this.gettingStarted.lastSelectedIndex = event.selectedIndex;
if (event.selectedIndex > this.gettingStarted.maxSelectedIndex) {
this.gettingStarted.maxSelectedIndex = event.selectedIndex;
this.updateCompletedSteps();
}
this.userSettingsService
.updateGettingStarted(this.gettingStarted, {ignoreLoading: true}).subscribe();
}
}
updateCompletedSteps() {
if (this.gettingStarted.maxSelectedIndex >= this.matStepper.steps.length-1) {
this.allCompleted = true;
}
}
}

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

@ -24,6 +24,10 @@ import { DocLinksWidgetComponent } from '@home/components/widget/lib/home-page/d
import { DocLinkComponent } from '@home/components/widget/lib/home-page/doc-link.component'; import { DocLinkComponent } from '@home/components/widget/lib/home-page/doc-link.component';
import { AddDocLinkDialogComponent } from '@home/components/widget/lib/home-page/add-doc-link-dialog.component'; import { AddDocLinkDialogComponent } from '@home/components/widget/lib/home-page/add-doc-link-dialog.component';
import { EditDocLinksDialogComponent } from '@home/components/widget/lib/home-page/edit-doc-links-dialog.component'; import { EditDocLinksDialogComponent } from '@home/components/widget/lib/home-page/edit-doc-links-dialog.component';
import { GettingStartedWidgetComponent } from '@home/components/widget/lib/home-page/getting-started-widget.component';
import {
GettingStartedCompletedDialogComponent
} from '@home/components/widget/lib/home-page/getting-started-completed-dialog.component';
@NgModule({ @NgModule({
declarations: declarations:
@ -34,7 +38,9 @@ import { EditDocLinksDialogComponent } from '@home/components/widget/lib/home-pa
DocLinksWidgetComponent, DocLinksWidgetComponent,
DocLinkComponent, DocLinkComponent,
AddDocLinkDialogComponent, AddDocLinkDialogComponent,
EditDocLinksDialogComponent EditDocLinksDialogComponent,
GettingStartedWidgetComponent,
GettingStartedCompletedDialogComponent
], ],
imports: [ imports: [
CommonModule, CommonModule,
@ -47,7 +53,9 @@ import { EditDocLinksDialogComponent } from '@home/components/widget/lib/home-pa
DocLinksWidgetComponent, DocLinksWidgetComponent,
DocLinkComponent, DocLinkComponent,
AddDocLinkDialogComponent, AddDocLinkDialogComponent,
EditDocLinksDialogComponent EditDocLinksDialogComponent,
GettingStartedWidgetComponent,
GettingStartedCompletedDialogComponent
] ]
}) })
export class HomePageWidgetsModule { } export class HomePageWidgetsModule { }

16
ui-ngx/src/app/modules/home/components/widget/lib/home-page/version-info.component.html

@ -16,21 +16,23 @@
--> -->
<div class="tb-card-content" fxLayout="column"> <div class="tb-card-content" fxLayout="column">
<div class="tb-card-header tb-card-title"> <div class="tb-card-header">
<div class="tb-title">Version</div> <div class="tb-card-title">
<a mat-button color="primary" class="tb-contact-us" href="https://thingsboard.io/docs/contact-us/" target="_blank">Contact us</a> <div class="tb-title" translate>widgets.version-info.title</div>
</div>
<a mat-button color="primary" class="tb-contact-us" href="https://thingsboard.io/docs/contact-us/" target="_blank">{{ 'widgets.version-info.contact-us' | translate }}</a>
</div> </div>
<div fxLayout="column" fxFlex> <div fxLayout="column" fxFlex>
<div class="tb-version-info-container" fxFlex> <div class="tb-version-info-container" fxFlex>
<div class="tb-card-header"> <div class="tb-card-header">
<a class="tb-version-link" [href]="updateMessage?.currentVersionReleaseNotesUrl" target="_blank">Current version</a> <a class="tb-version-link" [href]="updateMessage?.currentVersionReleaseNotesUrl" target="_blank">{{ 'widgets.version-info.current-version' | translate }}</a>
</div> </div>
<div class="tb-version">{{ updateMessage?.currentVersion }}</div> <div class="tb-version">{{ updateMessage?.currentVersion }}</div>
</div> </div>
<div class="tb-version-info-container" fxFlex *ngIf="updateMessage?.updateAvailable; else versionUpToDate"> <div class="tb-version-info-container" fxFlex *ngIf="updateMessage?.updateAvailable; else versionUpToDate">
<div class="tb-card-header"> <div class="tb-card-header">
<a class="tb-version-link" [href]="updateMessage?.latestVersionReleaseNotesUrl" target="_blank">Available version</a> <a class="tb-version-link" [href]="updateMessage?.latestVersionReleaseNotesUrl" target="_blank">{{ 'widgets.version-info.available-version' | translate }}</a>
<a mat-flat-button color="primary" class="tb-upgrade" [href]="updateMessage?.upgradeInstructionsUrl" target="_blank">Upgrade</a> <a mat-flat-button color="primary" class="tb-upgrade" [href]="updateMessage?.upgradeInstructionsUrl" target="_blank">{{ 'widgets.version-info.upgrade' | translate }}</a>
</div> </div>
<div class="tb-version">{{ updateMessage?.latestVersion }}</div> <div class="tb-version">{{ updateMessage?.latestVersion }}</div>
</div> </div>
@ -39,6 +41,6 @@
<ng-template #versionUpToDate> <ng-template #versionUpToDate>
<div class="tb-version-info-container up-to-date" fxFlex> <div class="tb-version-info-container up-to-date" fxFlex>
<div class="material-icons-round check-icon">check</div> <div class="material-icons-round check-icon">check</div>
<div class="up-to-date-text">Version is up to date</div> <div class="up-to-date-text" translate>widgets.version-info.version-is-up-to-date</div>
</div> </div>
</ng-template> </ng-template>

3
ui-ngx/src/app/modules/home/components/widget/lib/home-page/version-info.component.scss

@ -24,9 +24,6 @@
flex-direction: row; flex-direction: row;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
}
.tb-card-title {
margin-bottom: 8px; margin-bottom: 8px;
@media screen and (min-width: 960px) and (max-width: 1819px) { @media screen and (min-width: 960px) and (max-width: 1819px) {
margin-bottom: 4px; margin-bottom: 4px;

2
ui-ngx/src/app/modules/home/components/widget/lib/settings/home-page/doc-links-widget-settings.component.html

@ -17,7 +17,7 @@
--> -->
<section class="tb-widget-settings" [formGroup]="docLinksWidgetSettingsForm" fxLayout="column"> <section class="tb-widget-settings" [formGroup]="docLinksWidgetSettingsForm" fxLayout="column">
<mat-form-field fxFlex class="mat-block"> <mat-form-field fxFlex class="mat-block">
<mat-label>Columns</mat-label> <mat-label translate>widgets.documentation.columns</mat-label>
<input required matInput type="number" step="1" min="1" max="20" formControlName="columns"> <input required matInput type="number" step="1" min="1" max="20" formControlName="columns">
</mat-form-field> </mat-form-field>
</section> </section>

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

File diff suppressed because one or more lines are too long

8
ui-ngx/src/app/shared/models/user-settings.models.ts

@ -26,7 +26,8 @@ export enum UserSettingsType {
GENERAL = 'GENERAL', GENERAL = 'GENERAL',
QUICK_LINKS = 'QUICK_LINKS', QUICK_LINKS = 'QUICK_LINKS',
DOC_LINKS = 'DOC_LINKS', DOC_LINKS = 'DOC_LINKS',
DASHBOARDS = 'DASHBOARDS' DASHBOARDS = 'DASHBOARDS',
GETTING_STARTED = 'GETTING_STARTED'
} }
export interface DocumentationLink { export interface DocumentationLink {
@ -38,3 +39,8 @@ export interface DocumentationLink {
export interface DocumentationLinks { export interface DocumentationLinks {
links?: DocumentationLink[]; links?: DocumentationLink[];
} }
export interface GettingStarted {
maxSelectedIndex?: number;
lastSelectedIndex?: number;
}

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

@ -2385,7 +2385,7 @@
"ignore-case": "ignore case", "ignore-case": "ignore case",
"value": "Value", "value": "Value",
"remove-filter": "Remove filter", "remove-filter": "Remove filter",
"duplicate-filter": "Duplicate filter", "duplicate-filter-action": "Duplicate filter",
"preview": "Filter preview", "preview": "Filter preview",
"no-filters": "No filters configured", "no-filters": "No filters configured",
"add-filter": "Add filter", "add-filter": "Add filter",
@ -5060,6 +5060,113 @@
"font-weight-lighter": "Lighter", "font-weight-lighter": "Lighter",
"color": "Color", "color": "Color",
"shadow-color": "Shadow color" "shadow-color": "Shadow color"
},
"system-info": {
"cpu": "CPU",
"ram": "RAM",
"disk": "Disk",
"cpu-warning-text": "Running high on CPU usage. To avoid system failure, optimize system performance.",
"cpu-critical-text": "Critically high CPU usage. To avoid system failure, optimize system performance.",
"ram-warning-text": "Running low on reserve of RAM. To avoid system failure, optimize system performance or increase the size of RAM.",
"ram-critical-text": "Critically low reserve of RAM. To avoid system failure, optimize system performance or increase the size of RAM.",
"disk-warning-text": "Running low on disk space. To avoid data loss, free up or expand the disk space.",
"disk-critical-text": "Critically low disk space. To avoid data loss, free up or expand the disk space."
},
"cluster-info": {
"service-id": "Service id",
"service-type": "Service type",
"no-data": "No data"
},
"transport-messages": {
"title": "Transport messages",
"info": "All the messages that came from devices"
},
"documentation": {
"title": "Documentation",
"add-link": "Add link",
"add-link-title": "Add documentation link",
"name": "Name",
"name-required": "Name is required.",
"link": "Link",
"link-required": "Link is required.",
"columns": "Columns"
},
"configured-features": {
"title": "Configured features",
"info": "Status of features that require configuration",
"email-feature": "Email",
"sms-feature": "SMS",
"slack-feature": "Slack",
"oauth2-feature": "OAuth 2",
"2fa-feature": "2FA",
"feature-configured": "Feature is configured.\nClick to setup",
"feature-not-configured": "Feature is not configured.\nClick to setup"
},
"version-info": {
"title": "Version",
"contact-us": "Contact us",
"current-version": "Current version",
"available-version": "Available version",
"upgrade": "Upgrade",
"version-is-up-to-date": "Version is up to date"
},
"functions": {
"title": "Functions",
"pe-feature-tooltip": "Only on ThingsBoard\nProfessional Edition",
"switch-to-pe": "Switch to PE",
"alarms": "Alarms",
"dashboards": "Dashboards",
"entities-and-relations": "Entities & Relations",
"profiles": "Profiles",
"advanced-features": "Advanced features",
"notification-center": "Notification center",
"api-usage": "API usage",
"customers": "Customers",
"customers-hierarchy": "Customers hierarchy",
"roles-and-permissions": "Roles & Permissions",
"groups": "Groups",
"integrations": "Integrations",
"solution-templates": "Solution templates",
"scheduler": "Scheduler",
"white-labeling": "White-labeling"
},
"getting-started": {
"get-started": "Get started",
"finish": "Finish",
"done-welcome-title": "Welcome on board",
"done-welcome-text": "You did great with it!",
"sys-admin": {
"step1": {
"title": "Create Tenant and Tenant Administrator",
"content": "<p>A tenant is an individual or organization that owns or produces devices and assets. Tenants can create Tenant administrator users.</p><p>Follow the documentation on how to do it:</p>",
"how-to-create-tenant": "How to create Tenant"
},
"step2": {
"title": "Configure feature: Mail server",
"content": "<p>Users will receive activation and password reset emails from the System Administrator via an SMTP server.</p><p>Follow the documentation on how to do it:</p>",
"how-to-configure-mail-server": "How to configure Mail server"
},
"step3": {
"title": "Configure feature: SMS provider",
"content": "<p>To distribute SMS messages about alarms, the System Administrator can configure SMS providers.</p><p>Follow the documentation on how to do it:</p>",
"how-to-configure-sms-provider": "How to configure SMS provider"
},
"step4": {
"title": "Configure feature: 2FA",
"content": "<p>Adding two-factor authentication to your account increases security and prevents malicious access.</p><p>Follow the documentation on how to do it:</p>",
"how-to-configure-2fa": "How to configure 2FA"
},
"step5": {
"title": "Configure feature: OAuth 2",
"content": "<p>You can provide your customers with Single Sign-On functionality and automatically create tenants, customers, or sub-customers by using external user management platforms that support OAuth 2.0.</p><p>Follow the documentation on how to do it:</p>",
"how-to-configure-oauth2": "How to configure OAuth 2"
},
"step6": {
"title": "Configure feature: Notifications",
"content": "<p>Some text</p><p>Follow the documentation on how to do it:</p>",
"how-to-configure-notifications": "How to configure Notifications"
}
}
} }
}, },
"icon": { "icon": {

Loading…
Cancel
Save