feat: granular subsystem commands with parallel scanner execution

Split monolithic scan_updates into individual subsystems (updates/storage/system/docker).
Scanners now run in parallel via goroutines - cuts scan time roughly in half, maybe more.

Agent changes:
- Orchestrator pattern for scanner management
- New scanners: storage (disk metrics), system (cpu/mem/processes)
- New commands: scan_storage, scan_system, scan_docker
- Wrapped existing scanners (APT/DNF/Docker/Windows/Winget) with common interface
- Version bump to 0.1.20

Server changes:
- Migration 015: agent_subsystems table with trigger for auto-init
- Subsystem CRUD: enable/disable, interval (5min-24hr), auto-run toggle
- API routes: /api/v1/agents/:id/subsystems/* (9 endpoints)
- Stats tracking per subsystem

Web UI changes:
- ChatTimeline shows subsystem-specific labels and icons
- AgentScanners got interactive toggles, interval dropdowns, manual trigger buttons
- TypeScript types added for subsystems

Backward compatible with legacy scan_updates - for now. Bugs probably exist somewhere.
This commit is contained in:
Fimeg
2025-11-01 20:34:00 -04:00
parent bf4d46529f
commit 3690472396
19 changed files with 2151 additions and 253 deletions

View File

@@ -21,7 +21,10 @@ import {
RateLimitConfig,
RateLimitStats,
RateLimitUsage,
RateLimitSummary
RateLimitSummary,
AgentSubsystem,
SubsystemConfig,
SubsystemStats
} from '@/types';
// Base URL for API - use nginx proxy
@@ -111,6 +114,52 @@ export const agentApi = {
unregisterAgent: async (id: string): Promise<void> => {
await api.delete(`/agents/${id}`);
},
// Subsystem Management
getSubsystems: async (agentId: string): Promise<AgentSubsystem[]> => {
const response = await api.get(`/agents/${agentId}/subsystems`);
return response.data;
},
getSubsystem: async (agentId: string, subsystem: string): Promise<AgentSubsystem> => {
const response = await api.get(`/agents/${agentId}/subsystems/${subsystem}`);
return response.data;
},
updateSubsystem: async (agentId: string, subsystem: string, config: SubsystemConfig): Promise<{ message: string }> => {
const response = await api.patch(`/agents/${agentId}/subsystems/${subsystem}`, config);
return response.data;
},
enableSubsystem: async (agentId: string, subsystem: string): Promise<{ message: string }> => {
const response = await api.post(`/agents/${agentId}/subsystems/${subsystem}/enable`);
return response.data;
},
disableSubsystem: async (agentId: string, subsystem: string): Promise<{ message: string }> => {
const response = await api.post(`/agents/${agentId}/subsystems/${subsystem}/disable`);
return response.data;
},
triggerSubsystem: async (agentId: string, subsystem: string): Promise<{ message: string; command_id: string }> => {
const response = await api.post(`/agents/${agentId}/subsystems/${subsystem}/trigger`);
return response.data;
},
getSubsystemStats: async (agentId: string, subsystem: string): Promise<SubsystemStats> => {
const response = await api.get(`/agents/${agentId}/subsystems/${subsystem}/stats`);
return response.data;
},
setSubsystemAutoRun: async (agentId: string, subsystem: string, autoRun: boolean): Promise<{ message: string }> => {
const response = await api.post(`/agents/${agentId}/subsystems/${subsystem}/auto-run`, { auto_run: autoRun });
return response.data;
},
setSubsystemInterval: async (agentId: string, subsystem: string, intervalMinutes: number): Promise<{ message: string }> => {
const response = await api.post(`/agents/${agentId}/subsystems/${subsystem}/interval`, { interval_minutes: intervalMinutes });
return response.data;
},
};
export const updateApi = {