fix(plugins): gestion nativa de estado por disco y particion con contrato estandarizado (v1.24.7)
parent
1ee3b6d465
commit
dc4186ff4d
10
CHANGELOG.md
10
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
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "og-webconsole",
|
||||
"version": "1.24.6",
|
||||
"version": "1.24.7",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in New Issue