fix(plugins): gestion nativa de estado por disco y particion con contrato estandarizado (v1.24.7)
oggui-debian-package/pipeline/head This commit looks good Details
oggui-debian-package/pipeline/tag This commit looks good Details
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details

main 1.24.7
Juan Manuel Bardallo 2026-10-31 22:26:51 +01:00
parent 1ee3b6d465
commit dc4186ff4d
3 changed files with 161 additions and 161 deletions

View File

@ -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

View File

@ -1,6 +1,6 @@
{
"name": "og-webconsole",
"version": "1.24.6",
"version": "1.24.7",
"scripts": {
"ng": "ng",
"start": "ng serve",

View File

@ -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,