From fb437fd8a98bece517036532313c5a5a233c0b2c Mon Sep 17 00:00:00 2001 From: jmbardallo Date: Mon, 26 Oct 2026 13:46:08 +0100 Subject: [PATCH] feat(plugins): add generic partition-level status and badge support --- .../client-details.component.css | 64 ++++++++++++ .../client-details.component.html | 7 ++ .../client-details.component.ts | 6 ++ .../src/app/services/plugin.service.ts | 98 ++++++++++++++++++- 4 files changed, 174 insertions(+), 1 deletion(-) diff --git a/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.css b/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.css index 583b364..f51161f 100644 --- a/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.css +++ b/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.css @@ -274,3 +274,67 @@ table { font-style: italic; margin-top: 2px; } + +.partition-badges-row { + display: flex; + flex-wrap: wrap; + gap: 4px; + margin-top: 4px; +} + +.plugin-badge { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 2px 8px; + border-radius: 12px; + font-size: 0.72rem; + font-weight: 600; + letter-spacing: 0.2px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); + transition: all 0.2s ease; + width: fit-content; +} + +.plugin-badge .plugin-badge-icon { + font-size: 14px; + width: 14px; + height: 14px; + line-height: 14px; +} + +.plugin-badge.badge-neutral { + background: #f1f5f9; + color: #64748b; + border: 1px solid #cbd5e1; +} + +.plugin-badge.badge-pending { + background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%); + color: #b45309; + border: 1px solid #fcd34d; +} + +.plugin-badge.badge-error { + background: linear-gradient(135deg, #fee2e2 0%, #fecaca 100%); + color: #b91c1c; + border: 1px solid #fca5a5; +} + +.plugin-badge.badge-info { + background: linear-gradient(135deg, #eff6ff 0%, #dbeafe 100%); + color: #1d4ed8; + border: 1px solid #93c5fd; +} + +.plugin-badge.badge-success { + background: linear-gradient(135deg, #ecfdf5 0%, #d1fae5 100%); + color: #047857; + border: 1px solid #6ee7b7; +} + +.plugin-badge.badge-warning { + background: linear-gradient(135deg, #fffbeb 0%, #fef3c7 100%); + color: #b45309; + border: 1px solid #fde68a; +} diff --git a/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.html b/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.html index 1855f37..8a51a55 100644 --- a/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.html +++ b/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.html @@ -91,6 +91,13 @@ @if (image.deployDate) { Desplegada: {{ image.deployDate | date:'dd/MM/yyyy HH:mm' }} } +
+ @for (badge of getPartitionBadges(disk.diskNumber, image.partitionNumber); track badge.id + '-' + badge.type + '-' + badge.label) { + + {{ badge.icon }} {{ badge.label }} + + } +
} } diff --git a/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.ts b/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.ts index f59e8bd..8dd46e7 100644 --- a/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.ts +++ b/ogWebconsole/src/app/components/groups/shared/client-details/client-details.component.ts @@ -10,6 +10,7 @@ import { Router } from "@angular/router"; import { ToastrService } from "ngx-toastr"; import { ManageClientComponent } from "../../shared/clients/manage-client/manage-client.component"; import { ConfigService } from '@services/config.service'; +import { PluginBadge, PluginService } from '@services/plugin.service'; interface ClientInfo { property: string; @@ -23,6 +24,7 @@ interface ClientInfo { }) export class ClientDetailsComponent { public translate = inject(TranslateService); + private pluginService = inject(PluginService); baseUrl: string; @ViewChild('assistantContainer') assistantContainer!: ElementRef; clientUuid: string; @@ -209,6 +211,10 @@ export class ClientDetailsComponent { }); } + getPartitionBadges(diskNumber: number, partitionNumber: number): PluginBadge[] { + return this.pluginService.getPartitionBadges(this.clientData, diskNumber, partitionNumber); + } + onNoClick(): void { this.dialog.closeAll(); } diff --git a/ogWebconsole/src/app/services/plugin.service.ts b/ogWebconsole/src/app/services/plugin.service.ts index 1eb7309..7667744 100644 --- a/ogWebconsole/src/app/services/plugin.service.ts +++ b/ogWebconsole/src/app/services/plugin.service.ts @@ -108,14 +108,26 @@ export class PluginService { return `${pluginName}:${idOrUuid}`; } + private getPartitionStatusKey(pluginName: string, idOrUuid: string | number, disk: number, partition: number): string { + return `${pluginName}:${idOrUuid}:${disk}:${partition}`; + } + public setPluginStatus(pluginName: string, idOrUuid: string | number, status: string): void { this.clientPluginStatusMap.set(this.getStatusKey(pluginName, idOrUuid), status); } + public setPartitionPluginStatus(pluginName: string, idOrUuid: string | number, disk: number, partition: number, status: string): void { + this.clientPluginStatusMap.set(this.getPartitionStatusKey(pluginName, idOrUuid, disk, partition), status); + } + public getPluginStatus(pluginName: string, idOrUuid: string | number): string | undefined { return this.clientPluginStatusMap.get(this.getStatusKey(pluginName, idOrUuid)); } + public getPartitionPluginStatus(pluginName: string, idOrUuid: string | number, disk: number, partition: number): string | undefined { + return this.clientPluginStatusMap.get(this.getPartitionStatusKey(pluginName, idOrUuid, disk, partition)); + } + private initMercureSubscription(): void { const mercureUrl = this.configService.mercureUrl; if (!mercureUrl || this.mercureEventSource) { @@ -128,11 +140,20 @@ export class PluginService { try { const data = JSON.parse(event.data); if (data && data.plugin && data.status) { + const disk = data.disk ?? data.disk_number ?? data.diskNumber; + const part = data.partition ?? data.partition_number ?? data.partitionNumber; + if (data.clientId !== undefined && data.clientId !== null) { this.setPluginStatus(data.plugin, String(data.clientId), data.status); + if (disk !== undefined && disk !== null && part !== undefined && part !== null) { + this.setPartitionPluginStatus(data.plugin, String(data.clientId), Number(disk), Number(part), data.status); + } } if (data.clientUuid) { this.setPluginStatus(data.plugin, data.clientUuid, data.status); + if (disk !== undefined && disk !== null && part !== undefined && part !== null) { + this.setPartitionPluginStatus(data.plugin, data.clientUuid, Number(disk), Number(part), data.status); + } } } } catch { @@ -243,12 +264,21 @@ export class PluginService { const status = row.status; const id = row.client_id ?? row.clientId ?? row.id; const uuid = row.client_uuid ?? row.clientUuid ?? row.uuid; + const disk = row.disk_number ?? row.diskNumber ?? row.disk; + const part = row.partition_number ?? row.partitionNumber ?? row.partition; + if (status) { if (id !== undefined && id !== null) { this.setPluginStatus(plugin.name, String(id), status); + if (disk !== undefined && disk !== null && part !== undefined && part !== null) { + this.setPartitionPluginStatus(plugin.name, String(id), Number(disk), Number(part), status); + } } if (uuid) { this.setPluginStatus(plugin.name, uuid, status); + if (disk !== undefined && disk !== null && part !== undefined && part !== null) { + this.setPartitionPluginStatus(plugin.name, uuid, Number(disk), Number(part), status); + } } } } @@ -357,9 +387,75 @@ export class PluginService { continue; } - const currentStatus = (idKey ? this.getPluginStatus(plugin.name, idKey) : null) + // 1. Estado global del cliente + let currentStatus = (idKey ? this.getPluginStatus(plugin.name, idKey) : null) || (uuidKey ? this.getPluginStatus(plugin.name, uuidKey) : null); + // 2. Si no hay estado global explícito, buscar si alguna partición del equipo tiene un estado activo + if (!currentStatus) { + for (const [key, val] of this.clientPluginStatusMap.entries()) { + const isMatch = (idKey && key.startsWith(`${plugin.name}:${idKey}:`)) || + (uuidKey && key.startsWith(`${plugin.name}:${uuidKey}:`)); + if (isMatch && val && val !== 'NONE' && val !== 'UNFROZEN') { + currentStatus = val; + break; + } + } + } + + if (!currentStatus) { + continue; + } + + const matchingBadge = plugin.badges.find( + b => b.status.toUpperCase() === currentStatus.toUpperCase() + ); + + if (matchingBadge) { + let label = matchingBadge.label; + if (matchingBadge.labelKey) { + const trans = this.translateService.instant(matchingBadge.labelKey); + if (trans && trans !== matchingBadge.labelKey) { + label = trans; + } + } + + let tooltip = matchingBadge.tooltip; + if (matchingBadge.tooltipKey) { + const trans = this.translateService.instant(matchingBadge.tooltipKey); + if (trans && trans !== matchingBadge.tooltipKey) { + tooltip = trans; + } + } + + badges.push({ + id: plugin.name, + type: matchingBadge.type, + icon: matchingBadge.icon, + label, + tooltip + }); + } + } + + return badges; + } + + getPartitionBadges(client: any, diskNumber: number, partitionNumber: number): PluginBadge[] { + const badges: PluginBadge[] = []; + if (!client || this.activePlugins.length === 0) return badges; + + const idKey = client.id ? String(client.id) : null; + const uuidKey = client.uuid || client.name; + + for (const plugin of this.activePlugins) { + if (!plugin.badges || plugin.badges.length === 0) { + continue; + } + + const currentStatus = (idKey ? this.getPartitionPluginStatus(plugin.name, idKey, diskNumber, partitionNumber) : null) + || (uuidKey ? this.getPartitionPluginStatus(plugin.name, uuidKey, diskNumber, partitionNumber) : null); + if (!currentStatus) { continue; }