Updated logic for file upload, changed to dialog window upload

This commit is contained in:
LeoMorgan113 2025-09-02 10:34:56 +03:00
parent 5b14dc67fc
commit e4588616f3
7 changed files with 248 additions and 78 deletions

View File

@ -28,6 +28,12 @@
[class.!hidden]="isEdit || dashboardScope !== 'tenant'">
{{'dashboard.export' | translate }}
</button>
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'import')"
[class.!hidden]="isEdit || dashboardScope !== 'tenant'">
{{'dashboard.import' | translate }}
</button>
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'makePublic')"
@ -144,19 +150,6 @@
formControlName="image">
</tb-gallery-image-input>
</div>
<div class="tb-form-panel stroked gap-2" [class.!hidden]="!isEdit">
<div class="tb-form-panel-title" translate>dashboard.update-dashboard</div>
<tb-file-input [contentConvertFunction]="loadDataFromJsonContent"
[existingFileName]="currentFileName"
(fileNameChanged)="currentFileName = $event"
formControlName="fileContent"
dropLabel="{{ 'import.drop-json-file-or' | translate }}"
accept=".json,application/json"
allowedExtensions="json">
</tb-file-input>
</div>
</fieldset>
</form>
</div>

View File

@ -22,7 +22,7 @@ import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms
import { ActionNotificationShow } from '@core/notification/notification.actions';
import { TranslateService } from '@ngx-translate/core';
import {
Dashboard,
Dashboard, DashboardInfo,
getDashboardAssignedCustomersText,
isCurrentPublicDashboardCustomer,
isPublicDashboard
@ -31,13 +31,14 @@ import { DashboardService } from '@core/http/dashboard.service';
import { EntityTableConfig } from '@home/models/entity/entities-table-config.models';
import { isEqual } from '@core/utils';
import { EntityType } from '@shared/models/entity-type.models';
import {PageLink} from "@shared/models/page/page-link";
@Component({
selector: 'tb-dashboard-form',
templateUrl: './dashboard-form.component.html',
styleUrls: ['./dashboard-form.component.scss']
})
export class DashboardFormComponent extends EntityComponent<Dashboard> {
export class DashboardFormComponent extends EntityComponent<Dashboard, PageLink, DashboardInfo> {
dashboardScope: 'tenant' | 'customer' | 'customer_user' | 'edge';
customerId: string;
@ -45,7 +46,6 @@ export class DashboardFormComponent extends EntityComponent<Dashboard> {
publicLink: string;
assignedCustomersText: string;
entityType = EntityType;
currentFileName: string = '';
constructor(protected store: Store<AppState>,
protected translate: TranslateService,
@ -85,7 +85,6 @@ export class DashboardFormComponent extends EntityComponent<Dashboard> {
{
title: [entity ? entity.title : '', [Validators.required, Validators.maxLength(255)]],
image: [entity ? entity.image : null],
fileContent: [null],
mobileHide: [entity ? entity.mobileHide : false],
mobileOrder: [entity ? entity.mobileOrder : null, [Validators.pattern(/^-?[0-9]+$/)]],
configuration: this.fb.group(
@ -103,11 +102,9 @@ export class DashboardFormComponent extends EntityComponent<Dashboard> {
}
updateForm(entity: Dashboard) {
this.currentFileName = '';
this.updateFields(entity);
this.entityForm.patchValue({title: entity.title});
this.entityForm.patchValue({image: entity.image});
this.entityForm.patchValue({fileContent: entity.fileContent || null});
this.entityForm.patchValue({mobileHide: entity.mobileHide});
this.entityForm.patchValue({mobileOrder: entity.mobileOrder});
this.entityForm.patchValue({configuration: {description: entity.configuration ? entity.configuration.description : ''}});
@ -147,14 +144,4 @@ export class DashboardFormComponent extends EntityComponent<Dashboard> {
this.publicLink = this.dashboardService.getPublicDashboardLink(entity);
}
}
loadDataFromJsonContent(content: string): any {
try {
const importData = JSON.parse(content);
return importData ? importData['configuration'] : importData;
} catch (err) {
this.store.dispatch(new ActionNotificationShow({message: err.message, type: 'error'}));
return null;
}
}
}

View File

@ -24,13 +24,15 @@ import { DashboardRoutingModule } from './dashboard-routing.module';
import { MakeDashboardPublicDialogComponent } from '@modules/home/pages/dashboard/make-dashboard-public-dialog.component';
import { HomeComponentsModule } from '@modules/home/components/home-components.module';
import { DashboardTabsComponent } from '@home/pages/dashboard/dashboard-tabs.component';
import {ImportDashboardFileDialogComponent} from "@home/pages/dashboard/import-dashboard-file-dialog.component";
@NgModule({
declarations: [
DashboardFormComponent,
DashboardTabsComponent,
ManageDashboardCustomersDialogComponent,
MakeDashboardPublicDialogComponent
MakeDashboardPublicDialogComponent,
ImportDashboardFileDialogComponent
],
imports: [
CommonModule,

View File

@ -14,9 +14,9 @@
/// limitations under the License.
///
import { Injectable } from '@angular/core';
import {Injectable} from '@angular/core';
import { ActivatedRouteSnapshot, Router } from '@angular/router';
import {ActivatedRouteSnapshot, Router} from '@angular/router';
import {
CellActionDescriptor,
checkBoxCell,
@ -26,20 +26,20 @@ import {
GroupActionDescriptor,
HeaderActionDescriptor
} from '@home/models/entity/entities-table-config.models';
import { TranslateService } from '@ngx-translate/core';
import { DatePipe } from '@angular/common';
import { EntityType, entityTypeResources, entityTypeTranslations } from '@shared/models/entity-type.models';
import { EntityAction } from '@home/models/entity/entity-component.models';
import { forkJoin, Observable, of } from 'rxjs';
import { select, Store } from '@ngrx/store';
import { selectAuthUser } from '@core/auth/auth.selectors';
import { map, mergeMap, take, tap } from 'rxjs/operators';
import { AppState } from '@core/core.state';
import { Authority } from '@app/shared/models/authority.enum';
import { CustomerService } from '@core/http/customer.service';
import { Customer } from '@app/shared/models/customer.model';
import { MatDialog } from '@angular/material/dialog';
import { DialogService } from '@core/services/dialog.service';
import {TranslateService} from '@ngx-translate/core';
import {DatePipe} from '@angular/common';
import {EntityType, entityTypeResources, entityTypeTranslations} from '@shared/models/entity-type.models';
import {EntityAction} from '@home/models/entity/entity-component.models';
import {forkJoin, Observable, of} from 'rxjs';
import {select, Store} from '@ngrx/store';
import {selectAuthUser} from '@core/auth/auth.selectors';
import {map, mergeMap, take, tap} from 'rxjs/operators';
import {AppState} from '@core/core.state';
import {Authority} from '@app/shared/models/authority.enum';
import {CustomerService} from '@core/http/customer.service';
import {Customer} from '@app/shared/models/customer.model';
import {MatDialog} from '@angular/material/dialog';
import {DialogService} from '@core/services/dialog.service';
import {
AddEntitiesToCustomerDialogComponent,
AddEntitiesToCustomerDialogData
@ -52,8 +52,8 @@ import {
isCurrentPublicDashboardCustomer,
isPublicDashboard
} from '@app/shared/models/dashboard.models';
import { DashboardService } from '@app/core/http/dashboard.service';
import { DashboardFormComponent } from '@modules/home/pages/dashboard/dashboard-form.component';
import {DashboardService} from '@app/core/http/dashboard.service';
import {DashboardFormComponent} from '@modules/home/pages/dashboard/dashboard-form.component';
import {
ManageDashboardCustomersActionType,
ManageDashboardCustomersDialogComponent,
@ -63,25 +63,30 @@ import {
MakeDashboardPublicDialogComponent,
MakeDashboardPublicDialogData
} from '@modules/home/pages/dashboard/make-dashboard-public-dialog.component';
import { DashboardTabsComponent } from '@home/pages/dashboard/dashboard-tabs.component';
import { ImportExportService } from '@shared/import-export/import-export.service';
import { EdgeService } from '@core/http/edge.service';
import {DashboardTabsComponent} from '@home/pages/dashboard/dashboard-tabs.component';
import {ImportExportService} from '@shared/import-export/import-export.service';
import {EdgeService} from '@core/http/edge.service';
import {
AddEntitiesToEdgeDialogComponent,
AddEntitiesToEdgeDialogData
} from '@home/dialogs/add-entities-to-edge-dialog.component';
import { HomeDialogsService } from '@home/dialogs/home-dialogs.service';
import { Widget } from '@shared/models/widget.models';
import { EntityAliases } from '@shared/models/alias.models';
import {HomeDialogsService} from '@home/dialogs/home-dialogs.service';
import {Widget} from '@shared/models/widget.models';
import {EntityAliases} from '@shared/models/alias.models';
import {
EntityAliasesDialogComponent,
EntityAliasesDialogData
} from '@home/components/alias/entity-aliases-dialog.component';
import {
DashboardInfoDialogData,
ImportDashboardFileDialogComponent
} from "@home/pages/dashboard/import-dashboard-file-dialog.component";
import {PageLink} from "@shared/models/page/page-link";
@Injectable()
export class DashboardsTableConfigResolver {
export class DashboardsTableConfigResolver {
private readonly config: EntityTableConfig<DashboardInfo | Dashboard> = new EntityTableConfig<DashboardInfo | Dashboard>();
private readonly config: EntityTableConfig<Dashboard, PageLink, DashboardInfo> = new EntityTableConfig<Dashboard, PageLink, DashboardInfo>();
constructor(private store: Store<AppState>,
private dashboardService: DashboardService,
@ -110,7 +115,7 @@ export class DashboardsTableConfigResolver {
this.config.deleteEntitiesContent = () => this.translate.instant('dashboard.delete-dashboards-text');
this.config.loadEntity = id => this.dashboardService.getDashboard(id.id);
this.config.saveEntity = dashboard => this.saveAndAssignDashboard(this.dashboardContentModification(dashboard) as DashboardSetup);
this.config.saveEntity = dashboard => this.saveAndAssignDashboard(dashboard as DashboardSetup);
this.config.onEntityAction = action => this.onDashboardAction(action);
this.config.detailsReadonly = () => (this.config.componentsData.dashboardScope === 'customer_user' ||
this.config.componentsData.dashboardScope === 'edge_customer_user');
@ -179,20 +184,6 @@ export class DashboardsTableConfigResolver {
);
}
private dashboardContentModification(dashboard: Dashboard): Dashboard{
if(dashboard.fileContent != undefined){
const { description, ...dashboardContent } = dashboard.fileContent;
dashboard.configuration = {
...dashboard.configuration,
...dashboardContent
}
}
delete dashboard.fileContent;
return dashboard;
}
configureColumns(dashboardScope: string): Array<EntityTableColumn<DashboardInfo>> {
const columns: Array<EntityTableColumn<DashboardInfo>> = [
new DateEntityTableColumn<DashboardInfo>('createdTime', 'common.created-time', this.datePipe, '150px'),
@ -389,7 +380,7 @@ export class DashboardsTableConfigResolver {
return actions;
}
openDashboard($event: Event, dashboard: DashboardInfo) {
openDashboard($event: Event, dashboard: Dashboard) {
if ($event) {
$event.stopPropagation();
}
@ -436,13 +427,27 @@ export class DashboardsTableConfigResolver {
));
}
exportDashboard($event: Event, dashboard: DashboardInfo) {
exportDashboard($event: Event, dashboard: Dashboard) {
if ($event) {
$event.stopPropagation();
}
this.importExport.exportDashboard(dashboard.id.id);
}
importDashboardFile($event: Event, dashboard: Dashboard) {
if ($event) {
$event.stopPropagation();
}
return this.dialog.open<ImportDashboardFileDialogComponent, DashboardInfoDialogData,
boolean>(ImportDashboardFileDialogComponent, {
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {
dashboard
}
}).afterClosed();
}
addDashboardsToCustomer($event: Event) {
if ($event) {
$event.stopPropagation();
@ -463,7 +468,7 @@ export class DashboardsTableConfigResolver {
});
}
makePublic($event: Event, dashboard: DashboardInfo) {
makePublic($event: Event, dashboard: Dashboard) {
if ($event) {
$event.stopPropagation();
}
@ -484,7 +489,7 @@ export class DashboardsTableConfigResolver {
);
}
makePrivate($event: Event, dashboard: DashboardInfo) {
makePrivate($event: Event, dashboard: Dashboard) {
if ($event) {
$event.stopPropagation();
}
@ -506,7 +511,7 @@ export class DashboardsTableConfigResolver {
);
}
manageAssignedCustomers($event: Event, dashboard: DashboardInfo) {
manageAssignedCustomers($event: Event, dashboard: Dashboard) {
const assignedCustomersIds = dashboard.assignedCustomers ?
dashboard.assignedCustomers.map(customerInfo => customerInfo.customerId.id) : [];
this.showManageAssignedCustomersDialog($event, [dashboard.id.id], 'manage', assignedCustomersIds);
@ -543,7 +548,7 @@ export class DashboardsTableConfigResolver {
});
}
unassignFromCustomer($event: Event, dashboard: DashboardInfo, customerId: string) {
unassignFromCustomer($event: Event, dashboard: Dashboard, customerId: string) {
if ($event) {
$event.stopPropagation();
}
@ -593,7 +598,7 @@ export class DashboardsTableConfigResolver {
);
}
onDashboardAction(action: EntityAction<DashboardInfo>): boolean {
onDashboardAction(action: EntityAction<Dashboard>): boolean {
switch (action.action) {
case 'open':
this.openDashboard(action.event, action.entity);
@ -601,6 +606,9 @@ export class DashboardsTableConfigResolver {
case 'export':
this.exportDashboard(action.event, action.entity);
return true;
case 'import':
this.importDashboardFile(action.event, action.entity);
return true;
case 'makePublic':
this.makePublic(action.event, action.entity);
return true;

View File

@ -0,0 +1,73 @@
<!--
ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL
Copyright © 2016-2025 ThingsBoard, Inc. All Rights Reserved.
NOTICE: All information contained herein is, and remains
the property of ThingsBoard, Inc. and its suppliers,
if any. The intellectual and technical concepts contained
herein are proprietary to ThingsBoard, Inc.
and its suppliers and may be covered by U.S. and Foreign Patents,
patents in process, and are protected by trade secret or copyright law.
Dissemination of this information or reproduction of this material is strictly forbidden
unless prior written permission is obtained from COMPANY.
Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees,
managers or contractors who have executed Confidentiality and Non-disclosure agreements
explicitly covering such access.
The copyright notice above does not evidence any actual or intended publication
or disclosure of this source code, which includes
information that is confidential and/or proprietary, and is a trade secret, of COMPANY.
ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE,
OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT
THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED,
AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES.
THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION
DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS,
OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART.
-->
<form [formGroup]="uploadFileFormGroup" (ngSubmit)="save()" style="width: 800px;">
<mat-toolbar color="primary">
<h2>{{ 'dashboard.update-dashboard' | translate }}</h2>
<span class="flex-1"></span>
<button mat-icon-button
(click)="cancel()"
type="button">
<mat-icon class="material-icons">close</mat-icon>
</button>
</mat-toolbar>
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
</mat-progress-bar>
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
<div mat-dialog-content>
<tb-file-input [contentConvertFunction]="loadDataFromJsonContent"
[existingFileName]="currentFileName"
(fileNameChanged)="currentFileName = $event"
label="{{'dashboard.upload-file-to-update' | translate}}"
formControlName="file"
dropLabel="{{ 'import.drop-json-file-or' | translate }}"
accept=".json,application/json"
allowedExtensions="json">
</tb-file-input>
</div>
<div mat-dialog-actions class="flex flex-row items-center justify-end">
<button mat-button color="primary"
type="button"
[disabled]="(isLoading$ | async)"
(click)="cancel()" cdkFocusInitial>
{{ 'action.cancel' | translate }}
</button>
<button mat-raised-button color="primary"
type="submit"
[disabled]="(isLoading$ | async) || uploadFileFormGroup.invalid
|| !uploadFileFormGroup.dirty">
{{ 'action.save' | translate }}
</button>
</div>
</form>

View File

@ -0,0 +1,106 @@
///
/// ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL
///
/// Copyright © 2016-2025 ThingsBoard, Inc. All Rights Reserved.
///
/// NOTICE: All information contained herein is, and remains
/// the property of ThingsBoard, Inc. and its suppliers,
/// if any. The intellectual and technical concepts contained
/// herein are proprietary to ThingsBoard, Inc.
/// and its suppliers and may be covered by U.S. and Foreign Patents,
/// patents in process, and are protected by trade secret or copyright law.
///
/// Dissemination of this information or reproduction of this material is strictly forbidden
/// unless prior written permission is obtained from COMPANY.
///
/// Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees,
/// managers or contractors who have executed Confidentiality and Non-disclosure agreements
/// explicitly covering such access.
///
/// The copyright notice above does not evidence any actual or intended publication
/// or disclosure of this source code, which includes
/// information that is confidential and/or proprietary, and is a trade secret, of COMPANY.
/// ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE,
/// OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT
/// THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED,
/// AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES.
/// THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION
/// DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS,
/// OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART.
///
import {Component, Inject, OnInit} from '@angular/core';
import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog';
import {Store} from '@ngrx/store';
import {AppState} from '@core/core.state';
import {UntypedFormBuilder, UntypedFormGroup} from '@angular/forms';
import {DashboardService} from '@core/http/dashboard.service';
import {Dashboard, DashboardInfo} from '@app/shared/models/dashboard.models';
import {ActionNotificationShow} from '@core/notification/notification.actions';
import {TranslateService} from '@ngx-translate/core';
import {DialogComponent} from '@shared/components/dialog.component';
import {Router} from '@angular/router';
export interface DashboardInfoDialogData {
dashboard: Dashboard;
}
@Component({
selector: 'tb-import-dashboard-file-dialog',
templateUrl: './import-dashboard-file-dialog.component.html',
styleUrls: []
})
export class ImportDashboardFileDialogComponent extends DialogComponent<ImportDashboardFileDialogComponent> implements OnInit {
dashboard: Dashboard;
currentFileName: string = '';
uploadFileFormGroup: UntypedFormGroup;
constructor(protected store: Store<AppState>,
protected router: Router,
@Inject(MAT_DIALOG_DATA) public data: DashboardInfoDialogData,
public translate: TranslateService,
private dashboardService: DashboardService,
public dialogRef: MatDialogRef<ImportDashboardFileDialogComponent>,
public fb: UntypedFormBuilder) {
super(store, router, dialogRef);
this.dashboard = data.dashboard;
}
ngOnInit(): void {
this.uploadFileFormGroup = this.fb.group({
file: [null]
});
}
cancel(): void {
this.dialogRef.close();
}
save(){
const fileControl = this.uploadFileFormGroup.get('file');
if(!fileControl || !fileControl.value){
return;
}
const dashboardContent = {
...fileControl.value,
description: this.dashboard.configuration.description
};
this.dashboard.configuration = dashboardContent;
this.dashboardService.saveDashboard(this.dashboard).subscribe(()=>{
this.dialogRef.close(true);
})
}
loadDataFromJsonContent(content: string): any {
try {
const importData = JSON.parse(content);
return importData ? importData['configuration'] : importData;
} catch (err) {
this.store.dispatch(new ActionNotificationShow({message: err.message, type: 'error'}));
return null;
}
}
}

View File

@ -1348,6 +1348,7 @@
"mobile-hide": "Hide dashboard in mobile application",
"update-image": "Update dashboard image",
"update-dashboard": "Update the dashboard",
"upload-file-to-update": "Upload file to update",
"take-screenshot": "Take screenshot",
"select-widget-title": "Select widget",
"select-widget-value": "{{title}}: select widget",