feat(plugins): decouple plugin Mercure status and implement dynamic i18n support
parent
9f34d2c234
commit
f10613af82
12
CHANGELOG.md
12
CHANGELOG.md
|
|
@ -1,5 +1,17 @@
|
|||
# Changelog
|
||||
|
||||
## [1.18.0] - 2026-10-23
|
||||
|
||||
### Added
|
||||
- **Desacoplamiento Total de Plugins y Eventos Mercure (`PluginService`, `groups.component`, `execute-command.component`)**:
|
||||
- `PluginService`: Suscripción dinámica y agnóstica al topic genérico de Mercure `plugins`, procesando eventos de cualquier plugin de forma desacoplada y actualizando el estado de los clientes en tiempo real sin hardcoding.
|
||||
- Consulta agnóstica de endpoints de estado (`statusEndpoint`) y reglas de badges (`badges`) declaradas dinámicamente en los manifiestos de los plugins.
|
||||
- Inferencia dinámica de badges contextuales en clientes (`getClientBadges`) mapeados directamente desde la especificación `badges` del manifiesto del plugin.
|
||||
- Limpieza de estilos en `groups.component.css`, adoptando clases semánticas universales (`.badge-info`, `.badge-neutral`, `.badge-pending`, `.badge-error`, `.badge-success`, `.badge-warning`).
|
||||
- **Sistema Estandarizado de Internacionalización Dinámica de Plugins (`PluginService`, `plugins.component`, `plugin-settings.component`, `execute-command.component`)**:
|
||||
- Incorporada la carga e inyección en caliente de traducciones (`i18n`) desde el endpoint `/api/plugins/{name}/i18n/{lang}` de cada plugin directamente en `@ngx-translate`, sin necesidad de alterar los ficheros de idioma en disco ni requerir recompilación.
|
||||
- Soporte completo para claves semánticas (`titleKey`, `descriptionKey`, `labelKey`, `tooltipKey`) en comandos, badges y formularios de configuración de plugins, con reactividad automática ante el cambio de idioma en la cabecera.
|
||||
|
||||
## [1.17.2] - 2026-10-22
|
||||
|
||||
### Revert
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "og-webconsole",
|
||||
"version": "1.14.0",
|
||||
"version": "1.18.0",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@
|
|||
@if (group.plugin.icon) {
|
||||
<mat-icon>{{ group.plugin.icon }}</mat-icon>
|
||||
}
|
||||
<span>{{ group.plugin.title }}</span>
|
||||
<span>{{ group.plugin.titleKey ? (group.plugin.titleKey | translate) : group.plugin.title }}</span>
|
||||
</button>
|
||||
|
||||
<mat-menu #pluginSubMenu="matMenu">
|
||||
|
|
@ -44,7 +44,7 @@
|
|||
@if (cmd.icon) {
|
||||
<mat-icon>{{ cmd.icon }}</mat-icon>
|
||||
}
|
||||
<span>{{ cmd.label }}</span>
|
||||
<span>{{ (cmd.translationKey || cmd.labelKey) ? ((cmd.translationKey || cmd.labelKey) | translate) : cmd.label }}</span>
|
||||
</button>
|
||||
}
|
||||
</mat-menu>
|
||||
|
|
|
|||
|
|
@ -72,8 +72,12 @@ export class ExecuteCommandComponent implements OnInit {
|
|||
const allowed = this.getAllowedCommandsByRole();
|
||||
this.arrayCommands = this.arrayCommands.filter(c => allowed.includes(c.slug));
|
||||
|
||||
// Punto de anclaje para grupos de comandos aportados por plugins
|
||||
// Punto de anclaje reactivo para grupos de comandos aportados por plugins
|
||||
this.pluginGroups = this.pluginService.getPluginGroups();
|
||||
this.pluginService.plugins$.subscribe(() => {
|
||||
this.pluginGroups = this.pluginService.getPluginGroups();
|
||||
this.updateCommandStates();
|
||||
});
|
||||
|
||||
this.updateCommandStates();
|
||||
}
|
||||
|
|
@ -577,13 +581,14 @@ export class ExecuteCommandComponent implements OnInit {
|
|||
}
|
||||
|
||||
private sendPluginCommandRequest(pluginCmd: PluginCommand, queue: boolean): void {
|
||||
const defaultSlug = pluginCmd.slug.includes('unfreeze') ? 'unfreeze' : 'freeze';
|
||||
const endpoint = pluginCmd.endpoint || `/api/plugins/ogfreeze/${defaultSlug}`;
|
||||
const cleanEndpoint = endpoint.replace(/^\/api/, '');
|
||||
const url = `${this.baseUrl}${cleanEndpoint}`;
|
||||
if (!pluginCmd.endpoint) {
|
||||
this.toastService.error('El comando no especifica un endpoint de ejecución', pluginCmd.label);
|
||||
return;
|
||||
}
|
||||
|
||||
const cleanEndpoint = pluginCmd.endpoint.replace(/^\/api/, '');
|
||||
const url = `${this.baseUrl}${cleanEndpoint}`;
|
||||
const clientIds = this.clientData.map(c => c.id || c['@id']);
|
||||
const isFreeze = pluginCmd.slug.includes('freeze') && !pluginCmd.slug.includes('unfreeze');
|
||||
|
||||
this.http.post<any>(url, {
|
||||
clients: clientIds,
|
||||
|
|
@ -591,10 +596,7 @@ export class ExecuteCommandComponent implements OnInit {
|
|||
target_os: pluginCmd.os
|
||||
}).subscribe({
|
||||
next: (response) => {
|
||||
this.pluginService.toggleFreezeStatus(this.clientData, isFreeze);
|
||||
const msg = response?.message || (isFreeze
|
||||
? 'Comando de congelación (UWF) procesado correctamente'
|
||||
: 'Comando de descongelación (UWF) procesado correctamente');
|
||||
const msg = response?.message || 'Comando procesado correctamente';
|
||||
this.toastService.success(msg, pluginCmd.label);
|
||||
},
|
||||
error: (error) => {
|
||||
|
|
|
|||
|
|
@ -2082,18 +2082,42 @@ mat-button-toggle-group {
|
|||
line-height: 14px;
|
||||
}
|
||||
|
||||
.plugin-badge.badge-frozen {
|
||||
background: linear-gradient(135deg, #e0f2fe 0%, #bae6fd 100%);
|
||||
color: #0369a1;
|
||||
border: 1px solid #7dd3fc;
|
||||
}
|
||||
|
||||
.plugin-badge.badge-unfrozen {
|
||||
.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;
|
||||
}
|
||||
|
||||
.plugin-badge.list-view-badge {
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -423,7 +423,7 @@
|
|||
RemotePC
|
||||
</span>
|
||||
}
|
||||
@for (badge of getPluginBadges(client); track badge.id) {
|
||||
@for (badge of getPluginBadges(client); 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>
|
||||
|
|
@ -554,7 +554,7 @@
|
|||
RemotePC
|
||||
</span>
|
||||
}
|
||||
@for (badge of getPluginBadges(client); track badge.id) {
|
||||
@for (badge of getPluginBadges(client); track badge.id + '-' + badge.type + '-' + badge.label) {
|
||||
<span class="plugin-badge badge-{{badge.type}} list-view-badge" [matTooltip]="badge.tooltip">
|
||||
<mat-icon class="plugin-badge-icon">{{ badge.icon }}</mat-icon> {{ badge.label }}
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -557,6 +557,8 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
|||
this.getStatusForAllClientsInNode(node);
|
||||
}
|
||||
|
||||
this.pluginService.loadActivePluginsStatus().subscribe();
|
||||
|
||||
// Calcular estadísticas después de cargar los clientes
|
||||
this.calculateLocalStats();
|
||||
},
|
||||
|
|
|
|||
|
|
@ -6,11 +6,11 @@
|
|||
</div>
|
||||
<div class="plugin-header-info">
|
||||
<div class="plugin-title-row">
|
||||
<h2 class="plugin-title">{{ plugin.title }}</h2>
|
||||
<h2 class="plugin-title">{{ plugin.titleKey ? (plugin.titleKey | translate) : plugin.title }}</h2>
|
||||
<span class="plugin-version-tag">v{{ plugin.version }}</span>
|
||||
<span class="plugin-status-tag">Activo</span>
|
||||
</div>
|
||||
<p class="plugin-description">{{ plugin.description }}</p>
|
||||
<p class="plugin-description">{{ plugin.descriptionKey ? (plugin.descriptionKey | translate) : plugin.description }}</p>
|
||||
<span class="plugin-author">Autor: {{ plugin.author }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -42,9 +42,9 @@
|
|||
@for (field of schema; track field.key) {
|
||||
<div class="form-field-card" [class.full-width]="field.type === 'textarea'">
|
||||
<div class="field-meta">
|
||||
<label class="field-label" [for]="'field-' + field.key">{{ field.label }}</label>
|
||||
<label class="field-label" [for]="'field-' + field.key">{{ field.labelKey ? (field.labelKey | translate) : field.label }}</label>
|
||||
@if (field.description) {
|
||||
<span class="field-description">{{ field.description }}</span>
|
||||
<span class="field-description">{{ field.descriptionKey ? (field.descriptionKey | translate) : field.description }}</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
<mat-tab>
|
||||
<ng-template mat-tab-label>
|
||||
<mat-icon class="tab-plugin-icon">{{ plugin.icon }}</mat-icon>
|
||||
<span>{{ plugin.title }}</span>
|
||||
<span>{{ plugin.titleKey ? (plugin.titleKey | translate) : plugin.title }}</span>
|
||||
</ng-template>
|
||||
|
||||
<div class="tab-inner-content">
|
||||
|
|
@ -42,7 +42,7 @@
|
|||
<h3 class="empty-title">No hay plugins instalados</h3>
|
||||
<p class="empty-desc">
|
||||
Actualmente no hay ninguna extensión o plugin registrado en este servidor OpenGnsys.
|
||||
Cuando instales un paquete compatible (como <code>ogfreeze</code>), aparecerá automáticamente aquí.
|
||||
Cuando instales un paquete compatible, aparecerá automáticamente aquí.
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
import { Injectable, inject } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { BehaviorSubject, Observable, of } from 'rxjs';
|
||||
import { catchError, map, tap } from 'rxjs/operators';
|
||||
import { BehaviorSubject, Observable, forkJoin, of } from 'rxjs';
|
||||
import { catchError, map, switchMap, tap } from 'rxjs/operators';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { ConfigService } from './config.service';
|
||||
|
||||
export interface PluginCommand {
|
||||
slug: string;
|
||||
translationKey?: string;
|
||||
labelKey?: string;
|
||||
label: string;
|
||||
icon: string;
|
||||
disabled: boolean;
|
||||
|
|
@ -14,9 +16,19 @@ export interface PluginCommand {
|
|||
os?: string[]; // Ej: ['windows'], ['linux']
|
||||
}
|
||||
|
||||
export interface PluginBadgeRule {
|
||||
status: string;
|
||||
type: string;
|
||||
icon: string;
|
||||
label: string;
|
||||
labelKey?: string;
|
||||
tooltip: string;
|
||||
tooltipKey?: string;
|
||||
}
|
||||
|
||||
export interface PluginBadge {
|
||||
id: string;
|
||||
type: 'frozen' | 'unfrozen' | 'info';
|
||||
id: string; // Plugin name
|
||||
type: string;
|
||||
icon: string;
|
||||
label: string;
|
||||
tooltip: string;
|
||||
|
|
@ -32,7 +44,9 @@ export interface FieldAction {
|
|||
export interface SchemaField {
|
||||
key: string;
|
||||
label: string;
|
||||
labelKey?: string;
|
||||
description?: string;
|
||||
descriptionKey?: string;
|
||||
type: 'text' | 'number' | 'select' | 'checkbox' | 'textarea';
|
||||
options?: string[];
|
||||
default: any;
|
||||
|
|
@ -42,12 +56,17 @@ export interface SchemaField {
|
|||
export interface PluginManifest {
|
||||
name: string;
|
||||
title: string;
|
||||
titleKey?: string;
|
||||
version: string;
|
||||
description: string;
|
||||
descriptionKey?: string;
|
||||
author: string;
|
||||
icon: string;
|
||||
enabled: boolean;
|
||||
commands: PluginCommand[];
|
||||
statusEndpoint?: string;
|
||||
badges?: PluginBadgeRule[];
|
||||
i18n?: Record<string, Record<string, any>>;
|
||||
schema: SchemaField[];
|
||||
defaultConfig?: Record<string, any>;
|
||||
config?: Record<string, any>;
|
||||
|
|
@ -64,16 +83,62 @@ export interface PluginGroup {
|
|||
export class PluginService {
|
||||
private http = inject(HttpClient);
|
||||
private configService = inject(ConfigService);
|
||||
private translateService = inject(TranslateService);
|
||||
|
||||
private activePlugins: PluginManifest[] = [];
|
||||
private pluginsSubject = new BehaviorSubject<PluginManifest[]>([]);
|
||||
public plugins$ = this.pluginsSubject.asObservable();
|
||||
|
||||
// Dynamic state cache (e.g. client freeze status: uuid/id -> 'FROZEN' | 'UNFROZEN')
|
||||
private clientFreezeMap = new Map<string, 'FROZEN' | 'UNFROZEN'>();
|
||||
// Dynamic state cache: key is `${pluginName}:${clientIdOrUuid}` -> status string
|
||||
private clientPluginStatusMap = new Map<string, string>();
|
||||
private mercureEventSource: EventSource | null = null;
|
||||
|
||||
constructor() {
|
||||
this.refreshPlugins().subscribe();
|
||||
this.initMercureSubscription();
|
||||
|
||||
// Re-aplicar traducciones dinámicas al cambiar de idioma
|
||||
this.translateService.onLangChange.subscribe((event) => {
|
||||
this.loadPluginTranslations(event.lang);
|
||||
});
|
||||
}
|
||||
|
||||
private getStatusKey(pluginName: string, idOrUuid: string | number): string {
|
||||
return `${pluginName}:${idOrUuid}`;
|
||||
}
|
||||
|
||||
public setPluginStatus(pluginName: string, idOrUuid: string | number, status: string): void {
|
||||
this.clientPluginStatusMap.set(this.getStatusKey(pluginName, idOrUuid), status);
|
||||
}
|
||||
|
||||
public getPluginStatus(pluginName: string, idOrUuid: string | number): string | undefined {
|
||||
return this.clientPluginStatusMap.get(this.getStatusKey(pluginName, idOrUuid));
|
||||
}
|
||||
|
||||
private initMercureSubscription(): void {
|
||||
const mercureUrl = this.configService.mercureUrl;
|
||||
if (!mercureUrl || this.mercureEventSource) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.mercureEventSource = new EventSource(`${mercureUrl}?topic=` + encodeURIComponent('plugins'));
|
||||
this.mercureEventSource.onmessage = (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
if (data && data.plugin && data.status) {
|
||||
if (data.clientId !== undefined && data.clientId !== null) {
|
||||
this.setPluginStatus(data.plugin, String(data.clientId), data.status);
|
||||
}
|
||||
if (data.clientUuid) {
|
||||
this.setPluginStatus(data.plugin, data.clientUuid, data.status);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
};
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
|
||||
refreshPlugins(): Observable<PluginManifest[]> {
|
||||
|
|
@ -84,13 +149,103 @@ export class PluginService {
|
|||
|
||||
return this.http.get<PluginManifest[]>(`${apiUrl}/plugins`).pipe(
|
||||
catchError(() => of([])),
|
||||
tap((plugins) => {
|
||||
this.activePlugins = (plugins || []).filter(p => p.enabled);
|
||||
this.pluginsSubject.next(this.activePlugins);
|
||||
switchMap((plugins) => {
|
||||
const active = (plugins || []).filter(p => p.enabled);
|
||||
if (active.length === 0) {
|
||||
this.activePlugins = [];
|
||||
this.pluginsSubject.next([]);
|
||||
return of([]);
|
||||
}
|
||||
|
||||
// Descubrir y fusionar manifiestos extendidos de cada plugin activo
|
||||
const manifestRequests = active.map(p =>
|
||||
this.http.get<Partial<PluginManifest>>(`${apiUrl}/plugins/${p.name}/manifest`).pipe(
|
||||
catchError(() => of({})),
|
||||
map(extra => ({ ...p, ...extra }))
|
||||
)
|
||||
);
|
||||
|
||||
return forkJoin(manifestRequests).pipe(
|
||||
tap((fullPlugins) => {
|
||||
this.activePlugins = fullPlugins;
|
||||
this.pluginsSubject.next(this.activePlugins);
|
||||
this.loadPluginTranslations();
|
||||
this.loadActivePluginsStatus().subscribe();
|
||||
})
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
public loadPluginTranslations(lang?: string): void {
|
||||
const currentLang = lang || this.translateService.currentLang || this.translateService.defaultLang || 'es';
|
||||
const apiUrl = this.configService.apiUrl;
|
||||
|
||||
for (const plugin of this.activePlugins) {
|
||||
// 1. Si el plugin declara i18n directamente en el manifest, lo inyectamos
|
||||
if (plugin.i18n && typeof plugin.i18n === 'object') {
|
||||
for (const [l, dict] of Object.entries(plugin.i18n)) {
|
||||
if (dict && typeof dict === 'object') {
|
||||
this.translateService.setTranslation(l, dict, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Si el plugin tiene endpoint /i18n/{lang} (carpeta i18n/ estandarizada), lo descargamos bajo demanda
|
||||
if (apiUrl) {
|
||||
this.http.get<Record<string, any>>(`${apiUrl}/plugins/${plugin.name}/i18n/${currentLang}`).pipe(
|
||||
catchError(() => of(null)),
|
||||
tap((catalog) => {
|
||||
if (catalog && typeof catalog === 'object' && Object.keys(catalog).length > 0) {
|
||||
this.translateService.setTranslation(currentLang, catalog, true);
|
||||
}
|
||||
})
|
||||
).subscribe();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loadActivePluginsStatus(): Observable<void> {
|
||||
const apiUrl = this.configService.apiUrl;
|
||||
if (!apiUrl || this.activePlugins.length === 0) {
|
||||
return of(undefined);
|
||||
}
|
||||
|
||||
const pluginsWithStatus = this.activePlugins.filter(p => !!p.statusEndpoint);
|
||||
if (pluginsWithStatus.length === 0) {
|
||||
return of(undefined);
|
||||
}
|
||||
|
||||
const requests = pluginsWithStatus.map(plugin => {
|
||||
const endpoint = plugin.statusEndpoint!.startsWith('http')
|
||||
? plugin.statusEndpoint!
|
||||
: `${apiUrl.replace(/\/api\/?$/, '')}${plugin.statusEndpoint!.startsWith('/') ? '' : '/'}${plugin.statusEndpoint}`;
|
||||
|
||||
return this.http.get<any[]>(endpoint).pipe(
|
||||
catchError(() => of([])),
|
||||
tap(rows => {
|
||||
if (Array.isArray(rows)) {
|
||||
for (const row of rows) {
|
||||
const status = row.status;
|
||||
const id = row.client_id ?? row.clientId ?? row.id;
|
||||
const uuid = row.client_uuid ?? row.clientUuid ?? row.uuid;
|
||||
if (status) {
|
||||
if (id !== undefined && id !== null) {
|
||||
this.setPluginStatus(plugin.name, String(id), status);
|
||||
}
|
||||
if (uuid) {
|
||||
this.setPluginStatus(plugin.name, uuid, status);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
return forkJoin(requests).pipe(map(() => undefined));
|
||||
}
|
||||
|
||||
getPlugins(): PluginManifest[] {
|
||||
return this.activePlugins.filter(p => p.enabled);
|
||||
}
|
||||
|
|
@ -110,17 +265,32 @@ export class PluginService {
|
|||
getPluginGroups(): PluginGroup[] {
|
||||
return this.getPlugins()
|
||||
.filter(p => p.commands && p.commands.length > 0)
|
||||
.map(p => ({
|
||||
plugin: p,
|
||||
commands: p.commands
|
||||
}));
|
||||
.map(p => {
|
||||
const translatedTitle = p.titleKey ? this.translateService.instant(p.titleKey) : p.title;
|
||||
return {
|
||||
plugin: {
|
||||
...p,
|
||||
title: (translatedTitle && translatedTitle !== p.titleKey) ? translatedTitle : p.title
|
||||
},
|
||||
commands: p.commands.map(cmd => {
|
||||
const key = cmd.translationKey || cmd.labelKey;
|
||||
if (key) {
|
||||
const trans = this.translateService.instant(key);
|
||||
if (trans && trans !== key) {
|
||||
return { ...cmd, label: trans };
|
||||
}
|
||||
}
|
||||
return cmd;
|
||||
})
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
getCommands(): PluginCommand[] {
|
||||
const commands: PluginCommand[] = [];
|
||||
for (const plugin of this.getPlugins()) {
|
||||
if (plugin.commands && Array.isArray(plugin.commands)) {
|
||||
commands.push(...plugin.commands);
|
||||
for (const group of this.getPluginGroups()) {
|
||||
if (group.commands && Array.isArray(group.commands)) {
|
||||
commands.push(...group.commands);
|
||||
}
|
||||
}
|
||||
return commands;
|
||||
|
|
@ -155,28 +325,50 @@ export class PluginService {
|
|||
|
||||
getClientBadges(client: any): PluginBadge[] {
|
||||
const badges: PluginBadge[] = [];
|
||||
if (!client) return badges;
|
||||
if (!client || this.activePlugins.length === 0) return badges;
|
||||
|
||||
// Badges specific to active plugins (e.g., ogfreeze)
|
||||
if (this.isPluginActive('ogfreeze')) {
|
||||
const key = client.uuid || String(client.id) || client.name;
|
||||
const status = this.clientFreezeMap.get(key) || client.freezeStatus;
|
||||
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.getPluginStatus(plugin.name, idKey) : null)
|
||||
|| (uuidKey ? this.getPluginStatus(plugin.name, uuidKey) : null);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if (status === 'FROZEN') {
|
||||
badges.push({
|
||||
id: 'ogfreeze',
|
||||
type: 'frozen',
|
||||
icon: 'ac_unit',
|
||||
label: 'Congelado',
|
||||
tooltip: 'Equipo protegido contra escritura con UWF'
|
||||
});
|
||||
} else if (status === 'UNFROZEN') {
|
||||
badges.push({
|
||||
id: 'ogfreeze',
|
||||
type: 'unfrozen',
|
||||
icon: 'lock_open',
|
||||
label: 'Descongelado',
|
||||
tooltip: 'Filtro de protección UWF desactivado'
|
||||
id: plugin.name,
|
||||
type: matchingBadge.type,
|
||||
icon: matchingBadge.icon,
|
||||
label,
|
||||
tooltip
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -184,14 +376,6 @@ export class PluginService {
|
|||
return badges;
|
||||
}
|
||||
|
||||
toggleFreezeStatus(clients: any[], freeze: boolean): void {
|
||||
const newStatus = freeze ? 'FROZEN' : 'UNFROZEN';
|
||||
for (const client of clients) {
|
||||
const key = client.uuid || String(client.id) || client.name;
|
||||
this.clientFreezeMap.set(key, newStatus);
|
||||
}
|
||||
}
|
||||
|
||||
getPluginConfig(pluginName: string): Record<string, any> {
|
||||
const plugin = this.getPlugin(pluginName);
|
||||
const defaults = plugin?.defaultConfig || {};
|
||||
|
|
|
|||
Loading…
Reference in New Issue