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;
}