diff --git a/CHANGELOG.md b/CHANGELOG.md index 0897059..5f939ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## [1.24.6] - 2026-10-31 + +### Fixed +- **Desacoplamiento Estricto y Sincronización Agnóstica en `PluginService`**: + - Eliminadas todas las cadenas hardcodeadas de estados específicos de plugins (`['REMOVING', 'CHECKING', 'FREEZING', 'UNFREEZING']`, `'FREE'`, `'UNFROZEN'`). + - Implementada gestión genérica del ciclo de vida de particiones (`handlePartitionClear` y `syncGlobalStatusFromPartitions`): al recibir un estado `NONE` para una partición, se comprueba dinámicamente si al cliente le quedan otras particiones con estado activo; si no queda ninguna, se purga de forma inmediata el estado global en memoria evitando badges fantasma o residuales. + - La resolución de badges a nivel de equipo comprueba directamente contra el catálogo declarado por el plugin en su `manifest.json` (`plugin.badges`), eliminando filtros ad-hoc. + - El guardado y borrado de metadatos se gestiona de forma agnóstica según la presencia efectiva de datos contextuales en el payload. + ## [1.24.5] - 2026-10-30 ### Added diff --git a/ogWebconsole/package.json b/ogWebconsole/package.json index 79ca756..29178bc 100644 --- a/ogWebconsole/package.json +++ b/ogWebconsole/package.json @@ -1,6 +1,6 @@ { "name": "og-webconsole", - "version": "1.24.5", + "version": "1.24.6", "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 07a9f4b..a3d85f7 100644 --- a/ogWebconsole/src/app/services/plugin.service.ts +++ b/ogWebconsole/src/app/services/plugin.service.ts @@ -186,6 +186,30 @@ export class PluginService { 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); + } + } + private initMercureSubscription(): void { const mercureUrl = this.configService.mercureUrl; if (!mercureUrl || this.mercureEventSource) { @@ -218,33 +242,27 @@ export class PluginService { if (data.status === 'NONE') { if (data.clientId !== undefined && data.clientId !== null) { const idStr = String(data.clientId); - this.clientPluginMetadataMap.delete(this.getStatusKey(data.plugin, idStr)); if (disk !== undefined && disk !== null && part !== undefined && part !== null) { - this.clientPluginStatusMap.delete(this.getPartitionStatusKey(data.plugin, idStr, Number(disk), Number(part))); - const curGlobal = this.clientPluginStatusMap.get(this.getStatusKey(data.plugin, idStr)); - if (curGlobal && ['REMOVING', 'CHECKING', 'FREEZING', 'UNFREEZING'].includes(curGlobal.toUpperCase())) { - this.clientPluginStatusMap.delete(this.getStatusKey(data.plugin, idStr)); - } + 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; - this.clientPluginMetadataMap.delete(this.getStatusKey(data.plugin, uuidStr)); if (disk !== undefined && disk !== null && part !== undefined && part !== null) { - this.clientPluginStatusMap.delete(this.getPartitionStatusKey(data.plugin, uuidStr, Number(disk), Number(part))); - const curGlobal = this.clientPluginStatusMap.get(this.getStatusKey(data.plugin, uuidStr)); - if (curGlobal && ['REMOVING', 'CHECKING', 'FREEZING', 'UNFREEZING'].includes(curGlobal.toUpperCase())) { - this.clientPluginStatusMap.delete(this.getStatusKey(data.plugin, uuidStr)); - } + 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}:`); } } @@ -262,9 +280,9 @@ export class PluginService { if (data.clientId !== undefined && data.clientId !== null) { const idStr = String(data.clientId); this.setPluginStatus(data.plugin, idStr, data.status); - if (hasMeta && data.status !== 'FREE') { + if (hasMeta) { this.setPluginMetadata(data.plugin, idStr, metadata); - } else if (data.status === 'FREE') { + } else { this.clearPluginMetadata(data.plugin, idStr); } if (disk !== undefined && disk !== null && part !== undefined && part !== null) { @@ -273,9 +291,9 @@ export class PluginService { } if (data.clientUuid) { this.setPluginStatus(data.plugin, data.clientUuid, data.status); - if (hasMeta && data.status !== 'FREE') { + if (hasMeta) { this.setPluginMetadata(data.plugin, data.clientUuid, metadata); - } else if (data.status === 'FREE') { + } else { this.clearPluginMetadata(data.plugin, data.clientUuid); } if (disk !== undefined && disk !== null && part !== undefined && part !== null) { @@ -416,9 +434,9 @@ export class PluginService { if (id !== undefined && id !== null) { const idStr = String(id); this.setPluginStatus(plugin.name, idStr, status); - if (hasMeta && status !== 'FREE') { + if (hasMeta) { this.setPluginMetadata(plugin.name, idStr, metadata); - } else if (status === 'FREE') { + } else { this.clearPluginMetadata(plugin.name, idStr); } if (disk !== undefined && disk !== null && part !== undefined && part !== null) { @@ -427,9 +445,9 @@ export class PluginService { } if (uuid) { this.setPluginStatus(plugin.name, uuid, status); - if (hasMeta && status !== 'FREE') { + if (hasMeta) { this.setPluginMetadata(plugin.name, uuid, metadata); - } else if (status === 'FREE') { + } else { this.clearPluginMetadata(plugin.name, uuid); } if (disk !== undefined && disk !== null && part !== undefined && part !== null) { @@ -686,14 +704,17 @@ export class PluginService { 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 + // 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' && val !== 'UNFROZEN') { - currentStatus = val; - break; + if (isMatch && val && val !== 'NONE') { + const hasBadge = plugin.badges.some(b => b.status.toUpperCase() === val.toUpperCase()); + if (hasBadge) { + currentStatus = val; + break; + } } } }