parent
f8bd02c316
commit
5d206432a5
|
|
@ -1,5 +1,13 @@
|
|||
# Changelog
|
||||
|
||||
## [1.23.0] - 2026-10-27
|
||||
|
||||
### Added
|
||||
- **Enriquecimiento Contextual de Tooltips en Badges de Plugins (`PluginService`, `groups.component`)**:
|
||||
- Incorporada caché reactiva de metadatos de clientes (`clientPluginMetadataMap`) en `PluginService`, extrayendo automáticamente atributos contextuales proporcionados por cualquier plugin tanto en la consulta inicial (`statusEndpoint`) como en las actualizaciones en tiempo real recibidas por Mercure.
|
||||
- Al renderizar los badges (`getClientBadges`), si existen atributos contextuales como el identificador del alumno/usuario (`dni`, `user`) y el tiempo restante (`remaining_minutes`, `remainingMinutes`), se añaden dinámicamente al texto del tooltip (ej. `"Puesto reservado y en uso (DNI: 44212977P · 58 min restantes)"`), preservando el badge visualmente limpio y compacto (`Ocupado`, `A punto de expirar`).
|
||||
- Limpieza automática y atómica de metadatos cuando el puesto queda libre (`FREE`) o se reinicia (`NONE`), manteniendo sincronizada la consola web en todo momento sin acoplamiento a ningún plugin en concreto.
|
||||
|
||||
## [1.22.0] - 2026-10-27
|
||||
|
||||
### Added
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "og-webconsole",
|
||||
"version": "1.22.0",
|
||||
"version": "1.23.0",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
|
|
|
|||
|
|
@ -113,6 +113,8 @@ export class PluginService {
|
|||
|
||||
// Dynamic state cache: key is `${pluginName}:${clientIdOrUuid}` -> status string
|
||||
private clientPluginStatusMap = new Map<string, string>();
|
||||
// Dynamic metadata cache: key is `${pluginName}:${clientIdOrUuid}` -> extra attributes (e.g. dni, remaining_minutes)
|
||||
private clientPluginMetadataMap = new Map<string, any>();
|
||||
private mercureEventSource: EventSource | null = null;
|
||||
|
||||
constructor() {
|
||||
|
|
@ -149,6 +151,20 @@ export class PluginService {
|
|||
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);
|
||||
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 clearPluginMetadata(pluginName: string, idOrUuid: string | number): void {
|
||||
this.clientPluginMetadataMap.delete(this.getStatusKey(pluginName, idOrUuid));
|
||||
}
|
||||
|
||||
private initMercureSubscription(): void {
|
||||
const mercureUrl = this.configService.mercureUrl;
|
||||
if (!mercureUrl || this.mercureEventSource) {
|
||||
|
|
@ -170,11 +186,17 @@ export class PluginService {
|
|||
this.clientPluginStatusMap.delete(key);
|
||||
}
|
||||
}
|
||||
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);
|
||||
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));
|
||||
|
|
@ -190,6 +212,7 @@ export class PluginService {
|
|||
}
|
||||
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));
|
||||
|
|
@ -204,14 +227,34 @@ export class PluginService {
|
|||
}
|
||||
}
|
||||
} 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) {
|
||||
this.setPluginStatus(data.plugin, String(data.clientId), data.status);
|
||||
const idStr = String(data.clientId);
|
||||
this.setPluginStatus(data.plugin, idStr, data.status);
|
||||
if (hasMeta && data.status !== 'FREE') {
|
||||
this.setPluginMetadata(data.plugin, idStr, metadata);
|
||||
} else if (data.status === 'FREE') {
|
||||
this.clearPluginMetadata(data.plugin, idStr);
|
||||
}
|
||||
if (disk !== undefined && disk !== null && part !== undefined && part !== null) {
|
||||
this.setPartitionPluginStatus(data.plugin, String(data.clientId), Number(disk), Number(part), data.status);
|
||||
this.setPartitionPluginStatus(data.plugin, idStr, Number(disk), Number(part), data.status);
|
||||
}
|
||||
}
|
||||
if (data.clientUuid) {
|
||||
this.setPluginStatus(data.plugin, data.clientUuid, data.status);
|
||||
if (hasMeta && data.status !== 'FREE') {
|
||||
this.setPluginMetadata(data.plugin, data.clientUuid, metadata);
|
||||
} else if (data.status === 'FREE') {
|
||||
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);
|
||||
}
|
||||
|
|
@ -329,15 +372,35 @@ export class PluginService {
|
|||
const disk = row.disk_number ?? row.diskNumber ?? row.disk;
|
||||
const part = row.partition_number ?? row.partitionNumber ?? row.partition;
|
||||
|
||||
// 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) {
|
||||
metadata[k] = v;
|
||||
}
|
||||
}
|
||||
const hasMeta = Object.keys(metadata).length > 0;
|
||||
|
||||
if (status) {
|
||||
if (id !== undefined && id !== null) {
|
||||
this.setPluginStatus(plugin.name, String(id), status);
|
||||
const idStr = String(id);
|
||||
this.setPluginStatus(plugin.name, idStr, status);
|
||||
if (hasMeta && status !== 'FREE') {
|
||||
this.setPluginMetadata(plugin.name, idStr, metadata);
|
||||
} else if (status === 'FREE') {
|
||||
this.clearPluginMetadata(plugin.name, idStr);
|
||||
}
|
||||
if (disk !== undefined && disk !== null && part !== undefined && part !== null) {
|
||||
this.setPartitionPluginStatus(plugin.name, String(id), Number(disk), Number(part), status);
|
||||
this.setPartitionPluginStatus(plugin.name, idStr, Number(disk), Number(part), status);
|
||||
}
|
||||
}
|
||||
if (uuid) {
|
||||
this.setPluginStatus(plugin.name, uuid, status);
|
||||
if (hasMeta && status !== 'FREE') {
|
||||
this.setPluginMetadata(plugin.name, uuid, metadata);
|
||||
} else if (status === 'FREE') {
|
||||
this.clearPluginMetadata(plugin.name, uuid);
|
||||
}
|
||||
if (disk !== undefined && disk !== null && part !== undefined && part !== null) {
|
||||
this.setPartitionPluginStatus(plugin.name, uuid, Number(disk), Number(part), status);
|
||||
}
|
||||
|
|
@ -490,6 +553,26 @@ export class PluginService {
|
|||
}
|
||||
}
|
||||
|
||||
// Enriquecer tooltip con metadatos contextuales si están disponibles (p. ej. DNI del alumno, tiempo restante)
|
||||
const meta = (idKey ? this.getPluginMetadata(plugin.name, idKey) : null)
|
||||
|| (uuidKey ? this.getPluginMetadata(plugin.name, uuidKey) : null);
|
||||
|
||||
if (meta) {
|
||||
const dni = meta.dni || meta.user || meta.student;
|
||||
const mins = meta.remaining_minutes !== undefined ? meta.remaining_minutes : meta.remainingMinutes;
|
||||
const infoParts: string[] = [];
|
||||
if (dni) {
|
||||
infoParts.push(`DNI: ${dni}`);
|
||||
}
|
||||
if (mins !== undefined && mins !== null && mins !== '') {
|
||||
infoParts.push(`${mins} min`);
|
||||
}
|
||||
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