Browse Source

Update card installed state, remove labels, detail dialog info, and API changes

- Item card: installed chip (non-clickable pill, green) + separate remove icon
  button (mdi:trash-can-outline) for both same-version and update-available states
- Detail dialog: info section matches design exactly (badge, links, actions layout),
  solution template links on separate row, remove button with warn outline matching
  label text color, matButtonIcon + tb-mat-24 on remove icon
- Rename all Delete labels/keys to Remove across locale and components
- Solution template remove dialog: specific description about multiple entities
- getInstalledItems API: change itemType to List<String> itemTypes through full stack
  (controller, service, DAO, repository JPQL uses IN clause, UI accepts string|string[])
pull/15508/head
Igor Kulikov 6 months ago
parent
commit
c770bf290f
  1. 4
      application/src/main/java/org/thingsboard/server/controller/IotHubController.java
  2. 2
      dao/src/main/java/org/thingsboard/server/dao/iot_hub/IotHubInstalledItemDao.java
  3. 2
      dao/src/main/java/org/thingsboard/server/dao/iot_hub/IotHubInstalledItemService.java
  4. 4
      dao/src/main/java/org/thingsboard/server/dao/iot_hub/IotHubInstalledItemServiceImpl.java
  5. 4
      dao/src/main/java/org/thingsboard/server/dao/sql/iot_hub/IotHubInstalledItemRepository.java
  6. 4
      dao/src/main/java/org/thingsboard/server/dao/sql/iot_hub/JpaIotHubInstalledItemDao.java
  7. 7
      ui-ngx/src/app/core/http/iot-hub-api.service.ts
  8. 2
      ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-browse.component.ts
  9. 7
      ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-delete-dialog.component.ts
  10. 2
      ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-home.component.ts
  11. 8
      ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-installed-items.component.ts
  12. 16
      ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-item-card.component.html
  13. 44
      ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-item-card.component.scss
  14. 44
      ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-item-detail-dialog.component.html
  15. 25
      ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-item-detail-dialog.component.scss
  16. 2
      ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-item-detail-dialog.component.ts
  17. 15
      ui-ngx/src/assets/locale/locale.constant-en_US.json

4
application/src/main/java/org/thingsboard/server/controller/IotHubController.java

@ -94,9 +94,9 @@ public class IotHubController extends BaseController {
@RequestParam(required = false) String textSearch, @RequestParam(required = false) String textSearch,
@RequestParam(required = false) String sortProperty, @RequestParam(required = false) String sortProperty,
@RequestParam(required = false) String sortOrder, @RequestParam(required = false) String sortOrder,
@RequestParam(required = false) String itemType) throws ThingsboardException { @RequestParam(required = false) List<String> itemTypes) throws ThingsboardException {
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
return iotHubInstalledItemService.findByTenantId(getTenantId(), itemType, pageLink); return iotHubInstalledItemService.findByTenantId(getTenantId(), itemTypes, pageLink);
} }
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")

2
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<IotHubInstalledItem> { public interface IotHubInstalledItemDao extends Dao<IotHubInstalledItem> {
PageData<IotHubInstalledItem> findByTenantId(TenantId tenantId, String itemType, PageLink pageLink); PageData<IotHubInstalledItem> findByTenantId(TenantId tenantId, List<String> itemTypes, PageLink pageLink);
List<UUID> findInstalledItemIdsByTenantId(TenantId tenantId); List<UUID> findInstalledItemIdsByTenantId(TenantId tenantId);

2
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); IotHubInstalledItem findById(TenantId tenantId, IotHubInstalledItemId id);
PageData<IotHubInstalledItem> findByTenantId(TenantId tenantId, String itemType, PageLink pageLink); PageData<IotHubInstalledItem> findByTenantId(TenantId tenantId, List<String> itemTypes, PageLink pageLink);
List<UUID> findInstalledItemIdsByTenantId(TenantId tenantId); List<UUID> findInstalledItemIdsByTenantId(TenantId tenantId);

4
dao/src/main/java/org/thingsboard/server/dao/iot_hub/IotHubInstalledItemServiceImpl.java

@ -46,8 +46,8 @@ class IotHubInstalledItemServiceImpl implements IotHubInstalledItemService {
} }
@Override @Override
public PageData<IotHubInstalledItem> findByTenantId(TenantId tenantId, String itemType, PageLink pageLink) { public PageData<IotHubInstalledItem> findByTenantId(TenantId tenantId, List<String> itemTypes, PageLink pageLink) {
return iotHubInstalledItemDao.findByTenantId(tenantId, itemType, pageLink); return iotHubInstalledItemDao.findByTenantId(tenantId, itemTypes, pageLink);
} }
@Override @Override

4
dao/src/main/java/org/thingsboard/server/dao/sql/iot_hub/IotHubInstalledItemRepository.java

@ -35,13 +35,13 @@ interface IotHubInstalledItemRepository extends JpaRepository<IotHubInstalledIte
@Query(""" @Query("""
SELECT item FROM IotHubInstalledItemEntity item SELECT item FROM IotHubInstalledItemEntity item
WHERE item.tenantId = :tenantId WHERE item.tenantId = :tenantId
AND (:itemType IS NULL OR item.itemType = :itemType) AND (:itemTypes IS NULL OR item.itemType IN :itemTypes)
AND (:textSearch IS NULL AND (:textSearch IS NULL
OR ilike(item.itemName, CONCAT('%', :textSearch, '%')) = true OR ilike(item.itemName, CONCAT('%', :textSearch, '%')) = true
OR ilike(item.itemType, CONCAT('%', :textSearch, '%')) = true OR ilike(item.itemType, CONCAT('%', :textSearch, '%')) = true
OR ilike(item.version, CONCAT('%', :textSearch, '%')) = true) OR ilike(item.version, CONCAT('%', :textSearch, '%')) = true)
""") """)
Page<IotHubInstalledItemEntity> findByTenantId(@Param("tenantId") UUID tenantId, @Param("itemType") String itemType, @Param("textSearch") String textSearch, Pageable pageable); Page<IotHubInstalledItemEntity> findByTenantId(@Param("tenantId") UUID tenantId, @Param("itemTypes") List<String> itemTypes, @Param("textSearch") String textSearch, Pageable pageable);
@Query(""" @Query("""
SELECT COUNT(item) FROM IotHubInstalledItemEntity item SELECT COUNT(item) FROM IotHubInstalledItemEntity item

4
dao/src/main/java/org/thingsboard/server/dao/sql/iot_hub/JpaIotHubInstalledItemDao.java

@ -43,10 +43,10 @@ class JpaIotHubInstalledItemDao extends JpaAbstractDao<IotHubInstalledItemEntity
private final IotHubInstalledItemRepository repository; private final IotHubInstalledItemRepository repository;
@Override @Override
public PageData<IotHubInstalledItem> findByTenantId(TenantId tenantId, String itemType, PageLink pageLink) { public PageData<IotHubInstalledItem> findByTenantId(TenantId tenantId, List<String> itemTypes, PageLink pageLink) {
return DaoUtil.toPageData(repository.findByTenantId( return DaoUtil.toPageData(repository.findByTenantId(
tenantId.getId(), tenantId.getId(),
itemType, itemTypes == null || itemTypes.isEmpty() ? null : itemTypes,
StringUtils.defaultIfEmpty(pageLink.getTextSearch(), null), StringUtils.defaultIfEmpty(pageLink.getTextSearch(), null),
toPageRequest(pageLink)) toPageRequest(pageLink))
); );

7
ui-ngx/src/app/core/http/iot-hub-api.service.ts

@ -151,10 +151,11 @@ export class IotHubApiService {
); );
} }
public getInstalledItems(pageLink: PageLink, itemType?: string, config?: IotHubRequestConfig): Observable<PageData<IotHubInstalledItem>> { public getInstalledItems(pageLink: PageLink, itemTypes?: string | string[], config?: IotHubRequestConfig): Observable<PageData<IotHubInstalledItem>> {
let query = pageLink.toQuery(); let query = pageLink.toQuery();
if (itemType) { if (itemTypes) {
query += `&itemType=${itemType}`; const types = Array.isArray(itemTypes) ? itemTypes : [itemTypes];
types.forEach(t => query += `&itemTypes=${t}`);
} }
return this.http.get<PageData<IotHubInstalledItem>>( return this.http.get<PageData<IotHubInstalledItem>>(
`/api/iot-hub/installedItems${query}`, `/api/iot-hub/installedItems${query}`,

2
ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-browse.component.ts

@ -500,7 +500,7 @@ export class TbIotHubBrowseComponent implements OnInit, OnDestroy {
const dialogRef = this.dialog.open(TbIotHubDeleteDialogComponent, { const dialogRef = this.dialog.open(TbIotHubDeleteDialogComponent, {
panelClass: ['tb-dialog'], panelClass: ['tb-dialog'],
autoFocus: false, autoFocus: false,
data: { itemName: item.name } as IotHubDeleteDialogData data: { itemName: item.name, itemType: item.type } as IotHubDeleteDialogData
}); });
dialogRef.afterClosed().subscribe(confirmed => { dialogRef.afterClosed().subscribe(confirmed => {
if (confirmed) { if (confirmed) {

7
ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-delete-dialog.component.ts

@ -19,6 +19,7 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
export interface IotHubDeleteDialogData { export interface IotHubDeleteDialogData {
itemName: string; itemName: string;
itemType?: string;
} }
@Component({ @Component({
@ -26,12 +27,12 @@ export interface IotHubDeleteDialogData {
standalone: false, standalone: false,
template: ` template: `
<div class="tb-iot-hub-delete-content"> <div class="tb-iot-hub-delete-content">
<h2 class="tb-iot-hub-delete-title">{{ 'iot-hub.delete-confirm-title' | translate:{ name: data.itemName } }}</h2> <h2 class="tb-iot-hub-delete-title">{{ 'iot-hub.remove-confirm-title' | translate:{ name: data.itemName } }}</h2>
<p class="tb-iot-hub-delete-desc">{{ 'iot-hub.delete-desc' | translate }}</p> <p class="tb-iot-hub-delete-desc">{{ (data.itemType === 'SOLUTION_TEMPLATE' ? 'iot-hub.remove-desc-solution-template' : 'iot-hub.remove-desc') | translate }}</p>
</div> </div>
<div class="tb-iot-hub-delete-actions"> <div class="tb-iot-hub-delete-actions">
<button mat-button (click)="cancel()">{{ 'action.cancel' | translate }}</button> <button mat-button (click)="cancel()">{{ 'action.cancel' | translate }}</button>
<button mat-flat-button color="warn" (click)="confirm()">{{ 'iot-hub.delete-anyway' | translate }}</button> <button mat-flat-button color="warn" (click)="confirm()">{{ 'iot-hub.remove-anyway' | translate }}</button>
</div> </div>
`, `,
styles: [` styles: [`

2
ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-home.component.ts

@ -315,7 +315,7 @@ export class TbIotHubHomeComponent implements OnInit, OnDestroy {
const dialogRef = this.dialog.open(TbIotHubDeleteDialogComponent, { const dialogRef = this.dialog.open(TbIotHubDeleteDialogComponent, {
panelClass: ['tb-dialog'], panelClass: ['tb-dialog'],
autoFocus: false, autoFocus: false,
data: { itemName: item.name } as IotHubDeleteDialogData data: { itemName: item.name, itemType: item.type } as IotHubDeleteDialogData
}); });
dialogRef.afterClosed().subscribe(confirmed => { dialogRef.afterClosed().subscribe(confirmed => {
if (confirmed) { if (confirmed) {

8
ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-installed-items.component.ts

@ -106,8 +106,8 @@ export class TbIotHubInstalledItemsComponent implements OnInit, AfterViewInit {
deleteItem(item: IotHubInstalledItem): void { deleteItem(item: IotHubInstalledItem): void {
this.dialogService.confirm( this.dialogService.confirm(
this.translate.instant('iot-hub.delete-installed-item-title'), this.translate.instant('iot-hub.remove-installed-item-title'),
this.translate.instant('iot-hub.delete-installed-item-text', {name: item.itemName}), this.translate.instant('iot-hub.remove-installed-item-text', {name: item.itemName}),
this.translate.instant('action.no'), this.translate.instant('action.no'),
this.translate.instant('action.yes') this.translate.instant('action.yes')
).subscribe(result => { ).subscribe(result => {
@ -115,7 +115,7 @@ export class TbIotHubInstalledItemsComponent implements OnInit, AfterViewInit {
this.iotHubApiService.deleteInstalledItem(item.id.id).subscribe({ this.iotHubApiService.deleteInstalledItem(item.id.id).subscribe({
next: () => { next: () => {
this.store.dispatch(new ActionNotificationShow({ this.store.dispatch(new ActionNotificationShow({
message: this.translate.instant('iot-hub.installed-item-deleted', {name: item.itemName}), message: this.translate.instant('iot-hub.installed-item-removed', {name: item.itemName}),
type: 'success', type: 'success',
duration: 3000 duration: 3000
})); }));
@ -123,7 +123,7 @@ export class TbIotHubInstalledItemsComponent implements OnInit, AfterViewInit {
}, },
error: () => { error: () => {
this.store.dispatch(new ActionNotificationShow({ this.store.dispatch(new ActionNotificationShow({
message: this.translate.instant('iot-hub.installed-item-delete-error', {name: item.itemName}), message: this.translate.instant('iot-hub.installed-item-remove-error', {name: item.itemName}),
type: 'error', type: 'error',
duration: 5000 duration: 5000
})); }));

16
ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-item-card.component.html

@ -36,9 +36,15 @@
</div> </div>
@if (isInstalled()) { @if (isInstalled()) {
@if (isSameVersion()) { @if (isSameVersion()) {
<button class="tb-iot-hub-card-installed-label" (click)="onDeleteClick($event)"><tb-icon>check</tb-icon>{{ 'iot-hub.installed' | translate }}</button> <span class="tb-iot-hub-card-installed-chip"><tb-icon>check</tb-icon>{{ 'iot-hub.installed' | translate }}</span>
<button class="tb-iot-hub-card-remove-btn" (click)="onDeleteClick($event)">
<tb-icon>mdi:trash-can-outline</tb-icon>
</button>
} @else { } @else {
<button class="tb-iot-hub-card-update-btn" (click)="onUpdateClick($event)">{{ 'iot-hub.update' | translate }}</button> <button class="tb-iot-hub-card-update-btn" (click)="onUpdateClick($event)">{{ 'iot-hub.update' | translate }}</button>
<button class="tb-iot-hub-card-remove-btn" (click)="onDeleteClick($event)">
<tb-icon>mdi:trash-can-outline</tb-icon>
</button>
} }
} @else { } @else {
<button class="tb-iot-hub-card-install-btn" (click)="onInstallClick($event)">{{ 'iot-hub.install' | translate }}</button> <button class="tb-iot-hub-card-install-btn" (click)="onInstallClick($event)">{{ 'iot-hub.install' | translate }}</button>
@ -72,9 +78,15 @@
<span class="tb-iot-hub-card-stats"> <span class="tb-iot-hub-card-stats">
@if (isInstalled()) { @if (isInstalled()) {
@if (isSameVersion()) { @if (isSameVersion()) {
<button class="tb-iot-hub-card-installed-label" (click)="onDeleteClick($event)"><tb-icon>check</tb-icon>{{ 'iot-hub.installed' | translate }}</button> <span class="tb-iot-hub-card-installed-chip"><tb-icon>check</tb-icon>{{ 'iot-hub.installed' | translate }}</span>
<button class="tb-iot-hub-card-remove-btn" (click)="onDeleteClick($event)">
<tb-icon>mdi:trash-can-outline</tb-icon>
</button>
} @else { } @else {
<button class="tb-iot-hub-card-update-btn" (click)="onUpdateClick($event)">{{ 'iot-hub.update' | translate }}</button> <button class="tb-iot-hub-card-update-btn" (click)="onUpdateClick($event)">{{ 'iot-hub.update' | translate }}</button>
<button class="tb-iot-hub-card-remove-btn" (click)="onDeleteClick($event)">
<tb-icon>mdi:trash-can-outline</tb-icon>
</button>
} }
} @else { } @else {
<button class="tb-iot-hub-card-install-btn" (click)="onInstallClick($event)">{{ 'iot-hub.install' | translate }}</button> <button class="tb-iot-hub-card-install-btn" (click)="onInstallClick($event)">{{ 'iot-hub.install' | translate }}</button>

44
ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-item-card.component.scss

@ -320,35 +320,53 @@
} }
} }
// Installed label — Design: check icon + text, no bg, $tb-primary-color, h-32, pl-8 pr-12 py-6, rounded-4, gap-4 // Installed chip — Design: pill, bg rgba(25,128,56,0.06), check 18px + "Installed" 14px Regular, #198038
.tb-iot-hub-card-installed-label { .tb-iot-hub-card-installed-chip {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
gap: 4px; gap: 4px;
height: 32px; height: 32px;
padding: 6px 12px 6px 8px; padding: 6px 12px 6px 8px;
border-radius: 4px; border-radius: 16px;
border: none; background: rgba(25, 128, 56, 0.06);
background: none; color: #198038;
color: $tb-primary-color;
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 400;
line-height: 20px; line-height: 20px;
letter-spacing: 0.25px;
white-space: nowrap; white-space: nowrap;
flex-shrink: 0; flex-shrink: 0;
cursor: pointer;
font-family: inherit;
transition: background 0.15s;
&:hover { tb-icon {
background: rgba($tb-primary-color, 0.04); font-size: 18px;
width: 18px;
height: 18px;
} }
}
// Remove icon button — Design: trash-can-outline 24px, rgba(0,0,0,0.54)
.tb-iot-hub-card-remove-btn {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
padding: 4px;
border: none;
background: none;
cursor: pointer;
flex-shrink: 0;
border-radius: 50%;
transition: background 0.15s;
tb-icon { tb-icon {
font-size: 24px; font-size: 24px;
width: 24px; width: 24px;
height: 24px; height: 24px;
color: rgba(0, 0, 0, 0.54);
}
&:hover {
background: rgba(0, 0, 0, 0.04);
} }
} }

44
ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-item-detail-dialog.component.html

@ -54,7 +54,7 @@
<div class="dlg-main"> <div class="dlg-main">
@if (item.type === ItemType.CALCULATED_FIELD || item.type === ItemType.RULE_CHAIN || item.type === ItemType.DEVICE) { @if (item.type === ItemType.CALCULATED_FIELD || item.type === ItemType.RULE_CHAIN || item.type === ItemType.DEVICE) {
<!-- CF/RC/Device: description + author (no image) --> <!-- CF/RC/Device: description + author (no image) -->
<div class="dlg-info"> <div class="dlg-info dlg-info-compact">
<div class="dlg-info-text"> <div class="dlg-info-text">
@if (isInstalled()) { @if (isInstalled()) {
<div class="dlg-info-status-row"> <div class="dlg-info-status-row">
@ -146,33 +146,39 @@
</div> </div>
<div class="dlg-info"> <div class="dlg-info">
<div class="dlg-info-text"> <div class="dlg-info-text">
<!-- Installed status + links row --> <!-- Installed status + links -->
@if (isInstalled()) { @if (isInstalled()) {
<div class="dlg-info-status-row"> @if (item.type === ItemType.SOLUTION_TEMPLATE) {
<!-- Solution template: badge on own row, links on separate row -->
<span class="dlg-installed-badge"> <span class="dlg-installed-badge">
<mat-icon>check</mat-icon> <mat-icon>check</mat-icon>
{{ 'iot-hub.installed' | translate }} v{{ installedItem.version }} {{ 'iot-hub.installed' | translate }} v{{ installedItem.version }}
</span> </span>
@if (item.type === ItemType.SOLUTION_TEMPLATE) { <div class="dlg-info-status-row">
<div class="dlg-info-links-row"> @if (isSameVersion()) {
@if (isSameVersion()) { <a class="dlg-info-link" (click)="openSolutionInstructions()">
<a class="dlg-info-link" (click)="openSolutionInstructions()"> <mat-icon>info_outline</mat-icon>
<mat-icon>info_outline</mat-icon> {{ 'iot-hub.solution-instructions' | translate }}
{{ 'iot-hub.solution-instructions' | translate }}
</a>
}
<a class="dlg-info-link" (click)="openEntityDetails()">
<mat-icon>open_in_new</mat-icon>
{{ 'iot-hub.goto-main-dashboard' | translate }}
</a> </a>
</div> }
} @else { <a class="dlg-info-link" (click)="openEntityDetails()">
<mat-icon>open_in_new</mat-icon>
{{ 'iot-hub.goto-main-dashboard' | translate }}
</a>
</div>
} @else {
<!-- Other types: badge + link on same row -->
<div class="dlg-info-status-row">
<span class="dlg-installed-badge">
<mat-icon>check</mat-icon>
{{ 'iot-hub.installed' | translate }} v{{ installedItem.version }}
</span>
<a class="dlg-info-link" (click)="openEntityDetails()"> <a class="dlg-info-link" (click)="openEntityDetails()">
<mat-icon>open_in_new</mat-icon> <mat-icon>open_in_new</mat-icon>
{{ 'iot-hub.open-item-type-details' | translate:{ type: getTypeLabel() } }} {{ 'iot-hub.open-item-type-details' | translate:{ type: getTypeLabel() } }}
</a> </a>
} </div>
</div> }
} }
@if (item.description) { @if (item.description) {
<div class="dlg-description"> <div class="dlg-description">
@ -197,7 +203,7 @@
} }
@if (isInstalled()) { @if (isInstalled()) {
<button mat-stroked-button color="warn" (click)="deleteItem()"> <button mat-stroked-button color="warn" (click)="deleteItem()">
<mat-icon>delete</mat-icon> <tb-icon matButtonIcon class="tb-mat-24">mdi:trash-can-outline</tb-icon>
{{ 'iot-hub.remove' | translate }} {{ 'iot-hub.remove' | translate }}
</button> </button>
} }

25
ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-item-detail-dialog.component.scss

@ -115,11 +115,10 @@
padding: 0 24px 16px; padding: 0 24px 16px;
} }
// Row 1 — Design: flex row, gap-24, items-center // Row 1 — Design: flex row, gap-24
.dlg-row-1 { .dlg-row-1 {
display: flex; display: flex;
gap: 24px; gap: 24px;
align-items: center;
} }
// Preview image — Design: flex-1, aspect 305:172, bg rgba(0,0,0,0.06), rounded-8, p-16, dot pattern // Preview image — Design: flex-1, aspect 305:172, bg rgba(0,0,0,0.06), rounded-8, p-16, dot pattern
@ -462,15 +461,20 @@
&.active { background: rgba(0, 0, 0, 0.6); } &.active { background: rgba(0, 0, 0, 0.6); }
} }
// Info panel — Design: flex-col, gap-32 between text-container and buttons, py-16 // Info panel — Design: flex-col, space-between, py-16, gap-32
.dlg-info { .dlg-info {
flex: 1; flex: 1;
min-width: 0; min-width: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: space-between;
gap: 32px; gap: 32px;
justify-content: center;
padding: 16px 0; padding: 16px 0;
&.dlg-info-compact {
justify-content: flex-start;
padding: 0;
}
} }
// Text container — Design: flex-col, gap-16 // Text container — Design: flex-col, gap-16
@ -492,6 +496,7 @@
.dlg-installed-badge { .dlg-installed-badge {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
align-self: flex-start;
gap: 4px; gap: 4px;
padding: 4px 8px; padding: 4px 8px;
border-radius: 16px; border-radius: 16px;
@ -521,6 +526,8 @@
color: $tb-primary-color; color: $tb-primary-color;
cursor: pointer; cursor: pointer;
text-decoration: none; text-decoration: none;
border: none;
border-bottom: none;
mat-icon { mat-icon {
font-size: 16px; font-size: 16px;
@ -530,6 +537,8 @@
&:hover { &:hover {
text-decoration: underline; text-decoration: underline;
border: none;
border-bottom: none;
} }
} }
@ -540,12 +549,16 @@
gap: 16px; gap: 16px;
} }
// Action buttons row — Design: flex, justify-between, full width // Action buttons row — Design: flex, full width, Remove always right-aligned
.dlg-info-actions { .dlg-info-actions {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between;
width: 100%; width: 100%;
[color="warn"] {
margin-left: auto;
--mat-button-outlined-outline-color: var(--mat-button-outlined-label-text-color);
}
} }
// Footer — only Close button // Footer — only Close button

2
ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-item-detail-dialog.component.ts

@ -357,7 +357,7 @@ export class TbIotHubItemDetailDialogComponent {
const deleteDialogRef = this.dialog.open(TbIotHubDeleteDialogComponent, { const deleteDialogRef = this.dialog.open(TbIotHubDeleteDialogComponent, {
panelClass: ['tb-dialog'], panelClass: ['tb-dialog'],
autoFocus: false, autoFocus: false,
data: { itemName: this.item.name } as IotHubDeleteDialogData data: { itemName: this.item.name, itemType: this.item.type } as IotHubDeleteDialogData
}); });
deleteDialogRef.afterClosed().subscribe(confirmed => { deleteDialogRef.afterClosed().subscribe(confirmed => {
if (confirmed) { if (confirmed) {

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

@ -3770,10 +3770,11 @@
"install-desc": "Installing this will automatically create and configure the necessary resources within your tenant", "install-desc": "Installing this will automatically create and configure the necessary resources within your tenant",
"install-success-desc": "'{{name}}' has been installed successfully", "install-success-desc": "'{{name}}' has been installed successfully",
"open-details": "Open details", "open-details": "Open details",
"delete-confirm-title": "Delete '{{name}}'?",
"delete-desc": "Deleting this will also remove the installed entity and its associated resources within your tenant",
"delete-anyway": "Delete anyway",
"remove": "Remove", "remove": "Remove",
"remove-confirm-title": "Remove '{{name}}'?",
"remove-desc": "Removing this will also remove the installed entity and its associated resources within your tenant",
"remove-desc-solution-template": "Removing this solution template will also remove all created entities including dashboards, rule chains, devices, and other associated resources within your tenant",
"remove-anyway": "Remove anyway",
"select-entity-for-cf": "Select entity for calculated field '{{name}}'", "select-entity-for-cf": "Select entity for calculated field '{{name}}'",
"install-type": "Type: {{type}}", "install-type": "Type: {{type}}",
"install-creator": "Creator: {{creator}}", "install-creator": "Creator: {{creator}}",
@ -3859,10 +3860,10 @@
"installed-date": "Installed", "installed-date": "Installed",
"actions": "Actions", "actions": "Actions",
"no-installed-items": "No installed items found", "no-installed-items": "No installed items found",
"delete-installed-item-title": "Delete Installed Item", "remove-installed-item-title": "Remove Installed Item",
"delete-installed-item-text": "Are you sure you want to delete '{{name}}'? The installed entity will also be removed.", "remove-installed-item-text": "Are you sure you want to remove '{{name}}'? The installed entity will also be removed.",
"installed-item-deleted": "'{{name}}' has been deleted", "installed-item-removed": "'{{name}}' has been removed",
"installed-item-delete-error": "Failed to delete '{{name}}'", "installed-item-remove-error": "Failed to remove '{{name}}'",
"open-installed-entity": "Open installed entity", "open-installed-entity": "Open installed entity",
"view-item-details": "View item details", "view-item-details": "View item details",
"open-item-type-details": "Open {{type}} details", "open-item-type-details": "Open {{type}} details",

Loading…
Cancel
Save