diff --git a/ui-ngx/src/app/core/http/edge.service.ts b/ui-ngx/src/app/core/http/edge.service.ts index 078ada56aa..aac095412b 100644 --- a/ui-ngx/src/app/core/http/edge.service.ts +++ b/ui-ngx/src/app/core/http/edge.service.ts @@ -114,7 +114,7 @@ export class EdgeService { return this.http.post('/api/edge/bulk_import', entitiesData, defaultHttpOptionsFromConfig(config)); } - public getEdgeDockerInstallInstructions(edgeId: string, config?: RequestConfig): Observable { - return this.http.get(`/api/edge/instructions/${edgeId}`, defaultHttpOptionsFromConfig(config)); + public getEdgeDockerInstallInstructions(edgeId: string, method: string, config?: RequestConfig): Observable { + return this.http.get(`/api/edge/instructions/${edgeId}/${method}`, defaultHttpOptionsFromConfig(config)); } } diff --git a/ui-ngx/src/app/modules/home/pages/edge/edge-instructions-dialog.component.html b/ui-ngx/src/app/modules/home/pages/edge/edge-instructions-dialog.component.html index 599d984738..cf834e203e 100644 --- a/ui-ngx/src/app/modules/home/pages/edge/edge-instructions-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/edge/edge-instructions-dialog.component.html @@ -15,29 +15,74 @@ limitations under the License. --> -
- -

info_outline - {{ 'edge.install-connect-instructions' | translate }}

- - -
- - -
-
- -
-
- -
+ +

{{ dialogTitle }}

+ + +
+
+
+
+ + + + + Ubuntu + + +
+
+ +
+
+
+
+ + + + CentOS + + +
+
+ +
+
+
+
+ + + + Docker + + +
+
+ +
+
+
+
+
+
+
+
+ {{ 'action.dont-show-again' | translate}} + + +
+ +
+ + + {{ 'edge.loading-edge-instructions' | translate }} + +
+
diff --git a/ui-ngx/src/app/modules/home/pages/edge/edge-instructions-dialog.component.scss b/ui-ngx/src/app/modules/home/pages/edge/edge-instructions-dialog.component.scss new file mode 100644 index 0000000000..d69eeb6c23 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/edge/edge-instructions-dialog.component.scss @@ -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; + } +} diff --git a/ui-ngx/src/app/modules/home/pages/edge/edge-instructions-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/edge/edge-instructions-dialog.component.ts index a4dfe4345b..51d7e42238 100644 --- a/ui-ngx/src/app/modules/home/pages/edge/edge-instructions-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/pages/edge/edge-instructions-dialog.component.ts @@ -14,33 +14,83 @@ /// limitations under the License. /// -import { Component, Inject } from '@angular/core'; -import { MAT_DIALOG_DATA, MatDialogRef } from "@angular/material/dialog"; -import { DialogComponent } from "@shared/components/dialog.component"; -import { Store } from "@ngrx/store"; -import { AppState } from "@core/core.state"; -import { Router } from "@angular/router"; - -export interface EdgeInstructionsData { - instructions: string; +import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; +import { DialogComponent } from '@shared/components/dialog.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +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 EdgeInstructionsDialogData { + edge: EdgeInfo; + afterAdd: boolean; } @Component({ - selector: 'tb-edge-instructions', - templateUrl: './edge-instructions-dialog.component.html' + selector: 'tb-edge-installation-dialog', + templateUrl: './edge-instructions-dialog.component.html', + styleUrls: ['./edge-instructions-dialog.component.scss'] }) -export class EdgeInstructionsDialogComponent extends DialogComponent { +export class EdgeInstructionsDialogComponent extends DialogComponent implements OnInit, OnDestroy { + + dialogTitle: string; + showDontShowAgain: boolean; - instructions: string = this.data.instructions; + loadedInstructions = false; + notShowAgain = false; + tabIndex = 0; + instructionsMethod = EdgeInstructionsMethod; + contentData: any = {}; constructor(protected store: Store, protected router: Router, - public dialogRef: MatDialogRef, - @Inject(MAT_DIALOG_DATA) public data: EdgeInstructionsData) { + @Inject(MAT_DIALOG_DATA) private data: EdgeInstructionsDialogData, + public dialogRef: MatDialogRef, + private edgeService: EdgeService) { 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; + } + } + + 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); + } + } + + selectedTabChange(index: number) { + this.getInstructions(this.instructionsMethod[index]); } - cancel(): void { - this.dialogRef.close(null); + 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; + } + ); + } } } diff --git a/ui-ngx/src/app/modules/home/pages/edge/edges-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/edge/edges-table-config.resolver.ts index 1acaff3841..6a80388b52 100644 --- a/ui-ngx/src/app/modules/home/pages/edge/edges-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/edge/edges-table-config.resolver.ts @@ -29,10 +29,10 @@ import { 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 { AddEntityDialogData, 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 { selectAuthUser, selectUserSettingsProperty } 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'; @@ -51,7 +51,7 @@ import { AddEntitiesToCustomerDialogData } from '../../dialogs/add-entities-to-customer-dialog.component'; 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 { EdgeComponent } from '@home/pages/edge/edge.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 { ActionNotificationShow } from '@core/notification/notification.actions'; import { - EdgeInstructionsData, - EdgeInstructionsDialogComponent -} from "@home/pages/edge/edge-instructions-dialog.component"; + EdgeInstructionsDialogComponent, + EdgeInstructionsDialogData +} from '@home/pages/edge/edge-instructions-dialog.component'; +import { AddEntityDialogComponent } from '@home/components/entity/add-entity-dialog.component'; @Injectable() export class EdgesTableConfigResolver implements Resolve> { @@ -140,6 +141,7 @@ export class EdgesTableConfigResolver implements Resolve this.config.componentsData.edgeScope === 'tenant'; + this.config.addEntity = () => { this.addEdge(); return of(null); }; return this.config; }) ); @@ -530,21 +532,50 @@ export class EdgesTableConfigResolver implements Resolve, + EdgeInfo>(AddEntityDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + 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.edgeService.getEdgeDockerInstallInstructions(edge.id.id).subscribe( - (edgeInstructionsTemplate: EdgeInstallInstructions) => { - this.dialog.open(EdgeInstructionsDialogComponent, { - disableClose: false, - panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], - data: { - instructions: edgeInstructionsTemplate.dockerInstallInstructions - } - }); + this.dialog.open + (EdgeInstructionsDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + edge, + afterAdd } - ) + }).afterClosed().subscribe(() => { + if (afterAdd) { + this.config.updateData(); + } + } + ); } onEdgeAction(action: EntityAction, config: EntityTableConfig): boolean { diff --git a/ui-ngx/src/app/shared/models/edge.models.ts b/ui-ngx/src/app/shared/models/edge.models.ts index 1acfa2ca28..7b5a6ddb3d 100644 --- a/ui-ngx/src/app/shared/models/edge.models.ts +++ b/ui-ngx/src/app/shared/models/edge.models.ts @@ -179,5 +179,11 @@ export interface EdgeEvent extends BaseData { } export interface EdgeInstallInstructions { - dockerInstallInstructions: string; + installInstructions: string; +} + +export enum EdgeInstructionsMethod { + ubuntu, + centos, + docker } diff --git a/ui-ngx/src/app/shared/models/user-settings.models.ts b/ui-ngx/src/app/shared/models/user-settings.models.ts index ac0bd95c29..bd64e0241b 100644 --- a/ui-ngx/src/app/shared/models/user-settings.models.ts +++ b/ui-ngx/src/app/shared/models/user-settings.models.ts @@ -17,6 +17,7 @@ export interface UserSettings { openedMenuSections?: string[]; notDisplayConnectivityAfterAddDevice?: boolean; + notDisplayInstructionsAfterAddEdge?: boolean; } export const initialUserSettings: UserSettings = { diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 672ed9b1b2..6e9c0375c9 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -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.", "import": "Import edge", "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", "load-entity-error": "Failed to load data. Entity has been deleted.", "assign-new-edge": "Assign new edge",