From 2332445c030922a16cf801942c2b0ad5f8d0c93e Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 31 Mar 2026 12:08:06 +0300 Subject: [PATCH] Add card install/update state, itemType filter, and detail dialog image carousel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add @Input installedItem to card component with install/installed/update states - Add updateClick output to card, wire update dialog from browse component - Add itemType filter to getInstalledItems across full stack (controller → DAO) - Fetch installed solution templates on browse page when tab selected - Pass installedItem to detail dialog from browse component - Reload items on install/update dialog close for solution templates - Add MpItemVersionResource model and resources field to MpItemVersionView - Add image carousel (ngx-hm-carousel) to detail dialog for solution templates - Carousel: screenshots preferred over icons, autoplay 3s, dots below images --- .../server/controller/IotHubController.java | 5 +- .../dao/iot_hub/IotHubInstalledItemDao.java | 2 +- .../iot_hub/IotHubInstalledItemService.java | 2 +- .../IotHubInstalledItemServiceImpl.java | 4 +- .../IotHubInstalledItemRepository.java | 3 +- .../iot_hub/JpaIotHubInstalledItemDao.java | 3 +- .../src/app/core/http/iot-hub-api.service.ts | 8 +- .../iot-hub/iot-hub-browse.component.html | 4 +- .../pages/iot-hub/iot-hub-browse.component.ts | 58 ++++++++++++- .../iot-hub-installed-items.component.ts | 2 +- .../iot-hub/iot-hub-item-card.component.html | 10 ++- .../iot-hub/iot-hub-item-card.component.scss | 28 +++++++ .../iot-hub/iot-hub-item-card.component.ts | 16 ++++ .../iot-hub-item-detail-dialog.component.html | 58 ++++++++++++- .../iot-hub-item-detail-dialog.component.scss | 84 +++++++++++++++++++ .../iot-hub-item-detail-dialog.component.ts | 16 ++++ .../iot-hub-update-dialog.component.ts | 22 ++++- .../models/iot-hub/iot-hub-version.models.ts | 6 ++ 18 files changed, 310 insertions(+), 21 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/IotHubController.java b/application/src/main/java/org/thingsboard/server/controller/IotHubController.java index 92e1a6f2de..86253a9195 100644 --- a/application/src/main/java/org/thingsboard/server/controller/IotHubController.java +++ b/application/src/main/java/org/thingsboard/server/controller/IotHubController.java @@ -81,9 +81,10 @@ public class IotHubController extends BaseController { @RequestParam int page, @RequestParam(required = false) String textSearch, @RequestParam(required = false) String sortProperty, - @RequestParam(required = false) String sortOrder) throws ThingsboardException { + @RequestParam(required = false) String sortOrder, + @RequestParam(required = false) String itemType) throws ThingsboardException { PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); - return iotHubInstalledItemService.findByTenantId(getTenantId(), pageLink); + return iotHubInstalledItemService.findByTenantId(getTenantId(), itemType, pageLink); } @PreAuthorize("hasAuthority('TENANT_ADMIN')") diff --git a/dao/src/main/java/org/thingsboard/server/dao/iot_hub/IotHubInstalledItemDao.java b/dao/src/main/java/org/thingsboard/server/dao/iot_hub/IotHubInstalledItemDao.java index 82abff4bf1..e5333c71ef 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/iot_hub/IotHubInstalledItemDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/iot_hub/IotHubInstalledItemDao.java @@ -26,7 +26,7 @@ import java.util.UUID; public interface IotHubInstalledItemDao extends Dao { - PageData findByTenantId(TenantId tenantId, PageLink pageLink); + PageData findByTenantId(TenantId tenantId, String itemType, PageLink pageLink); List findInstalledItemIdsByTenantId(TenantId tenantId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/iot_hub/IotHubInstalledItemService.java b/dao/src/main/java/org/thingsboard/server/dao/iot_hub/IotHubInstalledItemService.java index ccc1de3bc1..8973292f90 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/iot_hub/IotHubInstalledItemService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/iot_hub/IotHubInstalledItemService.java @@ -30,7 +30,7 @@ public interface IotHubInstalledItemService { IotHubInstalledItem findById(TenantId tenantId, IotHubInstalledItemId id); - PageData findByTenantId(TenantId tenantId, PageLink pageLink); + PageData findByTenantId(TenantId tenantId, String itemType, PageLink pageLink); List findInstalledItemIdsByTenantId(TenantId tenantId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/iot_hub/IotHubInstalledItemServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/iot_hub/IotHubInstalledItemServiceImpl.java index 1642ba3870..3a28a3a01d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/iot_hub/IotHubInstalledItemServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/iot_hub/IotHubInstalledItemServiceImpl.java @@ -46,8 +46,8 @@ class IotHubInstalledItemServiceImpl implements IotHubInstalledItemService { } @Override - public PageData findByTenantId(TenantId tenantId, PageLink pageLink) { - return iotHubInstalledItemDao.findByTenantId(tenantId, pageLink); + public PageData findByTenantId(TenantId tenantId, String itemType, PageLink pageLink) { + return iotHubInstalledItemDao.findByTenantId(tenantId, itemType, pageLink); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/iot_hub/IotHubInstalledItemRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/iot_hub/IotHubInstalledItemRepository.java index bb245133b5..84f2da1bf0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/iot_hub/IotHubInstalledItemRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/iot_hub/IotHubInstalledItemRepository.java @@ -35,12 +35,13 @@ interface IotHubInstalledItemRepository extends JpaRepository findByTenantId(@Param("tenantId") UUID tenantId, @Param("textSearch") String textSearch, Pageable pageable); + Page findByTenantId(@Param("tenantId") UUID tenantId, @Param("itemType") String itemType, @Param("textSearch") String textSearch, Pageable pageable); @Transactional @Modifying diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/iot_hub/JpaIotHubInstalledItemDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/iot_hub/JpaIotHubInstalledItemDao.java index ee837f6363..33a15c91b1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/iot_hub/JpaIotHubInstalledItemDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/iot_hub/JpaIotHubInstalledItemDao.java @@ -43,9 +43,10 @@ class JpaIotHubInstalledItemDao extends JpaAbstractDao findByTenantId(TenantId tenantId, PageLink pageLink) { + public PageData findByTenantId(TenantId tenantId, String itemType, PageLink pageLink) { return DaoUtil.toPageData(repository.findByTenantId( tenantId.getId(), + itemType, StringUtils.defaultIfEmpty(pageLink.getTextSearch(), null), toPageRequest(pageLink)) ); diff --git a/ui-ngx/src/app/core/http/iot-hub-api.service.ts b/ui-ngx/src/app/core/http/iot-hub-api.service.ts index 4d4d375e5f..d6816b69d1 100644 --- a/ui-ngx/src/app/core/http/iot-hub-api.service.ts +++ b/ui-ngx/src/app/core/http/iot-hub-api.service.ts @@ -139,9 +139,13 @@ export class IotHubApiService { ); } - public getInstalledItems(pageLink: PageLink, config?: IotHubRequestConfig): Observable> { + public getInstalledItems(pageLink: PageLink, itemType?: string, config?: IotHubRequestConfig): Observable> { + let query = pageLink.toQuery(); + if (itemType) { + query += `&itemType=${itemType}`; + } return this.http.get>( - `/api/iot-hub/installedItems${pageLink.toQuery()}`, + `/api/iot-hub/installedItems${query}`, { params: this.buildParams(config) } ); } diff --git a/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-browse.component.html b/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-browse.component.html index b352a5293b..05bf3d72ad 100644 --- a/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-browse.component.html +++ b/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-browse.component.html @@ -196,11 +196,13 @@ @for (item of items; track item.id) { + (installClick)="installItem($event)" + (updateClick)="updateItem($event)"> } diff --git a/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-browse.component.ts b/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-browse.component.ts index 5ca56d4a55..4a9418eec8 100644 --- a/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-browse.component.ts +++ b/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-browse.component.ts @@ -26,12 +26,14 @@ import { getCategoriesForType, useCaseTranslations } from '@shared/models/iot-hub/iot-hub-item.models'; import { cfTypeTranslations, widgetTypeTranslations, ruleChainTypeTranslations } from '@shared/models/iot-hub/iot-hub-version.models'; +import { IotHubInstalledItem } from '@shared/models/iot-hub/iot-hub-installed-item.models'; import { IotHubApiService } from '@core/http/iot-hub-api.service'; import { MatDialog } from '@angular/material/dialog'; import { TranslateService } from '@ngx-translate/core'; import { Router } from '@angular/router'; import { TbIotHubItemDetailDialogComponent, IotHubItemDetailDialogData } from './iot-hub-item-detail-dialog.component'; import { TbIotHubInstallDialogComponent, IotHubInstallDialogData } from './iot-hub-install-dialog.component'; +import { TbIotHubUpdateDialogComponent, IotHubUpdateDialogData } from './iot-hub-update-dialog.component'; interface SortOption { value: string; @@ -90,6 +92,8 @@ export class TbIotHubBrowseComponent implements OnInit, OnDestroy { widgetTypes: Map = widgetTypeTranslations; ruleChainTypes: Map = ruleChainTypeTranslations; + installedSolutionTemplates: IotHubInstalledItem[] = null; + private searchSubject = new Subject(); private destroy$ = new Subject(); @@ -139,6 +143,9 @@ export class TbIotHubBrowseComponent implements OnInit, OnDestroy { this.activeRuleChainTypes.clear(); this.updateCategories(); this.pageIndex = 0; + if (type === ItemType.SOLUTION_TEMPLATE) { + this.loadInstalledSolutionTemplates(); + } this.loadItems(); } @@ -326,27 +333,62 @@ export class TbIotHubBrowseComponent implements OnInit, OnDestroy { } } + getInstalledItem(item: MpItemVersionView): IotHubInstalledItem | undefined { + if (this.activeType !== ItemType.SOLUTION_TEMPLATE || !this.installedSolutionTemplates) { + return undefined; + } + return this.installedSolutionTemplates.find(i => i.itemId === item.itemId); + } + openItemDetail(item: MpItemVersionView): void { this.dialog.open(TbIotHubItemDetailDialogComponent, { panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], autoFocus: false, data: { item, - iotHubApiService: this.iotHubApiService + iotHubApiService: this.iotHubApiService, + installedItem: this.getInstalledItem(item) } as IotHubItemDetailDialogData }); } installItem(item: MpItemVersionView): void { - this.dialog.open(TbIotHubInstallDialogComponent, { + const dialogRef = this.dialog.open(TbIotHubInstallDialogComponent, { panelClass: ['tb-dialog'], data: { item, iotHubApiService: this.iotHubApiService } as IotHubInstallDialogData }); + dialogRef.afterClosed().subscribe(result => { + if (result === 'installed' && this.activeType === ItemType.SOLUTION_TEMPLATE) { + this.loadItems(); + } + }); } + updateItem(item: MpItemVersionView): void { + const installedItem = this.getInstalledItem(item); + if (!installedItem) { + return; + } + const dialogRef = this.dialog.open(TbIotHubUpdateDialogComponent, { + panelClass: ['tb-dialog'], + data: { + installedItemId: installedItem.id.id, + itemName: item.name, + itemType: item.type, + version: item.version, + versionId: item.id, + iotHubApiService: this.iotHubApiService + } as IotHubUpdateDialogData + }); + dialogRef.afterClosed().subscribe(result => { + if (result === 'updated' && this.activeType === ItemType.SOLUTION_TEMPLATE) { + this.loadItems(); + } + }); + } navigateToCreator(creatorId: string): void { this.router.navigate(['/iot-hub/creator', creatorId]); @@ -356,6 +398,18 @@ export class TbIotHubBrowseComponent implements OnInit, OnDestroy { this.router.navigate(['/iot-hub/installed']); } + private loadInstalledSolutionTemplates(): void { + if (this.installedSolutionTemplates !== null) { + return; + } + const pageLink = new PageLink(10000, 0); + this.iotHubApiService.getInstalledItems(pageLink, ItemType.SOLUTION_TEMPLATE, {ignoreLoading: true}).subscribe({ + next: (data) => { + this.installedSolutionTemplates = data.data; + } + }); + } + private updateCategories(): void { this.categories = getCategoriesForType(this.activeType); } diff --git a/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-installed-items.component.ts b/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-installed-items.component.ts index 31d00f041f..a8401a2313 100644 --- a/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-installed-items.component.ts +++ b/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-installed-items.component.ts @@ -258,7 +258,7 @@ export class TbIotHubInstalledItemsComponent implements OnInit, AfterViewInit { direction: this.sort?.direction === 'asc' ? Direction.ASC : Direction.DESC }; const pageLink = new PageLink(this.pageSize, this.pageIndex, this.textSearch || null, sortOrder); - this.iotHubApiService.getInstalledItems(pageLink, {ignoreLoading: true}).subscribe({ + this.iotHubApiService.getInstalledItems(pageLink, null, {ignoreLoading: true}).subscribe({ next: (data) => { this.dataSource = data.data; this.totalElements = data.totalElements; diff --git a/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-item-card.component.html b/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-item-card.component.html index 1750548a2a..94be8cb80b 100644 --- a/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-item-card.component.html +++ b/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-item-card.component.html @@ -100,7 +100,15 @@ download {{ item.totalInstallCount | shortNumber }} - + @if (isInstalled()) { + @if (isSameVersion()) { + {{ 'iot-hub.installed' | translate }} + } @else { + + } + } @else { + + } diff --git a/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-item-card.component.scss b/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-item-card.component.scss index afe2357bb3..2fc96ecd8d 100644 --- a/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-item-card.component.scss +++ b/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-item-card.component.scss @@ -349,4 +349,32 @@ } } + .tb-iot-hub-card-installed-label { + padding: 5px 14px; + border-radius: 6px; + background: #e8f5e9; + color: #2e7d32; + font-size: 12px; + font-weight: 600; + white-space: nowrap; + } + + .tb-iot-hub-card-update-btn { + padding: 5px 14px; + border-radius: 6px; + border: 1px solid #f57c00; + background: transparent; + color: #f57c00; + font-size: 12px; + font-weight: 600; + cursor: pointer; + white-space: nowrap; + transition: all 0.15s; + + &:hover { + background: #f57c00; + color: #fff; + } + } + } diff --git a/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-item-card.component.ts b/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-item-card.component.ts index 995aefa065..c43ba55e03 100644 --- a/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-item-card.component.ts +++ b/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-item-card.component.ts @@ -17,6 +17,7 @@ import { Component, EventEmitter, Input, Output } from '@angular/core'; import { MpItemVersionView, cfTypeTranslations, cfTypeIcons, ruleChainTypeTranslations, widgetTypeTranslations, NodeInfo, nodeComponentTypeTranslations } from '@shared/models/iot-hub/iot-hub-version.models'; import { ItemType, itemTypeTranslations, getCategoriesForType, useCaseTranslations } from '@shared/models/iot-hub/iot-hub-item.models'; +import { IotHubInstalledItem } from '@shared/models/iot-hub/iot-hub-installed-item.models'; import { TranslateService } from '@ngx-translate/core'; import { IotHubApiService } from '@core/http/iot-hub-api.service'; @@ -33,11 +34,13 @@ export class TbIotHubItemCardComponent { readonly maxVisibleNodes = 2; @Input() item: MpItemVersionView; + @Input() installedItem: IotHubInstalledItem; @Input() showCreator = true; @Input() showTypeChip = true; @Output() cardClick = new EventEmitter(); @Output() creatorClick = new EventEmitter(); @Output() installClick = new EventEmitter(); + @Output() updateClick = new EventEmitter(); typeTranslations = itemTypeTranslations; @@ -184,6 +187,14 @@ export class TbIotHubItemCardComponent { } } + isInstalled(): boolean { + return !!this.installedItem; + } + + isSameVersion(): boolean { + return this.installedItem?.itemVersionId === this.item.id; + } + onCardClick(): void { this.cardClick.emit(this.item); } @@ -193,6 +204,11 @@ export class TbIotHubItemCardComponent { this.installClick.emit(this.item); } + onUpdateClick(event: MouseEvent): void { + event.stopPropagation(); + this.updateClick.emit(this.item); + } + onCreatorClick(event: MouseEvent): void { event.stopPropagation(); this.creatorClick.emit(this.item.creatorId); diff --git a/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-item-detail-dialog.component.html b/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-item-detail-dialog.component.html index dc12d92b31..fc6980ccad 100644 --- a/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-item-detail-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-item-detail-dialog.component.html @@ -69,8 +69,8 @@ } - - @if (item.type === ItemType.DASHBOARD || item.type === ItemType.SOLUTION_TEMPLATE) { + + @if (item.type === ItemType.DASHBOARD) {
@if (getPreviewUrl(); as url) { @@ -87,6 +87,60 @@
} + + @if (item.type === ItemType.SOLUTION_TEMPLATE) { +
+ @if (carouselImages.length > 1) { + + } @else { +
+ @if (getPreviewUrl(); as url) { + + } @else { +
+ {{ getTypeIcon() }} +
+ } +
+ } + @if (item.description) { +
{{ item.description }}
+ } +
+ } +
diff --git a/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-item-detail-dialog.component.scss b/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-item-detail-dialog.component.scss index 5c4404b1c8..378a6d9b44 100644 --- a/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-item-detail-dialog.component.scss +++ b/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-item-detail-dialog.component.scss @@ -215,6 +215,90 @@ line-height: 1.6; } +// Image carousel +.dlg-hero-carousel { + background: #f5f7fa; + border-radius: 8px; + overflow: hidden; + position: relative; + height: 340px; + animation: dlg-carousel-fade-in 0s 0.3s forwards; + visibility: hidden; +} + +@keyframes dlg-carousel-fade-in { + to { + visibility: visible; + } +} + +.dlg-carousel { + height: 340px; + + ::ng-deep .carousel { + display: flex !important; + flex-direction: column !important; + + > section { + flex: 1; + min-height: 0; + } + + ul.indicators { + position: relative !important; + bottom: auto !important; + flex-shrink: 0; + padding: 8px 0; + } + + .direction { + height: calc(100% - 40px) !important; + } + } + + .dlg-carousel-container { + display: flex; + height: 100%; + + .dlg-carousel-item { + position: relative; + overflow: hidden; + + img { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + object-fit: contain; + padding: 8px; + } + } + } +} + +.dlg-carousel-nav { + color: rgba(0, 0, 0, 0.6); + background: rgba(255, 255, 255, 0.8); + + &:hover { + background: rgba(255, 255, 255, 0.95); + } +} + +.dlg-carousel-dot { + width: 8px; + height: 8px; + border-radius: 50%; + background: rgba(0, 0, 0, 0.2); + margin: 0 3px; + transition: background 0.2s; + + &.active { + background: rgba(0, 0, 0, 0.6); + } +} + // Stats bar .dlg-stats-bar { display: flex; diff --git a/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-item-detail-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-item-detail-dialog.component.ts index d2fe0f4833..64f6b33f0a 100644 --- a/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-item-detail-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-item-detail-dialog.component.ts @@ -48,6 +48,8 @@ export class TbIotHubItemDetailDialogComponent { typeTranslations = itemTypeTranslations; readmeContent: string = ''; installedItem?: IotHubInstalledItem; + carouselImages: string[] = []; + carouselIndex = 0; private categoryMap: Map; private useCaseMap = useCaseTranslations; @@ -62,6 +64,7 @@ export class TbIotHubItemDetailDialogComponent { this.item = data.item; this.installedItem = data.installedItem; this.categoryMap = getCategoriesForType(this.item.type); + this.buildCarouselImages(); this.loadReadme(); } @@ -321,6 +324,19 @@ export class TbIotHubItemDetailDialogComponent { this.dialogRef.close(); } + private buildCarouselImages(): void { + if (this.item.type !== ItemType.SOLUTION_TEMPLATE || !this.item.resources?.length) { + return; + } + const screenshotResources = this.item.resources.filter(r => r.type === 'SCREENSHOT'); + const allResources = screenshotResources.length > 0 + ? screenshotResources + : this.item.resources.filter(r => r.type === 'ICON'); + this.carouselImages = allResources.map(r => + this.data.iotHubApiService.resolveResourceUrl(`/api/resources/${r.id}`) + ); + } + private loadReadme(): void { const versionId = this.item.id as string; this.data.iotHubApiService.getVersionReadme(versionId, { ignoreLoading: true }).subscribe( diff --git a/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-update-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-update-dialog.component.ts index 7c646de3c4..c495c7be46 100644 --- a/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-update-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-update-dialog.component.ts @@ -15,15 +15,19 @@ /// import { Component, Inject } from '@angular/core'; -import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog'; import { Router } from '@angular/router'; import { ItemType, itemTypeTranslations } from '@shared/models/iot-hub/iot-hub-item.models'; -import { IotHubInstalledItemDescriptor } from '@shared/models/iot-hub/iot-hub-installed-item.models'; +import { + IotHubInstalledItemDescriptor, + SolutionTemplateInstalledItemDescriptor +} from '@shared/models/iot-hub/iot-hub-installed-item.models'; import { IotHubApiService } from '@core/http/iot-hub-api.service'; import { DialogService } from '@core/services/dialog.service'; import { TranslateService } from '@ngx-translate/core'; import { EntityType } from '@shared/models/entity-type.models'; import { getEntityDetailsPageURL } from '@core/utils'; +import { SolutionInstallDialogComponent } from '@home/components/solution/solution-install-dialog.component'; export interface IotHubUpdateDialogData { installedItemId: string; @@ -151,6 +155,7 @@ export class TbIotHubUpdateDialogComponent { constructor( @Inject(MAT_DIALOG_DATA) public data: IotHubUpdateDialogData, private dialogRef: MatDialogRef, + private dialog: MatDialog, private dialogService: DialogService, private router: Router, private translate: TranslateService @@ -166,8 +171,17 @@ export class TbIotHubUpdateDialogComponent { this.data.iotHubApiService.updateItemVersion(this.data.installedItemId, this.data.versionId, { ignoreLoading: true }, force).subscribe({ next: (result) => { if (result.success) { - this.state = 'success'; - this.entityDetailsUrl = this.resolveEntityDetailsUrl(result.descriptor); + if (result.descriptor?.type === 'SOLUTION_TEMPLATE') { + this.dialogRef.close('updated'); + this.dialog.open(SolutionInstallDialogComponent, { + disableClose: true, + panelClass: 'tb-solution-install-dialog-panel', + data: { descriptor: result.descriptor as SolutionTemplateInstalledItemDescriptor } + }); + } else { + this.state = 'success'; + this.entityDetailsUrl = this.resolveEntityDetailsUrl(result.descriptor); + } } else if (result.entityModified) { this.state = 'confirm'; this.dialogService.confirm( diff --git a/ui-ngx/src/app/shared/models/iot-hub/iot-hub-version.models.ts b/ui-ngx/src/app/shared/models/iot-hub/iot-hub-version.models.ts index 7ff055fc61..1d5637e2d0 100644 --- a/ui-ngx/src/app/shared/models/iot-hub/iot-hub-version.models.ts +++ b/ui-ngx/src/app/shared/models/iot-hub/iot-hub-version.models.ts @@ -77,6 +77,11 @@ export const ruleChainTypeTranslations = new Map([ ['EDGE', 'item-data.rule-chain-type-edge'], ]); +export interface MpItemVersionResource { + id: string; + type: string; +} + export interface MpItemVersionView { id: string; createdTime: number; @@ -103,6 +108,7 @@ export interface MpItemVersionView { creatorAvatarUrl: string; installCount: number; totalInstallCount: number; + resources: MpItemVersionResource[]; } export class MpItemVersionQuery {