feat(plugins): add generic partition-level status and badge support

main
Juan Manuel Bardallo 2026-10-26 13:46:08 +01:00
parent 80e8cf95e6
commit fb437fd8a9
4 changed files with 174 additions and 1 deletions

View File

@ -274,3 +274,67 @@ table {
font-style: italic;
margin-top: 2px;
}
.partition-badges-row {
display: flex;
flex-wrap: wrap;
gap: 4px;
margin-top: 4px;
}
.plugin-badge {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 2px 8px;
border-radius: 12px;
font-size: 0.72rem;
font-weight: 600;
letter-spacing: 0.2px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
transition: all 0.2s ease;
width: fit-content;
}
.plugin-badge .plugin-badge-icon {
font-size: 14px;
width: 14px;
height: 14px;
line-height: 14px;
}
.plugin-badge.badge-neutral {
background: #f1f5f9;
color: #64748b;
border: 1px solid #cbd5e1;
}
.plugin-badge.badge-pending {
background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
color: #b45309;
border: 1px solid #fcd34d;
}
.plugin-badge.badge-error {
background: linear-gradient(135deg, #fee2e2 0%, #fecaca 100%);
color: #b91c1c;
border: 1px solid #fca5a5;
}
.plugin-badge.badge-info {
background: linear-gradient(135deg, #eff6ff 0%, #dbeafe 100%);
color: #1d4ed8;
border: 1px solid #93c5fd;
}
.plugin-badge.badge-success {
background: linear-gradient(135deg, #ecfdf5 0%, #d1fae5 100%);
color: #047857;
border: 1px solid #6ee7b7;
}
.plugin-badge.badge-warning {
background: linear-gradient(135deg, #fffbeb 0%, #fef3c7 100%);
color: #b45309;
border: 1px solid #fde68a;
}

View File

@ -91,6 +91,13 @@
@if (image.deployDate) {
<span class="image-date">Desplegada: {{ image.deployDate | date:'dd/MM/yyyy HH:mm' }}</span>
}
<div class="partition-badges-row">
@for (badge of getPartitionBadges(disk.diskNumber, image.partitionNumber); track badge.id + '-' + badge.type + '-' + badge.label) {
<span class="plugin-badge badge-{{badge.type}}" [matTooltip]="badge.tooltip">
<mat-icon class="plugin-badge-icon">{{ badge.icon }}</mat-icon> {{ badge.label }}
</span>
}
</div>
}
</div>
}

View File

@ -10,6 +10,7 @@ import { Router } from "@angular/router";
import { ToastrService } from "ngx-toastr";
import { ManageClientComponent } from "../../shared/clients/manage-client/manage-client.component";
import { ConfigService } from '@services/config.service';
import { PluginBadge, PluginService } from '@services/plugin.service';
interface ClientInfo {
property: string;
@ -23,6 +24,7 @@ interface ClientInfo {
})
export class ClientDetailsComponent {
public translate = inject(TranslateService);
private pluginService = inject(PluginService);
baseUrl: string;
@ViewChild('assistantContainer') assistantContainer!: ElementRef;
clientUuid: string;
@ -209,6 +211,10 @@ export class ClientDetailsComponent {
});
}
getPartitionBadges(diskNumber: number, partitionNumber: number): PluginBadge[] {
return this.pluginService.getPartitionBadges(this.clientData, diskNumber, partitionNumber);
}
onNoClick(): void {
this.dialog.closeAll();
}

View File

@ -108,14 +108,26 @@ export class PluginService {
return `${pluginName}:${idOrUuid}`;
}
private getPartitionStatusKey(pluginName: string, idOrUuid: string | number, disk: number, partition: number): string {
return `${pluginName}:${idOrUuid}:${disk}:${partition}`;
}
public setPluginStatus(pluginName: string, idOrUuid: string | number, status: string): void {
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 getPartitionPluginStatus(pluginName: string, idOrUuid: string | number, disk: number, partition: number): string | undefined {
return this.clientPluginStatusMap.get(this.getPartitionStatusKey(pluginName, idOrUuid, disk, partition));
}
private initMercureSubscription(): void {
const mercureUrl = this.configService.mercureUrl;
if (!mercureUrl || this.mercureEventSource) {
@ -128,11 +140,20 @@ export class PluginService {
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.clientId !== undefined && data.clientId !== null) {
this.setPluginStatus(data.plugin, String(data.clientId), data.status);
if (disk !== undefined && disk !== null && part !== undefined && part !== null) {
this.setPartitionPluginStatus(data.plugin, String(data.clientId), Number(disk), Number(part), data.status);
}
}
if (data.clientUuid) {
this.setPluginStatus(data.plugin, data.clientUuid, data.status);
if (disk !== undefined && disk !== null && part !== undefined && part !== null) {
this.setPartitionPluginStatus(data.plugin, data.clientUuid, Number(disk), Number(part), data.status);
}
}
}
} catch {
@ -243,12 +264,21 @@ 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;
if (status) {
if (id !== undefined && id !== null) {
this.setPluginStatus(plugin.name, String(id), status);
if (disk !== undefined && disk !== null && part !== undefined && part !== null) {
this.setPartitionPluginStatus(plugin.name, String(id), Number(disk), Number(part), status);
}
}
if (uuid) {
this.setPluginStatus(plugin.name, uuid, status);
if (disk !== undefined && disk !== null && part !== undefined && part !== null) {
this.setPartitionPluginStatus(plugin.name, uuid, Number(disk), Number(part), status);
}
}
}
}
@ -357,9 +387,75 @@ export class PluginService {
continue;
}
const currentStatus = (idKey ? this.getPluginStatus(plugin.name, idKey) : null)
// 1. Estado global del cliente
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
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 (!currentStatus) {
continue;
}
const matchingBadge = plugin.badges.find(
b => b.status.toUpperCase() === currentStatus.toUpperCase()
);
if (matchingBadge) {
let label = matchingBadge.label;
if (matchingBadge.labelKey) {
const trans = this.translateService.instant(matchingBadge.labelKey);
if (trans && trans !== matchingBadge.labelKey) {
label = trans;
}
}
let tooltip = matchingBadge.tooltip;
if (matchingBadge.tooltipKey) {
const trans = this.translateService.instant(matchingBadge.tooltipKey);
if (trans && trans !== matchingBadge.tooltipKey) {
tooltip = trans;
}
}
badges.push({
id: plugin.name,
type: matchingBadge.type,
icon: matchingBadge.icon,
label,
tooltip
});
}
}
return badges;
}
getPartitionBadges(client: any, diskNumber: number, partitionNumber: number): PluginBadge[] {
const badges: PluginBadge[] = [];
if (!client || this.activePlugins.length === 0) return badges;
const idKey = client.id ? String(client.id) : null;
const uuidKey = client.uuid || client.name;
for (const plugin of this.activePlugins) {
if (!plugin.badges || plugin.badges.length === 0) {
continue;
}
const currentStatus = (idKey ? this.getPartitionPluginStatus(plugin.name, idKey, diskNumber, partitionNumber) : null)
|| (uuidKey ? this.getPartitionPluginStatus(plugin.name, uuidKey, diskNumber, partitionNumber) : null);
if (!currentStatus) {
continue;
}