Edge instructions show after add - UI

This commit is contained in:
deaflynx 2023-09-13 14:28:59 +03:00
parent 8412b61812
commit c3f4f8d3a1
8 changed files with 325 additions and 61 deletions

View File

@ -114,7 +114,7 @@ export class EdgeService {
return this.http.post<BulkImportResult>('/api/edge/bulk_import', entitiesData, defaultHttpOptionsFromConfig(config)); return this.http.post<BulkImportResult>('/api/edge/bulk_import', entitiesData, defaultHttpOptionsFromConfig(config));
} }
public getEdgeDockerInstallInstructions(edgeId: string, config?: RequestConfig): Observable<EdgeInstallInstructions> { public getEdgeDockerInstallInstructions(edgeId: string, method: string, config?: RequestConfig): Observable<EdgeInstallInstructions> {
return this.http.get<EdgeInstallInstructions>(`/api/edge/instructions/${edgeId}`, defaultHttpOptionsFromConfig(config)); return this.http.get<EdgeInstallInstructions>(`/api/edge/instructions/${edgeId}/${method}`, defaultHttpOptionsFromConfig(config));
} }
} }

View File

@ -15,29 +15,74 @@
limitations under the License. limitations under the License.
--> -->
<div style="min-width: 800px;">
<mat-toolbar color="primary"> <mat-toolbar color="primary">
<h2><mat-icon>info_outline</mat-icon> <h2 translate>{{ dialogTitle }}</h2>
{{ 'edge.install-connect-instructions' | translate }}</h2>
<span fxFlex></span> <span fxFlex></span>
<button mat-icon-button <button mat-icon-button
(click)="cancel()" (click)="close()"
type="button"> type="button">
<mat-icon class="material-icons">close</mat-icon> <mat-icon class="material-icons">close</mat-icon>
</button> </button>
</mat-toolbar> </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> <div mat-dialog-content>
<tb-markdown [data]="instructions" lineNumbers fallbackToPlainMarkdown></tb-markdown> <section *ngIf="loadedInstructions; else loadingInstructions" class="tb-form-panel no-padding no-border">
<div class="tb-form-panel no-padding no-border">
<mat-tab-group [(selectedIndex)]="tabIndex" (selectedTabChange)="selectedTabChange(tabIndex)">
<mat-tab>
<ng-template mat-tab-label>
<mat-icon class="tabs-icon" svgIcon="linux"></mat-icon>
Ubuntu
</ng-template>
<ng-template matTabContent>
<div class="tb-form-panel no-padding no-border tb-tab-body">
<div class="tb-form-panel stroked">
<tb-markdown lineNumbers fallbackToPlainMarkdown [data]='contentData.ubuntu'></tb-markdown>
</div> </div>
<div mat-dialog-actions fxLayout="row" fxLayoutAlign="end center"> </div>
<button mat-button color="primary" </ng-template>
type="button" </mat-tab>
<mat-tab>
<ng-template mat-tab-label>
<mat-icon class="tabs-icon" svgIcon="mdi:centos"></mat-icon>
CentOS
</ng-template>
<ng-template matTabContent>
<div class="tb-form-panel no-padding no-border tb-tab-body">
<div class="tb-form-panel stroked">
<tb-markdown lineNumbers fallbackToPlainMarkdown [data]='contentData.centos'></tb-markdown>
</div>
</div>
</ng-template>
</mat-tab>
<mat-tab>
<ng-template mat-tab-label>
<mat-icon class="tabs-icon" svgIcon="docker"></mat-icon>
Docker
</ng-template>
<ng-template matTabContent>
<div class="tb-form-panel no-padding no-border tb-tab-body">
<div class="tb-form-panel stroked">
<tb-markdown lineNumbers fallbackToPlainMarkdown [data]='contentData.docker'></tb-markdown>
</div>
</div>
</ng-template>
</mat-tab>
</mat-tab-group>
</div>
</section>
</div>
<div mat-dialog-actions class="tb-dialog-actions">
<mat-slide-toggle fxShow="{{ showDontShowAgain }}" [(ngModel)]="notShowAgain">{{ 'action.dont-show-again' | translate}}</mat-slide-toggle>
<span fxFlex></span>
<button mat-button
[disabled]="(isLoading$ | async)" [disabled]="(isLoading$ | async)"
(click)="cancel()" cdkFocusInitial> (click)="close()">{{ 'action.close' | translate }}</button>
{{ 'action.close' | translate }}
</button>
</div> </div>
<ng-template #loadingInstructions>
<div class="tb-loader">
<mat-spinner color="accent" diameter="65" strokeWidth="4"></mat-spinner>
<span class="mat-subtitle-1 label">
{{ 'edge.loading-edge-instructions' | translate }}
</span>
</div> </div>
</ng-template>

View File

@ -0,0 +1,129 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@import "../../../../../scss/constants";
:host {
height: 100%;
max-height: 100vh;
display: grid;
grid-template-rows: min-content minmax(auto, 1fr) min-content;
.tb-loader {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 16px;
height: 300px;
max-height: 100%;
.label {
margin-bottom: 0;
text-align: center;
}
}
.tb-font-14 {
font-size: 14px;
}
.tb-flex-1 {
flex: 1;
}
@media #{$mat-sm} {
width: 470px;
}
@media #{$mat-gt-sm} {
width: 720px;
}
}
:host-context(.mat-mdc-dialog-container) {
.tb-dialog-actions {
display: flex;
gap: 8px;
padding: 8px 16px;
}
.mat-mdc-dialog-content {
max-height: 80vh;
padding: 16px;
}
}
:host ::ng-deep {
.tb-markdown-view {
.tb-command-code {
.code-wrapper {
padding: 0;
pre[class*=language-] {
margin: 0;
background: #F3F6FA;
border-color: #305680;
padding-right: 38px;
overflow: scroll;
padding-bottom: 4px;
&::-webkit-scrollbar {
width: 4px;
height: 4px;
}
}
}
button.clipboard-btn {
right: -2px;
p {
color: #305680;
}
p, div {
background-color: #F3F6FA;
}
div {
img {
display: none;
}
&:after {
content: "";
position: initial;
display: block;
width: 18px;
height: 18px;
background: #305680;
mask-image: url(/assets/copy-code-icon.svg);
mask-repeat: no-repeat;
}
}
}
}
}
.mdc-button__label > span {
.mat-icon {
vertical-align: text-bottom;
box-sizing: initial;
}
}
.tabs-icon {
margin-right: 8px;
}
.tb-form-panel.tb-tab-body {
padding: 16px 0 0;
}
}

View File

@ -14,33 +14,83 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { Component, Inject } from '@angular/core'; import { Component, Inject, OnDestroy, OnInit } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from "@angular/material/dialog"; import { DialogComponent } from '@shared/components/dialog.component';
import { DialogComponent } from "@shared/components/dialog.component"; import { Store } from '@ngrx/store';
import { Store } from "@ngrx/store"; import { AppState } from '@core/core.state';
import { AppState } from "@core/core.state"; import { Router } from '@angular/router';
import { Router } from "@angular/router"; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { ActionPreferencesPutUserSettings } from '@core/auth/auth.actions';
import { EdgeInfo, EdgeInstructionsMethod } from '@shared/models/edge.models';
import { EdgeService } from '@core/http/edge.service';
export interface EdgeInstructionsData { export interface EdgeInstructionsDialogData {
instructions: string; edge: EdgeInfo;
afterAdd: boolean;
} }
@Component({ @Component({
selector: 'tb-edge-instructions', selector: 'tb-edge-installation-dialog',
templateUrl: './edge-instructions-dialog.component.html' templateUrl: './edge-instructions-dialog.component.html',
styleUrls: ['./edge-instructions-dialog.component.scss']
}) })
export class EdgeInstructionsDialogComponent extends DialogComponent<EdgeInstructionsDialogComponent, EdgeInstructionsData> { export class EdgeInstructionsDialogComponent extends DialogComponent<EdgeInstructionsDialogComponent> implements OnInit, OnDestroy {
instructions: string = this.data.instructions; dialogTitle: string;
showDontShowAgain: boolean;
loadedInstructions = false;
notShowAgain = false;
tabIndex = 0;
instructionsMethod = EdgeInstructionsMethod;
contentData: any = {};
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
protected router: Router, protected router: Router,
public dialogRef: MatDialogRef<EdgeInstructionsDialogComponent, EdgeInstructionsData>, @Inject(MAT_DIALOG_DATA) private data: EdgeInstructionsDialogData,
@Inject(MAT_DIALOG_DATA) public data: EdgeInstructionsData) { public dialogRef: MatDialogRef<EdgeInstructionsDialogComponent>,
private edgeService: EdgeService) {
super(store, router, dialogRef); super(store, router, dialogRef);
if (this.data.afterAdd) {
this.dialogTitle = 'edge.install-connect-instructions-edge-created';
this.showDontShowAgain = true;
} else {
this.dialogTitle = 'edge.install-connect-instructions';
this.showDontShowAgain = false;
}
} }
cancel(): void { ngOnInit() {
this.getInstructions(this.instructionsMethod[this.tabIndex]);
}
ngOnDestroy() {
super.ngOnDestroy();
}
close(): void {
if (this.notShowAgain && this.showDontShowAgain) {
this.store.dispatch(new ActionPreferencesPutUserSettings({notDisplayInstructionsAfterAddEdge: true}));
this.dialogRef.close(null);
} else {
this.dialogRef.close(null); this.dialogRef.close(null);
} }
} }
selectedTabChange(index: number) {
this.getInstructions(this.instructionsMethod[index]);
}
getInstructions(method: string) {
if (!this.contentData[method]) {
this.loadedInstructions = false;
this.edgeService.getEdgeDockerInstallInstructions(this.data.edge.id.id, method).subscribe(
res => {
this.contentData[method] = res.installInstructions;
this.loadedInstructions = true;
}
);
}
}
}

View File

@ -29,10 +29,10 @@ import {
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { DatePipe } from '@angular/common'; import { DatePipe } from '@angular/common';
import { EntityType, entityTypeResources, entityTypeTranslations } from '@shared/models/entity-type.models'; import { EntityType, entityTypeResources, entityTypeTranslations } from '@shared/models/entity-type.models';
import { EntityAction } from '@home/models/entity/entity-component.models'; import { AddEntityDialogData, EntityAction } from '@home/models/entity/entity-component.models';
import { forkJoin, Observable, of } from 'rxjs'; import { forkJoin, Observable, of } from 'rxjs';
import { select, Store } from '@ngrx/store'; import { select, Store } from '@ngrx/store';
import { selectAuthUser } from '@core/auth/auth.selectors'; import { selectAuthUser, selectUserSettingsProperty } from '@core/auth/auth.selectors';
import { map, mergeMap, take, tap } from 'rxjs/operators'; import { map, mergeMap, take, tap } from 'rxjs/operators';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
import { Authority } from '@app/shared/models/authority.enum'; import { Authority } from '@app/shared/models/authority.enum';
@ -51,7 +51,7 @@ import {
AddEntitiesToCustomerDialogData AddEntitiesToCustomerDialogData
} from '../../dialogs/add-entities-to-customer-dialog.component'; } from '../../dialogs/add-entities-to-customer-dialog.component';
import { HomeDialogsService } from '@home/dialogs/home-dialogs.service'; import { HomeDialogsService } from '@home/dialogs/home-dialogs.service';
import { Edge, EdgeInfo, EdgeInstallInstructions } from '@shared/models/edge.models'; import { Edge, EdgeInfo } from '@shared/models/edge.models';
import { EdgeService } from '@core/http/edge.service'; import { EdgeService } from '@core/http/edge.service';
import { EdgeComponent } from '@home/pages/edge/edge.component'; import { EdgeComponent } from '@home/pages/edge/edge.component';
import { EdgeTableHeaderComponent } from '@home/pages/edge/edge-table-header.component'; import { EdgeTableHeaderComponent } from '@home/pages/edge/edge-table-header.component';
@ -59,9 +59,10 @@ import { EdgeId } from '@shared/models/id/edge-id';
import { EdgeTabsComponent } from '@home/pages/edge/edge-tabs.component'; import { EdgeTabsComponent } from '@home/pages/edge/edge-tabs.component';
import { ActionNotificationShow } from '@core/notification/notification.actions'; import { ActionNotificationShow } from '@core/notification/notification.actions';
import { import {
EdgeInstructionsData, EdgeInstructionsDialogComponent,
EdgeInstructionsDialogComponent EdgeInstructionsDialogData
} from "@home/pages/edge/edge-instructions-dialog.component"; } from '@home/pages/edge/edge-instructions-dialog.component';
import { AddEntityDialogComponent } from '@home/components/entity/add-entity-dialog.component';
@Injectable() @Injectable()
export class EdgesTableConfigResolver implements Resolve<EntityTableConfig<EdgeInfo>> { export class EdgesTableConfigResolver implements Resolve<EntityTableConfig<EdgeInfo>> {
@ -140,6 +141,7 @@ export class EdgesTableConfigResolver implements Resolve<EntityTableConfig<EdgeI
this.config.addEnabled = this.config.componentsData.edgeScope !== 'customer_user'; this.config.addEnabled = this.config.componentsData.edgeScope !== 'customer_user';
this.config.entitiesDeleteEnabled = this.config.componentsData.edgeScope === 'tenant'; this.config.entitiesDeleteEnabled = this.config.componentsData.edgeScope === 'tenant';
this.config.deleteEnabled = () => this.config.componentsData.edgeScope === 'tenant'; this.config.deleteEnabled = () => this.config.componentsData.edgeScope === 'tenant';
this.config.addEntity = () => { this.addEdge(); return of(null); };
return this.config; return this.config;
}) })
); );
@ -530,21 +532,50 @@ export class EdgesTableConfigResolver implements Resolve<EntityTableConfig<EdgeI
); );
} }
openInstructions($event, edge) { addEdge() {
if ($event) { this.dialog.open<AddEntityDialogComponent, AddEntityDialogData<EdgeInfo>,
$event.stopPropagation(); EdgeInfo>(AddEntityDialogComponent, {
} disableClose: true,
this.edgeService.getEdgeDockerInstallInstructions(edge.id.id).subscribe(
(edgeInstructionsTemplate: EdgeInstallInstructions) => {
this.dialog.open<EdgeInstructionsDialogComponent, EdgeInstructionsData>(EdgeInstructionsDialogComponent, {
disableClose: false,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: { data: {
instructions: edgeInstructionsTemplate.dockerInstallInstructions entitiesTableConfig: this.config
}
}).afterClosed().subscribe(
(entity) => {
if (entity) {
this.store.pipe(select(selectUserSettingsProperty('notDisplayInstructionsAfterAddEdge'))).pipe(
take(1)
).subscribe((settings: boolean) => {
if (!settings) {
this.openInstructions(null, entity, true);
} else {
this.config.updateData();
this.config.entityAdded(entity);
} }
}); });
} }
) }
);
}
openInstructions($event, edge: EdgeInfo, afterAdd = false) {
if ($event) {
$event.stopPropagation();
}
this.dialog.open<EdgeInstructionsDialogComponent, EdgeInstructionsDialogData>
(EdgeInstructionsDialogComponent, {
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {
edge,
afterAdd
}
}).afterClosed().subscribe(() => {
if (afterAdd) {
this.config.updateData();
}
}
);
} }
onEdgeAction(action: EntityAction<EdgeInfo>, config: EntityTableConfig<EdgeInfo>): boolean { onEdgeAction(action: EntityAction<EdgeInfo>, config: EntityTableConfig<EdgeInfo>): boolean {

View File

@ -179,5 +179,11 @@ export interface EdgeEvent extends BaseData<EventId> {
} }
export interface EdgeInstallInstructions { export interface EdgeInstallInstructions {
dockerInstallInstructions: string; installInstructions: string;
}
export enum EdgeInstructionsMethod {
ubuntu,
centos,
docker
} }

View File

@ -17,6 +17,7 @@
export interface UserSettings { export interface UserSettings {
openedMenuSections?: string[]; openedMenuSections?: string[];
notDisplayConnectivityAfterAddDevice?: boolean; notDisplayConnectivityAfterAddDevice?: boolean;
notDisplayInstructionsAfterAddEdge?: boolean;
} }
export const initialUserSettings: UserSettings = { export const initialUserSettings: UserSettings = {

View File

@ -1962,6 +1962,8 @@
"make-private-edge-text": "After the confirmation the edge and all its data will be made private and won't be accessible by others.", "make-private-edge-text": "After the confirmation the edge and all its data will be made private and won't be accessible by others.",
"import": "Import edge", "import": "Import edge",
"install-connect-instructions": "Install & Connect Instructions", "install-connect-instructions": "Install & Connect Instructions",
"install-connect-instructions-edge-created": "Edge created! Check Install & Connect Instructions",
"loading-edge-instructions": "Loading edge instructions...",
"label": "Label", "label": "Label",
"load-entity-error": "Failed to load data. Entity has been deleted.", "load-entity-error": "Failed to load data. Entity has been deleted.",
"assign-new-edge": "Assign new edge", "assign-new-edge": "Assign new edge",