Browse Source

Merge with develop/3.5.2

pull/8708/head
Igor Kulikov 3 years ago
parent
commit
36f1cd00e7
  1. 23
      application/src/main/data/upgrade/3.5.0/schema_update.sql
  2. 1
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  3. 15
      application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
  4. 4
      ui-ngx/src/app/core/services/dashboard-utils.service.ts
  5. 18
      ui-ngx/src/app/core/services/dialog.service.ts
  6. 4
      ui-ngx/src/app/core/services/utils.service.ts
  7. 13
      ui-ngx/src/app/modules/home/components/event/event-table.component.ts
  8. 18
      ui-ngx/src/app/modules/home/components/widget/dialog/custom-dialog-container.component.ts
  9. 2
      ui-ngx/src/app/modules/home/components/widget/widget.component.html
  10. 3
      ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.ts
  11. 2
      ui-ngx/src/app/shared/components/dialog/alert-dialog.component.html
  12. 30
      ui-ngx/src/app/shared/components/dialog/error-alert-dialog.component.html
  13. 37
      ui-ngx/src/app/shared/components/dialog/error-alert-dialog.component.scss
  14. 49
      ui-ngx/src/app/shared/components/dialog/error-alert-dialog.component.ts
  15. 2
      ui-ngx/src/app/shared/components/snack-bar-component.scss
  16. 2
      ui-ngx/src/app/shared/components/time/timewindow.component.html
  17. 84
      ui-ngx/src/app/shared/components/time/timewindow.component.ts
  18. 6
      ui-ngx/src/app/shared/models/time/time.models.ts
  19. 3
      ui-ngx/src/app/shared/shared.module.ts
  20. 10
      ui-ngx/src/assets/locale/locale.constant-en_US.json

23
application/src/main/data/upgrade/3.5.0/schema_update.sql

@ -0,0 +1,23 @@
--
-- 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.
--
-- FIX DASHBOARD TEMPLATES AFTER ANGULAR MIGRATION TO VER.15
UPDATE dashboard SET configuration = REPLACE(configuration, 'mat-button mat-icon-button', 'mat-icon-button')
WHERE configuration like '%mat-button mat-icon-button%';
UPDATE widget_type SET descriptor = REPLACE(descriptor, 'mat-button mat-icon-button', 'mat-icon-button')
WHERE descriptor like '%mat-button mat-icon-button%';

1
application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java

@ -256,6 +256,7 @@ public class ThingsboardInstallService {
}
case "3.5.0":
log.info("Upgrading ThingsBoard from version 3.5.0 to 3.5.1 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.5.0");
case "3.5.1":
log.info("Upgrading ThingsBoard from version 3.5.1 to 3.5.2 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.5.1");

15
application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java

@ -714,10 +714,23 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
log.error("Failed updating schema!!!", e);
}
break;
case "3.5.1":
case "3.5.0":
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
log.info("Updating schema ...");
if (isOldSchema(conn, 3005000)) {
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.5.0", SCHEMA_UPDATE_SQL);
loadSql(schemaUpdateFile, conn);
conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3005001;");
}
log.info("Schema updated.");
} catch (Exception e) {
log.error("Failed updating schema!!!", e);
}
break;
case "3.5.1":
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
log.info("Updating schema ...");
if (isOldSchema(conn, 3005001)) {
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.5.1", SCHEMA_UPDATE_SQL);
loadSql(schemaUpdateFile, conn);

4
ui-ngx/src/app/core/services/dashboard-utils.service.ts

@ -32,6 +32,7 @@ import {
import { isDefined, isDefinedAndNotNull, isString, isUndefined } from '@core/utils';
import {
Datasource,
datasourcesHasOnlyComparisonAggregation,
DatasourceType,
defaultLegendConfig,
Widget,
@ -249,7 +250,8 @@ export class DashboardUtilsService {
}
});
if (type === widgetType.latest) {
widgetConfig.timewindow = initModelFromDefaultTimewindow(widgetConfig.timewindow, true, this.timeService);
const onlyHistoryTimewindow = datasourcesHasOnlyComparisonAggregation(widgetConfig.datasources);
widgetConfig.timewindow = initModelFromDefaultTimewindow(widgetConfig.timewindow, true, onlyHistoryTimewindow, this.timeService);
}
if (type === widgetType.alarm) {
if (!widgetConfig.alarmFilterConfig) {

18
ui-ngx/src/app/core/services/dialog.service.ts

@ -29,6 +29,7 @@ import {
} from '@shared/components/dialog/material-icons-dialog.component';
import { ConfirmDialogComponent } from '@shared/components/dialog/confirm-dialog.component';
import { AlertDialogComponent } from '@shared/components/dialog/alert-dialog.component';
import { ErrorAlertDialogComponent } from '@shared/components/dialog/error-alert-dialog.component';
import { TodoDialogComponent } from '@shared/components/dialog/todo-dialog.component';
@Injectable(
@ -78,6 +79,23 @@ export class DialogService {
return dialogRef.afterClosed();
}
errorAlert(title: string, message: string, error: any, ok: string = null, fullscreen: boolean = false): Observable<any> {
const dialogConfig: MatDialogConfig = {
disableClose: true,
data: {
title,
message,
error,
ok: ok || this.translate.instant('action.ok')
}
};
if (fullscreen) {
dialogConfig.panelClass = ['tb-fullscreen-dialog'];
}
const dialogRef = this.dialog.open(ErrorAlertDialogComponent, dialogConfig);
return dialogRef.afterClosed();
}
colorPicker(color: string): Observable<string> {
return this.dialog.open<ColorPickerDialogComponent, ColorPickerDialogData, string>(ColorPickerDialogComponent,
{

4
ui-ngx/src/app/core/services/utils.service.ts

@ -188,6 +188,10 @@ export class UtilsService {
public processWidgetException(exception: any): ExceptionData {
const data = this.parseException(exception, -6);
if (data.message?.startsWith('NG0')) {
data.message = `${this.translate.instant('widget.widget-template-error')}<br/>
<br/><i>${this.translate.instant('dialog.error-message-title')}</i><br/><br/>${data.message}`;
}
if (this.widgetEditMode) {
const message: WindowMessage = {
type: 'widgetException',

13
ui-ngx/src/app/modules/home/components/event/event-table.component.ts

@ -14,7 +14,16 @@
/// limitations under the License.
///
import { AfterViewInit, ChangeDetectorRef, Component, Input, OnInit, ViewChild, ViewContainerRef } from '@angular/core';
import {
AfterViewInit,
ChangeDetectorRef,
Component,
Input,
OnDestroy,
OnInit,
ViewChild,
ViewContainerRef
} from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { DatePipe } from '@angular/common';
import { MatDialog } from '@angular/material/dialog';
@ -32,7 +41,7 @@ import { Subscription } from 'rxjs';
templateUrl: './event-table.component.html',
styleUrls: ['./event-table.component.scss']
})
export class EventTableComponent implements OnInit, AfterViewInit {
export class EventTableComponent implements OnInit, AfterViewInit, OnDestroy {
@Input()
tenantId: string;

18
ui-ngx/src/app/modules/home/components/widget/dialog/custom-dialog-container.component.ts

@ -33,6 +33,8 @@ import {
CustomDialogComponent,
CustomDialogData
} from '@home/components/widget/dialog/custom-dialog.component';
import { DialogService } from '@core/services/dialog.service';
import { TranslateService } from '@ngx-translate/core';
export interface CustomDialogContainerData {
controller: (instance: CustomDialogComponent) => void;
@ -54,6 +56,8 @@ export class CustomDialogContainerComponent extends DialogComponent<CustomDialog
protected router: Router,
public viewContainerRef: ViewContainerRef,
public dialogRef: MatDialogRef<CustomDialogContainerComponent>,
private dialogService: DialogService,
private translate: TranslateService,
@Inject(MAT_DIALOG_DATA) public data: CustomDialogContainerData) {
super(store, router, dialogRef);
let customDialogData: CustomDialogData = {
@ -72,7 +76,19 @@ export class CustomDialogContainerComponent extends DialogComponent<CustomDialog
useValue: dialogRef
}]
});
this.customComponentRef = this.viewContainerRef.createComponent(this.data.customComponentFactory, 0, injector);
try {
this.customComponentRef = this.viewContainerRef.createComponent(this.data.customComponentFactory, 0, injector);
} catch (e: any) {
let message;
if (e.message?.startsWith('NG0')) {
message = this.translate.instant('widget-action.custom-pretty-template-error');
} else {
message = this.translate.instant('widget-action.custom-pretty-controller-error');
}
dialogRef.close();
console.error(e);
this.dialogService.errorAlert(this.translate.instant('widget-action.custom-pretty-error-title'), message, e);
}
}
ngOnDestroy(): void {

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

@ -22,7 +22,7 @@
</div>
</div>
<div class="tb-absolute-fill tb-widget-error" *ngIf="widgetErrorData">
<span>Widget Error: {{ widgetErrorData.name + ": " + widgetErrorData.message}}</span>
<span [innerHtml]="('Widget Error:<br/><br/>' + widgetErrorData.message) | safe:'html'"></span>
</div>
<div class="tb-absolute-fill tb-widget-no-data" *ngIf="displayNoData">
<span fxLayoutAlign="center center"

3
ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.ts

@ -489,9 +489,6 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe
if (!this.gotError) {
this.gotError = true;
let errorInfo = 'Error:';
if (details.name) {
errorInfo += ' ' + details.name + ':';
}
if (details.message) {
errorInfo += ' ' + details.message;
}

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

@ -16,7 +16,7 @@
-->
<h2 mat-dialog-title>{{data.title}}</h2>
<div mat-dialog-content [innerHTML]="data.message"></div>
<div mat-dialog-content [innerHTML]="data.message | safe:'html'"></div>
<div mat-dialog-actions fxLayoutAlign="end center">
<button mat-button color="primary" [mat-dialog-close]="true" cdkFocusInitial>{{data.ok}}</button>
</div>

30
ui-ngx/src/app/shared/components/dialog/error-alert-dialog.component.html

@ -0,0 +1,30 @@
<!--
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.
-->
<h2 mat-dialog-title>{{title}}</h2>
<div mat-dialog-content class="tb-error-alert-dialog-content">
<div>{{ message }}</div>
<div class="error-message-title" translate>dialog.error-message-title</div>
<div class="error-message-content"> {{ errorMessage }} </div>
<mat-expansion-panel *ngIf="errorDetails">
<mat-expansion-panel-header>{{ 'dialog.error-details-title' | translate }}</mat-expansion-panel-header>
<small class="error-details-content" [innerHTML]="errorDetails"></small>
</mat-expansion-panel>
</div>
<div mat-dialog-actions fxLayoutAlign="end center">
<button mat-button color="primary" [mat-dialog-close]="true" cdkFocusInitial>{{data.ok}}</button>
</div>

37
ui-ngx/src/app/shared/components/dialog/error-alert-dialog.component.scss

@ -0,0 +1,37 @@
/**
* 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 {
.mat-mdc-dialog-content {
padding: 0 24px 24px;
}
.tb-error-alert-dialog-content {
.error-message-title {
font-style: italic;
}
.error-message-content {
color: red;
}
.error-details-content {
display: block;
border: solid 1px #d3d3d3;
padding: 8px;
border-radius: 4px;
}
& > *:not(:last-child) {
padding-bottom: 16px;
}
}
}

49
ui-ngx/src/app/shared/components/dialog/error-alert-dialog.component.ts

@ -0,0 +1,49 @@
///
/// 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, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
export interface ErrorAlertDialogData {
title: string;
message: string;
error: any;
ok: string;
}
@Component({
selector: 'tb-error-alert-dialog',
templateUrl: './error-alert-dialog.component.html',
styleUrls: ['./error-alert-dialog.component.scss']
})
export class ErrorAlertDialogComponent {
title: string;
message: string;
errorMessage: string;
errorDetails?: string;
constructor(public dialogRef: MatDialogRef<ErrorAlertDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: ErrorAlertDialogData) {
this.title = this.data.title;
this.message = this.data.message;
this.errorMessage = this.data.error.message ? this.data.error.message : JSON.stringify(this.data.error);
if (this.data.error.stack) {
this.errorDetails = this.data.error.stack.replaceAll('\n', '<br/>');
}
}
}

2
ui-ngx/src/app/shared/components/snack-bar-component.scss

@ -51,7 +51,7 @@
padding: 0 18px;
margin: 8px;
.toast-text {
padding: 0 6px;
padding: 8px;
width: 100%;
}
button {

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

@ -54,7 +54,7 @@
(click)="toggleTimewindow($event)"
matTooltip="{{ 'timewindow.edit' | translate }}"
[matTooltipPosition]="tooltipPosition">
{{innerValue?.displayValue}} <span [fxShow]="innerValue?.displayTimezoneAbbr !== ''">| <span class="timezone-abbr">{{innerValue.displayTimezoneAbbr}}</span></span>
{{innerValue?.displayValue}} <span [fxShow]="innerValue?.displayTimezoneAbbr !== ''">| <span class="timezone-abbr">{{innerValue?.displayTimezoneAbbr}}</span></span>
</span>
<button *ngIf="direction === 'right'" [disabled]="timewindowDisabled" mat-icon-button class="tb-mat-32"
type="button"

84
ui-ngx/src/app/shared/components/time/timewindow.component.ts

@ -21,8 +21,6 @@ import {
forwardRef,
Injector,
Input,
OnDestroy,
OnInit,
StaticProvider,
ViewContainerRef
} from '@angular/core';
@ -66,7 +64,7 @@ import { coerceBoolean } from '@shared/decorators/coercion';
}
]
})
export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAccessor {
export class TimewindowComponent implements ControlValueAccessor {
historyOnlyValue = false;
@ -89,60 +87,29 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces
@coerceBoolean()
forAllTimeEnabled = false;
alwaysDisplayTypePrefixValue = false;
@Input()
set alwaysDisplayTypePrefix(val) {
this.alwaysDisplayTypePrefixValue = coerceBooleanProperty(val);
}
get alwaysDisplayTypePrefix() {
return this.alwaysDisplayTypePrefixValue;
}
quickIntervalOnlyValue = false;
@coerceBoolean()
alwaysDisplayTypePrefix = false;
@Input()
set quickIntervalOnly(val) {
this.quickIntervalOnlyValue = coerceBooleanProperty(val);
}
get quickIntervalOnly() {
return this.quickIntervalOnlyValue;
}
aggregationValue = false;
@coerceBoolean()
quickIntervalOnly = false;
@Input()
set aggregation(val) {
this.aggregationValue = coerceBooleanProperty(val);
}
get aggregation() {
return this.aggregationValue;
}
timezoneValue = false;
@coerceBoolean()
aggregation = false;
@Input()
set timezone(val) {
this.timezoneValue = coerceBooleanProperty(val);
}
get timezone() {
return this.timezoneValue;
}
asButtonValue = false;
@coerceBoolean()
timezone = false;
@Input()
set asButton(val) {
this.asButtonValue = coerceBooleanProperty(val);
}
@coerceBoolean()
isToolbar = false;
get asButton() {
return this.asButtonValue;
}
@Input()
@coerceBoolean()
asButton = false;
@Input()
@coerceBoolean()
@ -178,7 +145,9 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces
@Input()
tooltipPosition: TooltipPosition = 'above';
@Input() disabled: boolean;
@Input()
@coerceBoolean()
disabled: boolean;
innerValue: Timewindow;
@ -196,12 +165,6 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces
public viewContainerRef: ViewContainerRef) {
}
ngOnInit(): void {
}
ngOnDestroy(): void {
}
toggleTimewindow($event: Event) {
if ($event) {
$event.stopPropagation();
@ -213,7 +176,6 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces
maxHeight: '80vh',
height: 'min-content'
});
config.hasBackdrop = true;
const connectedPosition: ConnectedPosition = {
originX: 'start',
originY: 'bottom',
@ -260,12 +222,10 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces
}
private onHistoryOnlyChanged(): boolean {
if (this.historyOnlyValue && this.innerValue) {
if (this.innerValue.selectedTab !== TimewindowType.HISTORY) {
this.innerValue.selectedTab = TimewindowType.HISTORY;
this.updateDisplayValue();
return true;
}
if (this.historyOnlyValue && this.innerValue && this.innerValue.selectedTab !== TimewindowType.HISTORY) {
this.innerValue.selectedTab = TimewindowType.HISTORY;
this.updateDisplayValue();
return true;
}
return false;
}
@ -283,7 +243,7 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces
}
writeValue(obj: Timewindow): void {
this.innerValue = initModelFromDefaultTimewindow(obj, this.quickIntervalOnly, this.timeService);
this.innerValue = initModelFromDefaultTimewindow(obj, this.quickIntervalOnly, this.historyOnly, this.timeService);
this.timewindowDisabled = this.isTimewindowDisabled();
if (this.onHistoryOnlyChanged()) {
setTimeout(() => {

6
ui-ngx/src/app/shared/models/time/time.models.ts

@ -245,7 +245,8 @@ const getTimewindowType = (timewindow: Timewindow): TimewindowType => {
}
};
export const initModelFromDefaultTimewindow = (value: Timewindow, quickIntervalOnly: boolean, timeService: TimeService): Timewindow => {
export const initModelFromDefaultTimewindow = (value: Timewindow, quickIntervalOnly: boolean,
historyOnly: boolean, timeService: TimeService): Timewindow => {
const model = defaultTimewindow(timeService);
if (value) {
model.hideInterval = value.hideInterval;
@ -316,6 +317,9 @@ export const initModelFromDefaultTimewindow = (value: Timewindow, quickIntervalO
if (quickIntervalOnly) {
model.realtime.realtimeType = RealtimeWindowType.INTERVAL;
}
if (historyOnly) {
model.selectedTab = TimewindowType.HISTORY;
}
return model;
};

3
ui-ngx/src/app/shared/shared.module.ts

@ -123,6 +123,7 @@ import { JsFuncComponent } from '@shared/components/js-func.component';
import { JsonFormComponent } from '@shared/components/json-form/json-form.component';
import { ConfirmDialogComponent } from '@shared/components/dialog/confirm-dialog.component';
import { AlertDialogComponent } from '@shared/components/dialog/alert-dialog.component';
import { ErrorAlertDialogComponent } from '@shared/components/dialog/error-alert-dialog.component';
import { TodoDialogComponent } from '@shared/components/dialog/todo-dialog.component';
import { MaterialIconsDialogComponent } from '@shared/components/dialog/material-icons-dialog.component';
import { MaterialIconSelectComponent } from '@shared/components/material-icon-select.component';
@ -306,6 +307,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
WidgetsBundleSelectComponent,
ConfirmDialogComponent,
AlertDialogComponent,
ErrorAlertDialogComponent,
TodoDialogComponent,
ColorPickerDialogComponent,
MaterialIconsDialogComponent,
@ -530,6 +532,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
MarkdownModule,
ConfirmDialogComponent,
AlertDialogComponent,
ErrorAlertDialogComponent,
TodoDialogComponent,
ColorPickerDialogComponent,
MaterialIconsDialogComponent,

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

@ -1798,7 +1798,9 @@
}
},
"dialog": {
"close": "Close dialog"
"close": "Close dialog",
"error-message-title": "Error message:",
"error-details-title": "Error details"
},
"direction": {
"column": "Column",
@ -4017,7 +4019,8 @@
"alarm-data-overflow": "Widget displays alarms for {{allowedEntities}} (maximum allowed) entities out of {{totalEntities}} entities",
"search": "Search widget",
"filter": "Widget filter type",
"loading-widgets": "Loading widgets..."
"loading-widgets": "Loading widgets...",
"widget-template-error": "Invalid widget HTML template."
},
"widget-action": {
"header-button": "Widget header button",
@ -4026,6 +4029,9 @@
"open-dashboard": "Navigate to other dashboard",
"custom": "Custom action",
"custom-pretty": "Custom action (with HTML template)",
"custom-pretty-error-title": "Custom dialog error",
"custom-pretty-template-error": "Invalid custom dialog template.",
"custom-pretty-controller-error": "Error occurred while evaluating custom dialog function.",
"mobile-action": "Mobile action",
"target-dashboard-state": "Target dashboard state",
"target-dashboard-state-required": "Target dashboard state is required",

Loading…
Cancel
Save