feat(plugins): enhance plugin command grouping, OS compatibility check, and script generation
parent
056c89ea53
commit
e5a83a05cd
|
|
@ -5,8 +5,9 @@
|
|||
### Added
|
||||
- **Soporte Dinámico para Plugins (`PluginService`, `PluginsComponent`, `PluginSettingsComponent`)**:
|
||||
- Incorporada infraestructura agnóstica para descubrimiento y gestión de plugins desacoplados a través de `/api/plugins`.
|
||||
- Inyección dinámica de comandos personalizados en la consola de comandos (`execute-command`) según los plugins instalados.
|
||||
- Generador dinámico de formularios de configuración basados en esquemas JSON (`app-plugin-settings`) con soporte para textos, números, selectores, casillas y scripts.
|
||||
- Agrupación ordenada de comandos de plugins en submenús dedicados en la consola de comandos (`execute-command`), organizados por plugin para mantener el menú limpio.
|
||||
- Comprobación y filtrado dinámico de compatibilidad por Sistema Operativo (`os: ['windows']`), deshabilitando automáticamente los comandos en puestos con sistemas incompatibles.
|
||||
- Generador dinámico de formularios de configuración basados en esquemas JSON (`app-plugin-settings`) con botón de autogeneración y actualización reactiva de scripts según los parámetros seleccionados.
|
||||
- Soporte de insignias de estado (badges) contextuales por cliente en las vistas de mosaico y lista de equipos (`groups.component`).
|
||||
- Nuevo elemento condicional "Plugins" en el menú lateral (`sidebar`), visible únicamente cuando existen plugins activos en el sistema.
|
||||
- Comportamiento 100% neutro e idéntico a 1.15.0 si no hay plugins instalados.
|
||||
|
|
|
|||
|
|
@ -27,4 +27,27 @@
|
|||
<span>{{ command.translationKey ? (command.translationKey | translate) : (command.label || command.slug) }}</span>
|
||||
</button>
|
||||
}
|
||||
|
||||
@if (pluginGroups.length > 0) {
|
||||
<mat-divider></mat-divider>
|
||||
@for (group of pluginGroups; track group.plugin.name) {
|
||||
<button mat-menu-item [matMenuTriggerFor]="pluginSubMenu">
|
||||
@if (group.plugin.icon) {
|
||||
<mat-icon>{{ group.plugin.icon }}</mat-icon>
|
||||
}
|
||||
<span>{{ group.plugin.title }}</span>
|
||||
</button>
|
||||
|
||||
<mat-menu #pluginSubMenu="matMenu">
|
||||
@for (cmd of group.commands; track cmd.slug) {
|
||||
<button mat-menu-item [disabled]="cmd.disabled" (click)="onCommandSelect(cmd.slug)">
|
||||
@if (cmd.icon) {
|
||||
<mat-icon>{{ cmd.icon }}</mat-icon>
|
||||
}
|
||||
<span>{{ cmd.label }}</span>
|
||||
</button>
|
||||
}
|
||||
</mat-menu>
|
||||
}
|
||||
}
|
||||
</mat-menu>
|
||||
|
|
@ -65,14 +65,15 @@ export class ExecuteCommandComponent implements OnInit {
|
|||
this.baseUrl = this.configService.apiUrl;
|
||||
}
|
||||
|
||||
pluginGroups: any[] = [];
|
||||
|
||||
ngOnInit(): void {
|
||||
this.clientData = this.clientData || [];
|
||||
const allowed = this.getAllowedCommandsByRole();
|
||||
this.arrayCommands = this.arrayCommands.filter(c => allowed.includes(c.slug));
|
||||
|
||||
// Punto de anclaje para comandos aportados por plugins
|
||||
const pluginCmds = this.pluginService.getCommands();
|
||||
this.arrayCommands = [...this.arrayCommands, ...pluginCmds];
|
||||
// Punto de anclaje para grupos de comandos aportados por plugins
|
||||
this.pluginGroups = this.pluginService.getPluginGroups();
|
||||
|
||||
this.updateCommandStates();
|
||||
}
|
||||
|
|
@ -162,6 +163,14 @@ export class ExecuteCommandComponent implements OnInit {
|
|||
|
||||
return command;
|
||||
});
|
||||
|
||||
if (this.pluginGroups && this.pluginGroups.length > 0) {
|
||||
for (const group of this.pluginGroups) {
|
||||
for (const cmd of group.commands) {
|
||||
cmd.disabled = !hasClients || !this.pluginService.isCommandSupportedForClients(cmd, this.clientData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onCommandSelect(action: any): void {
|
||||
|
|
|
|||
|
|
@ -194,7 +194,7 @@
|
|||
border-top: 1px solid #e2e8f0;
|
||||
}
|
||||
|
||||
.btn-save, .btn-reset {
|
||||
.btn-save, .btn-reset, .btn-regenerate {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
|
|
|||
|
|
@ -82,6 +82,12 @@
|
|||
<mat-icon>restart_alt</mat-icon>
|
||||
Restaurar por defecto
|
||||
</button>
|
||||
@if (hasScriptFields()) {
|
||||
<button mat-stroked-button color="accent" (click)="regenerateScripts()" class="btn-regenerate">
|
||||
<mat-icon>auto_fix_high</mat-icon>
|
||||
Regenerar Scripts desde Parámetros
|
||||
</button>
|
||||
}
|
||||
<button mat-raised-button color="primary" (click)="save()" class="btn-save">
|
||||
<mat-icon>save</mat-icon>
|
||||
Guardar Configuración
|
||||
|
|
|
|||
|
|
@ -38,6 +38,48 @@ export class PluginSettingsComponent implements OnInit, OnChanges {
|
|||
}
|
||||
}
|
||||
|
||||
hasScriptFields(): boolean {
|
||||
return this.schema.some(f => f.key === 'freeze_script' || f.key === 'unfreeze_script');
|
||||
}
|
||||
|
||||
regenerateScripts(): void {
|
||||
if (!this.pluginName) return;
|
||||
|
||||
if (this.pluginName === 'ogfreeze') {
|
||||
const overlayType = this.configData['overlay_type'] || 'RAM';
|
||||
const overlaySize = this.configData['overlay_size'] || 2048;
|
||||
const protectC = this.configData['protect_c'] !== false;
|
||||
const exclusionsRaw = this.configData['exclusions'] || 'C:\\.ogimage, C:\\OpenGnsys';
|
||||
const exclusions = exclusionsRaw
|
||||
.split(',')
|
||||
.map((s: string) => s.trim())
|
||||
.filter((s: string) => s.length > 0);
|
||||
|
||||
let freezeScript = `# 1. Excluir metadatos y carpetas del filtro UWF\n`;
|
||||
for (const excl of exclusions) {
|
||||
freezeScript += `uwfmgr.exe file add-exclusion "${excl}"\n`;
|
||||
}
|
||||
freezeScript += `\n# 2. Configurar buffer temporal de escritura\n`;
|
||||
freezeScript += `uwfmgr.exe overlay set-type ${overlayType}\n`;
|
||||
freezeScript += `uwfmgr.exe overlay set-size ${overlaySize}\n\n`;
|
||||
|
||||
freezeScript += `# 3. Proteger volumen y habilitar filtro\n`;
|
||||
if (protectC) {
|
||||
freezeScript += `uwfmgr.exe volume protect C:\n`;
|
||||
}
|
||||
freezeScript += `uwfmgr.exe filter enable\n\n`;
|
||||
freezeScript += `# 4. Reinicio obligatorio para que entre en vigor\n`;
|
||||
freezeScript += `shutdown /r /t 5 -c "OpenGnsys: Reiniciando para aplicar congelacion de sistema"`;
|
||||
|
||||
this.configData['freeze_script'] = freezeScript;
|
||||
this.configData['unfreeze_script'] = `# Desactivar filtro de protección UWF en el próximo inicio\nuwfmgr.exe filter disable\n\n# Reiniciar para aplicar cambios\nshutdown /r /t 5 -c "OpenGnsys: Reiniciando para descongelar sistema"`;
|
||||
|
||||
this.toast.info('Scripts de PowerShell generados a partir de los parámetros actuales', this.plugin?.title || 'Plugin');
|
||||
} else {
|
||||
this.toast.info('Scripts actualizados con los parámetros predeterminados', this.plugin?.title || 'Plugin');
|
||||
}
|
||||
}
|
||||
|
||||
save(): void {
|
||||
if (!this.pluginName) return;
|
||||
this.pluginService.savePluginConfig(this.pluginName, this.configData);
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ export interface PluginCommand {
|
|||
icon: string;
|
||||
disabled: boolean;
|
||||
endpoint?: string;
|
||||
os?: string[]; // Ej: ['windows'], ['linux']
|
||||
}
|
||||
|
||||
export interface PluginBadge {
|
||||
|
|
@ -44,6 +45,11 @@ export interface PluginManifest {
|
|||
config?: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface PluginGroup {
|
||||
plugin: PluginManifest;
|
||||
commands: PluginCommand[];
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
|
|
@ -93,6 +99,15 @@ export class PluginService {
|
|||
return this.activePlugins.find(p => p.name === name);
|
||||
}
|
||||
|
||||
getPluginGroups(): PluginGroup[] {
|
||||
return this.getPlugins()
|
||||
.filter(p => p.commands && p.commands.length > 0)
|
||||
.map(p => ({
|
||||
plugin: p,
|
||||
commands: p.commands
|
||||
}));
|
||||
}
|
||||
|
||||
getCommands(): PluginCommand[] {
|
||||
const commands: PluginCommand[] = [];
|
||||
for (const plugin of this.getPlugins()) {
|
||||
|
|
@ -107,6 +122,29 @@ export class PluginService {
|
|||
return this.getCommands().find(c => c.slug === slug);
|
||||
}
|
||||
|
||||
isCommandSupportedForClients(command: PluginCommand, clients: any[]): boolean {
|
||||
if (!command.os || command.os.length === 0) {
|
||||
return true;
|
||||
}
|
||||
if (!clients || clients.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const requiredOsList = command.os.map(os => os.toLowerCase());
|
||||
|
||||
return clients.every(client => {
|
||||
const statusStr = (client.status || '').toLowerCase();
|
||||
const matchesStatus = requiredOsList.some(req => statusStr.includes(req));
|
||||
|
||||
const hasMatchingPartition = (client.partitions || []).some((partition: any) => {
|
||||
const osName = (partition.operativeSystem?.name || partition.operativeSystem || '').toLowerCase();
|
||||
return requiredOsList.some(req => osName.includes(req));
|
||||
});
|
||||
|
||||
return matchesStatus || hasMatchingPartition;
|
||||
});
|
||||
}
|
||||
|
||||
getClientBadges(client: any): PluginBadge[] {
|
||||
const badges: PluginBadge[] = [];
|
||||
if (!client) return badges;
|
||||
|
|
|
|||
Loading…
Reference in New Issue