From dc4186ff4d844a2414abbdc6372dbf13265a971a Mon Sep 17 00:00:00 2001 From: jmbardallo Date: Sat, 31 Oct 2026 22:26:51 +0100 Subject: [PATCH] fix(plugins): gestion nativa de estado por disco y particion con contrato estandarizado (v1.24.7) --- CHANGELOG.md | 10 + ogWebconsole/package.json | 2 +- .../src/app/services/plugin.service.ts | 310 +++++++++--------- 3 files changed, 161 insertions(+), 161 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f939ad..e68753d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## [1.24.7] - 2026-10-31 + +### Refactored +- **Gestión Nativa por Disco y Partición con Contrato Estandarizado en `PluginService`**: + - Eliminada la abstracción innecesaria de `subtarget`, operando directamente con las coordenadas nativas de OpenGnSys (`disk_number` y `partition_number`). + - La interfaz de usuario opera de manera 100% pasiva y desacoplada: es el plugin emisor (backend / listener Mercure / endpoint `/status`) quien suministra explícitamente tanto el estado global del cliente (`client_status` / `clientStatus`) como el estado de cada partición (`status` junto a `disk_number` y `partition_number`). + - Métodos nativos y directos en `PluginService`: `setPartitionPluginStatus`, `getPartitionPluginStatus`, `clearPartitionPluginStatus` y `getPartitionBadges(client, diskNumber, partitionNumber)`. + - Eliminadas las iteraciones o inferencias en `getClientBadges`: el badge general del cliente se consulta directamente en memoria sin realizar bucles de búsqueda sobre particiones ni calcular prioridades arbitrarias. + - Soporte completo de metadatos contextuales en tooltips tanto a nivel general del equipo como a nivel de cada partición individual. + ## [1.24.6] - 2026-10-31 ### Fixed diff --git a/ogWebconsole/package.json b/ogWebconsole/package.json index 29178bc..0d9f381 100644 --- a/ogWebconsole/package.json +++ b/ogWebconsole/package.json @@ -1,6 +1,6 @@ { "name": "og-webconsole", - "version": "1.24.6", + "version": "1.24.7", "scripts": { "ng": "ng", "start": "ng serve", diff --git a/ogWebconsole/src/app/services/plugin.service.ts b/ogWebconsole/src/app/services/plugin.service.ts index a3d85f7..9ab8c1c 100644 --- a/ogWebconsole/src/app/services/plugin.service.ts +++ b/ogWebconsole/src/app/services/plugin.service.ts @@ -160,54 +160,62 @@ export class PluginService { 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 setPartitionPluginStatus(pluginName: string, idOrUuid: string | number, disk: number, partition: number, status: string): void { + this.clientPluginStatusMap.set(this.getPartitionStatusKey(pluginName, idOrUuid, disk, partition), status); + } + public getPartitionPluginStatus(pluginName: string, idOrUuid: string | number, disk: number, partition: number): string | undefined { return this.clientPluginStatusMap.get(this.getPartitionStatusKey(pluginName, idOrUuid, disk, partition)); } - public setPluginMetadata(pluginName: string, idOrUuid: string | number, metadata: any): void { - const key = this.getStatusKey(pluginName, idOrUuid); + public clearPartitionPluginStatus(pluginName: string, idOrUuid: string | number, disk: number, partition: number): void { + const key = this.getPartitionStatusKey(pluginName, idOrUuid, disk, partition); + this.clientPluginStatusMap.delete(key); + this.clientPluginMetadataMap.delete(key); + } + + public clearPluginStatus(pluginName: string, idOrUuid: string | number): void { + const globalKey = this.getStatusKey(pluginName, idOrUuid); + this.clientPluginStatusMap.delete(globalKey); + this.clientPluginMetadataMap.delete(globalKey); + + const prefix = `${pluginName}:${idOrUuid}:`; + for (const key of Array.from(this.clientPluginStatusMap.keys())) { + if (key.startsWith(prefix)) { + this.clientPluginStatusMap.delete(key); + } + } + for (const key of Array.from(this.clientPluginMetadataMap.keys())) { + if (key.startsWith(prefix)) { + this.clientPluginMetadataMap.delete(key); + } + } + } + + public setPluginMetadata(pluginName: string, idOrUuid: string | number, metadata: any, disk?: number, partition?: number): void { + const key = (disk !== undefined && partition !== undefined) + ? this.getPartitionStatusKey(pluginName, idOrUuid, disk, partition) + : this.getStatusKey(pluginName, idOrUuid); const existing = this.clientPluginMetadataMap.get(key) || {}; this.clientPluginMetadataMap.set(key, { ...existing, ...metadata }); } - public getPluginMetadata(pluginName: string, idOrUuid: string | number): any { - return this.clientPluginMetadataMap.get(this.getStatusKey(pluginName, idOrUuid)); + public getPluginMetadata(pluginName: string, idOrUuid: string | number, disk?: number, partition?: number): any { + const key = (disk !== undefined && partition !== undefined) + ? this.getPartitionStatusKey(pluginName, idOrUuid, disk, partition) + : this.getStatusKey(pluginName, idOrUuid); + return this.clientPluginMetadataMap.get(key); } - public clearPluginMetadata(pluginName: string, idOrUuid: string | number): void { - this.clientPluginMetadataMap.delete(this.getStatusKey(pluginName, idOrUuid)); - } - - private handlePartitionClear(pluginName: string, idOrUuid: string, disk: number, part: number): void { - const partKey = this.getPartitionStatusKey(pluginName, idOrUuid, disk, part); - this.clientPluginStatusMap.delete(partKey); - this.syncGlobalStatusFromPartitions(pluginName, idOrUuid); - } - - private syncGlobalStatusFromPartitions(pluginName: string, idOrUuid: string): void { - const prefix = `${pluginName}:${idOrUuid}:`; - let remainingStatus: string | null = null; - for (const [key, val] of this.clientPluginStatusMap.entries()) { - if (key.startsWith(prefix) && val && val !== 'NONE') { - remainingStatus = val; - break; - } - } - - const globalKey = this.getStatusKey(pluginName, idOrUuid); - if (remainingStatus) { - this.clientPluginStatusMap.set(globalKey, remainingStatus); - } else { - this.clientPluginStatusMap.delete(globalKey); - } + public clearPluginMetadata(pluginName: string, idOrUuid: string | number, disk?: number, partition?: number): void { + const key = (disk !== undefined && partition !== undefined) + ? this.getPartitionStatusKey(pluginName, idOrUuid, disk, partition) + : this.getStatusKey(pluginName, idOrUuid); + this.clientPluginMetadataMap.delete(key); } private initMercureSubscription(): void { @@ -222,91 +230,67 @@ export class PluginService { this.ngZone.run(() => { 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 && data.plugin) { + const entityId = data.clientId !== undefined && data.clientId !== null ? String(data.clientId) : null; + const entityUuid = data.clientUuid ? String(data.clientUuid) : null; + const rawDisk = data.disk ?? data.disk_number ?? data.diskNumber; + const rawPart = data.partition ?? data.partition_number ?? data.partitionNumber; + const hasPartition = rawDisk !== undefined && rawDisk !== null && rawPart !== undefined && rawPart !== null; + const disk = hasPartition ? Number(rawDisk) : undefined; + const partition = hasPartition ? Number(rawPart) : undefined; - const clearEntriesForPrefix = (prefix: string) => { - for (const key of Array.from(this.clientPluginStatusMap.keys())) { - if (key.startsWith(prefix)) { - this.clientPluginStatusMap.delete(key); - } + // 1. Estado a nivel de partición + if (hasPartition && disk !== undefined && partition !== undefined) { + const partStatus = data.status; + if (partStatus === 'NONE') { + if (entityId) this.clearPartitionPluginStatus(data.plugin, entityId, disk, partition); + if (entityUuid) this.clearPartitionPluginStatus(data.plugin, entityUuid, disk, partition); + } else if (partStatus) { + if (entityId) this.setPartitionPluginStatus(data.plugin, entityId, disk, partition, partStatus); + if (entityUuid) this.setPartitionPluginStatus(data.plugin, entityUuid, disk, partition, partStatus); } - for (const key of Array.from(this.clientPluginMetadataMap.keys())) { - if (key.startsWith(prefix)) { - this.clientPluginMetadataMap.delete(key); - } - } - }; - - if (data.status === 'NONE') { - if (data.clientId !== undefined && data.clientId !== null) { - const idStr = String(data.clientId); - if (disk !== undefined && disk !== null && part !== undefined && part !== null) { - this.handlePartitionClear(data.plugin, idStr, Number(disk), Number(part)); - } else if (disk !== undefined && disk !== null) { - clearEntriesForPrefix(`${data.plugin}:${idStr}:${disk}:`); - this.syncGlobalStatusFromPartitions(data.plugin, idStr); - } else { - this.clientPluginStatusMap.delete(this.getStatusKey(data.plugin, idStr)); - this.clientPluginMetadataMap.delete(this.getStatusKey(data.plugin, idStr)); - clearEntriesForPrefix(`${data.plugin}:${idStr}:`); - } - } - if (data.clientUuid) { - const uuidStr = data.clientUuid; - if (disk !== undefined && disk !== null && part !== undefined && part !== null) { - this.handlePartitionClear(data.plugin, uuidStr, Number(disk), Number(part)); - } else if (disk !== undefined && disk !== null) { - clearEntriesForPrefix(`${data.plugin}:${uuidStr}:${disk}:`); - this.syncGlobalStatusFromPartitions(data.plugin, uuidStr); - } else { - this.clientPluginStatusMap.delete(this.getStatusKey(data.plugin, uuidStr)); - this.clientPluginMetadataMap.delete(this.getStatusKey(data.plugin, uuidStr)); - clearEntriesForPrefix(`${data.plugin}:${uuidStr}:`); - } - } - this.statusUpdatesSubject.next({ plugin: data.plugin, status: 'NONE' }); - } else { - // Extraer metadatos contextuales (p. ej. dni, remainingMinutes, remaining_minutes) - const metadata: any = {}; - for (const [k, v] of Object.entries(data)) { - if (!['plugin', 'status', 'clientId', 'clientUuid', 'disk', 'disk_number', 'diskNumber', 'partition', 'partition_number', 'partitionNumber', 'action', 'timestamp'].includes(k) && v !== null && v !== undefined) { - metadata[k] = v; - } - } - const hasMeta = Object.keys(metadata).length > 0; - - if (data.clientId !== undefined && data.clientId !== null) { - const idStr = String(data.clientId); - this.setPluginStatus(data.plugin, idStr, data.status); - if (hasMeta) { - this.setPluginMetadata(data.plugin, idStr, metadata); - } else { - this.clearPluginMetadata(data.plugin, idStr); - } - if (disk !== undefined && disk !== null && part !== undefined && part !== null) { - this.setPartitionPluginStatus(data.plugin, idStr, Number(disk), Number(part), data.status); - } - } - if (data.clientUuid) { - this.setPluginStatus(data.plugin, data.clientUuid, data.status); - if (hasMeta) { - this.setPluginMetadata(data.plugin, data.clientUuid, metadata); - } else { - this.clearPluginMetadata(data.plugin, data.clientUuid); - } - if (disk !== undefined && disk !== null && part !== undefined && part !== null) { - this.setPartitionPluginStatus(data.plugin, data.clientUuid, Number(disk), Number(part), data.status); - } - } - this.statusUpdatesSubject.next({ - plugin: data.plugin, - clientId: data.clientId ? String(data.clientId) : data.clientUuid, - status: data.status, - metadata - }); } + + // 2. Estado general del cliente + const generalStatus = data.clientStatus !== undefined + ? data.clientStatus + : (data.client_status !== undefined ? data.client_status : (!hasPartition ? data.status : null)); + + if (generalStatus !== null) { + if (generalStatus === 'NONE') { + if (entityId) { + this.clientPluginStatusMap.delete(this.getStatusKey(data.plugin, entityId)); + this.clientPluginMetadataMap.delete(this.getStatusKey(data.plugin, entityId)); + } + if (entityUuid) { + this.clientPluginStatusMap.delete(this.getStatusKey(data.plugin, entityUuid)); + this.clientPluginMetadataMap.delete(this.getStatusKey(data.plugin, entityUuid)); + } + } else { + if (entityId) this.setPluginStatus(data.plugin, entityId, generalStatus); + if (entityUuid) this.setPluginStatus(data.plugin, entityUuid, generalStatus); + } + } + + // Metadatos contextuales arbitrarios + const metadata: any = {}; + for (const [k, v] of Object.entries(data)) { + if (!['plugin', 'status', 'clientStatus', 'client_status', 'clientId', 'clientUuid', 'disk', 'disk_number', 'diskNumber', 'partition', 'partition_number', 'partitionNumber', 'action', 'timestamp', 'subtarget'].includes(k) && v !== null && v !== undefined) { + metadata[k] = v; + } + } + const hasMeta = Object.keys(metadata).length > 0; + if (hasMeta) { + if (entityId) this.setPluginMetadata(data.plugin, entityId, metadata, disk, partition); + if (entityUuid) this.setPluginMetadata(data.plugin, entityUuid, metadata, disk, partition); + } + + this.statusUpdatesSubject.next({ + plugin: data.plugin, + clientId: (entityId || entityUuid) ?? undefined, + status: data.status, + metadata + }); } } catch { } @@ -418,41 +402,43 @@ 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; + const rawDisk = row.disk_number ?? row.diskNumber ?? row.disk; + const rawPart = row.partition_number ?? row.partitionNumber ?? row.partition; + const hasPartition = rawDisk !== undefined && rawDisk !== null && rawPart !== undefined && rawPart !== null; + const disk = hasPartition ? Number(rawDisk) : undefined; + const partition = hasPartition ? Number(rawPart) : undefined; + const clientStatus = row.client_status ?? row.clientStatus ?? (!hasPartition ? status : null); // Extraer metadatos arbitrarios (dni, remaining_minutes, etc.) const metadata: any = {}; for (const [k, v] of Object.entries(row)) { - if (!['status', 'client_id', 'clientId', 'id', 'client_uuid', 'clientUuid', 'uuid', 'disk_number', 'diskNumber', 'disk', 'partition_number', 'partitionNumber', 'partition'].includes(k) && v !== null && v !== undefined) { + if (!['status', 'client_status', 'clientStatus', 'client_id', 'clientId', 'id', 'client_uuid', 'clientUuid', 'uuid', 'disk_number', 'diskNumber', 'disk', 'partition_number', 'partitionNumber', 'partition', 'subtarget'].includes(k) && v !== null && v !== undefined) { metadata[k] = v; } } const hasMeta = Object.keys(metadata).length > 0; - if (status) { - if (id !== undefined && id !== null) { - const idStr = String(id); - this.setPluginStatus(plugin.name, idStr, status); - if (hasMeta) { - this.setPluginMetadata(plugin.name, idStr, metadata); - } else { - this.clearPluginMetadata(plugin.name, idStr); - } - if (disk !== undefined && disk !== null && part !== undefined && part !== null) { - this.setPartitionPluginStatus(plugin.name, idStr, Number(disk), Number(part), status); - } + const idStr = id !== undefined && id !== null ? String(id) : null; + if (idStr) { + if (hasPartition && disk !== undefined && partition !== undefined && status && status !== 'NONE') { + this.setPartitionPluginStatus(plugin.name, idStr, disk, partition, status); } - if (uuid) { - this.setPluginStatus(plugin.name, uuid, status); - if (hasMeta) { - this.setPluginMetadata(plugin.name, uuid, metadata); - } else { - this.clearPluginMetadata(plugin.name, uuid); - } - if (disk !== undefined && disk !== null && part !== undefined && part !== null) { - this.setPartitionPluginStatus(plugin.name, uuid, Number(disk), Number(part), status); - } + if (clientStatus && clientStatus !== 'NONE') { + this.setPluginStatus(plugin.name, idStr, clientStatus); + } + if (hasMeta) { + this.setPluginMetadata(plugin.name, idStr, metadata, disk, partition); + } + } + if (uuid) { + if (hasPartition && disk !== undefined && partition !== undefined && status && status !== 'NONE') { + this.setPartitionPluginStatus(plugin.name, uuid, disk, partition, status); + } + if (clientStatus && clientStatus !== 'NONE') { + this.setPluginStatus(plugin.name, uuid, clientStatus); + } + if (hasMeta) { + this.setPluginMetadata(plugin.name, uuid, metadata, disk, partition); } } } @@ -700,26 +686,11 @@ export class PluginService { continue; } - // 1. Estado global del cliente - let currentStatus = (idKey ? this.getPluginStatus(plugin.name, idKey) : null) + // Estado global del cliente definido directamente por el plugin + const 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 con badge declarado en el plugin - 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') { - const hasBadge = plugin.badges.some(b => b.status.toUpperCase() === val.toUpperCase()); - if (hasBadge) { - currentStatus = val; - break; - } - } - } - } - - if (!currentStatus) { + if (!currentStatus || currentStatus === 'NONE') { continue; } @@ -792,7 +763,7 @@ export class PluginService { const currentStatus = (idKey ? this.getPartitionPluginStatus(plugin.name, idKey, diskNumber, partitionNumber) : null) || (uuidKey ? this.getPartitionPluginStatus(plugin.name, uuidKey, diskNumber, partitionNumber) : null); - if (!currentStatus) { + if (!currentStatus || currentStatus === 'NONE') { continue; } @@ -817,6 +788,25 @@ export class PluginService { } } + const meta = (idKey ? this.getPluginMetadata(plugin.name, idKey, diskNumber, partitionNumber) : null) + || (uuidKey ? this.getPluginMetadata(plugin.name, uuidKey, diskNumber, partitionNumber) : null); + + if (matchingBadge.tooltipFields && matchingBadge.tooltipFields.length > 0 && meta) { + const infoParts: string[] = []; + for (const field of matchingBadge.tooltipFields) { + const val = meta[field.key]; + if (val !== undefined && val !== null && val !== '') { + const prefix = field.prefix || ''; + const suffix = field.suffix || ''; + infoParts.push(`${prefix}${val}${suffix}`); + } + } + if (infoParts.length > 0) { + const detailStr = infoParts.join(' · '); + tooltip = tooltip ? `${tooltip} (${detailStr})` : detailStr; + } + } + badges.push({ id: plugin.name, type: matchingBadge.type,