diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e5d29c..41c7f15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## [1.19.0] - 2026-10-23 + +### Added +- **Activación y Desactivación Dinámica de Plugins (`PluginService`, `plugins.component`, `plugin-settings.component`)**: + - Incorporada la capacidad de activar y desactivar plugins en caliente sin desinstalarlos del sistema. + - Al desactivar un plugin, sus comandos contextuales y badges se ocultan automáticamente de las tarjetas y menús de clientes en toda la consola web, preservando íntegra su configuración en base de datos. + - En la vista de administración `/plugins`, se integró un interruptor deslizante (`mat-slide-toggle`) en la cabecera de configuración para conmutar entre `Activo` e `Inactivo` con notificación toast y actualización reactiva. + - Añadido indicador visual de estado y conteo en la cabecera del panel (`X Activo(s) / Y Instalado(s)`) y badge `Inactivo` en las pestañas de los plugins deshabilitados. + ## [1.18.0] - 2026-10-23 ### Added diff --git a/ogWebconsole/package.json b/ogWebconsole/package.json index 14b02c3..2a1171a 100644 --- a/ogWebconsole/package.json +++ b/ogWebconsole/package.json @@ -1,6 +1,6 @@ { "name": "og-webconsole", - "version": "1.18.0", + "version": "1.19.0", "scripts": { "ng": "ng", "start": "ng serve", diff --git a/ogWebconsole/src/app/components/plugins/plugin-settings/plugin-settings.component.css b/ogWebconsole/src/app/components/plugins/plugin-settings/plugin-settings.component.css index ca02592..243adc3 100644 --- a/ogWebconsole/src/app/components/plugins/plugin-settings/plugin-settings.component.css +++ b/ogWebconsole/src/app/components/plugins/plugin-settings/plugin-settings.component.css @@ -70,6 +70,20 @@ border-radius: 12px; } +.plugin-status-toggle { + margin-left: auto; + font-weight: 600; + font-size: 0.85rem; +} + +.plugin-status-toggle.active { + color: #166534; +} + +.plugin-status-toggle.inactive { + color: #64748b; +} + .plugin-description { margin: 6px 0 0 0; color: #475569; diff --git a/ogWebconsole/src/app/components/plugins/plugin-settings/plugin-settings.component.html b/ogWebconsole/src/app/components/plugins/plugin-settings/plugin-settings.component.html index c890873..77ead50 100644 --- a/ogWebconsole/src/app/components/plugins/plugin-settings/plugin-settings.component.html +++ b/ogWebconsole/src/app/components/plugins/plugin-settings/plugin-settings.component.html @@ -8,7 +8,15 @@

{{ plugin.titleKey ? (plugin.titleKey | translate) : plugin.title }}

v{{ plugin.version }} - Activo + + {{ plugin.enabled ? 'Activo' : 'Inactivo' }} +

{{ plugin.descriptionKey ? (plugin.descriptionKey | translate) : plugin.description }}

Autor: {{ plugin.author }} diff --git a/ogWebconsole/src/app/components/plugins/plugin-settings/plugin-settings.component.ts b/ogWebconsole/src/app/components/plugins/plugin-settings/plugin-settings.component.ts index db98551..63e112d 100644 --- a/ogWebconsole/src/app/components/plugins/plugin-settings/plugin-settings.component.ts +++ b/ogWebconsole/src/app/components/plugins/plugin-settings/plugin-settings.component.ts @@ -55,6 +55,20 @@ export class PluginSettingsComponent implements OnInit, OnChanges, OnDestroy { } } + toggleEnabled(checked: boolean): void { + if (!this.pluginName) return; + this.pluginService.togglePlugin(this.pluginName, checked).subscribe({ + next: () => { + const statusText = checked ? 'activado' : 'desactivado'; + this.toast.success(`Plugin ${statusText} correctamente`, this.plugin?.title || 'Plugin'); + }, + error: () => { + this.toast.error('Error al cambiar el estado del plugin', this.plugin?.title || 'Plugin'); + this.loadPluginData(); + } + }); + } + runFieldAction(field: SchemaField): void { if (!field.action || !field.action.endpoint) return; diff --git a/ogWebconsole/src/app/components/plugins/plugins.component.css b/ogWebconsole/src/app/components/plugins/plugins.component.css index bf2faa3..dbceaf3 100644 --- a/ogWebconsole/src/app/components/plugins/plugins.component.css +++ b/ogWebconsole/src/app/components/plugins/plugins.component.css @@ -148,3 +148,14 @@ font-size: 0.95rem; margin: 0; } + +.tab-disabled-badge { + margin-left: 8px; + background: #f1f5f9; + color: #64748b; + font-size: 0.7rem; + font-weight: 600; + padding: 2px 6px; + border-radius: 8px; + border: 1px solid #cbd5e1; +} diff --git a/ogWebconsole/src/app/components/plugins/plugins.component.html b/ogWebconsole/src/app/components/plugins/plugins.component.html index cb873d9..62b3221 100644 --- a/ogWebconsole/src/app/components/plugins/plugins.component.html +++ b/ogWebconsole/src/app/components/plugins/plugins.component.html @@ -5,7 +5,7 @@ extension

Plugins y Extensiones

@if (!isLoading) { - {{ plugins.length }} Activo(s) + {{ activePluginsCount }} Activo(s) / {{ plugins.length }} Instalado(s) }

@@ -27,6 +27,9 @@ {{ plugin.icon }} {{ plugin.titleKey ? (plugin.titleKey | translate) : plugin.title }} + @if (!plugin.enabled) { + Inactivo + }

diff --git a/ogWebconsole/src/app/components/plugins/plugins.component.ts b/ogWebconsole/src/app/components/plugins/plugins.component.ts index 92cf793..d10ffea 100644 --- a/ogWebconsole/src/app/components/plugins/plugins.component.ts +++ b/ogWebconsole/src/app/components/plugins/plugins.component.ts @@ -14,11 +14,15 @@ export class PluginsComponent implements OnInit, OnDestroy { private pluginSub?: Subscription; private cdr = inject(ChangeDetectorRef); + get activePluginsCount(): number { + return this.plugins.filter(p => p.enabled).length; + } + constructor(public pluginService: PluginService) {} ngOnInit(): void { // 1. Asignar estado actual en memoria si ya existe - this.plugins = this.pluginService.getPlugins(); + this.plugins = this.pluginService.getAllPlugins(); if (this.plugins.length > 0) { this.isLoading = false; } diff --git a/ogWebconsole/src/app/services/plugin.service.ts b/ogWebconsole/src/app/services/plugin.service.ts index d9f91bc..aa62694 100644 --- a/ogWebconsole/src/app/services/plugin.service.ts +++ b/ogWebconsole/src/app/services/plugin.service.ts @@ -85,6 +85,7 @@ export class PluginService { private configService = inject(ConfigService); private translateService = inject(TranslateService); + private allPlugins: PluginManifest[] = []; private activePlugins: PluginManifest[] = []; private pluginsSubject = new BehaviorSubject([]); public plugins$ = this.pluginsSubject.asObservable(); @@ -147,18 +148,18 @@ export class PluginService { return of([]); } - return this.http.get(`${apiUrl}/plugins`).pipe( + return this.http.get(`${apiUrl}/plugins?all=true`).pipe( catchError(() => of([])), switchMap((plugins) => { - const active = (plugins || []).filter(p => p.enabled); - if (active.length === 0) { + if (!plugins || plugins.length === 0) { + this.allPlugins = []; this.activePlugins = []; this.pluginsSubject.next([]); return of([]); } - // Descubrir y fusionar manifiestos extendidos de cada plugin activo - const manifestRequests = active.map(p => + // Descubrir y fusionar manifiestos extendidos de cada plugin + const manifestRequests = plugins.map(p => this.http.get>(`${apiUrl}/plugins/${p.name}/manifest`).pipe( catchError(() => of({})), map(extra => ({ ...p, ...extra })) @@ -167,8 +168,9 @@ export class PluginService { return forkJoin(manifestRequests).pipe( tap((fullPlugins) => { - this.activePlugins = fullPlugins; - this.pluginsSubject.next(this.activePlugins); + this.allPlugins = fullPlugins; + this.activePlugins = fullPlugins.filter(p => p.enabled); + this.pluginsSubject.next(this.allPlugins); this.loadPluginTranslations(); this.loadActivePluginsStatus().subscribe(); }) @@ -177,6 +179,14 @@ export class PluginService { ); } + togglePlugin(name: string, enabled?: boolean): Observable { + const apiUrl = this.configService.apiUrl; + if (!apiUrl) return of(null); + return this.http.post(`${apiUrl}/plugins/${name}/toggle`, enabled !== undefined ? { enabled } : {}).pipe( + switchMap(() => this.refreshPlugins()) + ); + } + public loadPluginTranslations(lang?: string): void { const currentLang = lang || this.translateService.currentLang || this.translateService.defaultLang || 'es'; const apiUrl = this.configService.apiUrl; @@ -250,6 +260,10 @@ export class PluginService { return this.activePlugins.filter(p => p.enabled); } + getAllPlugins(): PluginManifest[] { + return this.allPlugins; + } + hasPlugins(): boolean { return this.getPlugins().length > 0; } @@ -259,7 +273,7 @@ export class PluginService { } getPlugin(name: string): PluginManifest | undefined { - return this.activePlugins.find(p => p.name === name); + return this.allPlugins.find(p => p.name === name); } getPluginGroups(): PluginGroup[] {