feat: implement Phase 4 - Configuration Management
All checks were successful
Build and Push Docker Image to Gitea / build-and-push (push) Successful in 59s
All checks were successful
Build and Push Docker Image to Gitea / build-and-push (push) Successful in 59s
Features added: - View all process configurations in sortable table - Reload supervisord configuration with confirmation - Add new process groups dynamically - Remove process groups with confirmation - Configuration auto-refresh every 10 seconds Implementation details: - Created config API routes: /api/supervisor/config (GET), /api/supervisor/config/reload (POST), /api/supervisor/config/group (POST/DELETE) - Added React Query hooks: useConfig, useReloadConfig, useAddProcessGroup, useRemoveProcessGroup - Created components: - ConfigTable: Sortable table with columns for group, name, command, directory, autostart, priority, numprocs - ReloadConfigButton: Reload config with confirmation dialog - ProcessGroupForm: Add/remove groups with separate forms Configuration page features: - Displays all process configurations in sortable table - Click column headers to sort (ascending/descending) - Visual indicators for autostart (green dot = enabled) - Shows command in monospace code blocks - Process group management forms - Reload configuration button in header Data displayed per process: - Group name - Process name - Command (with syntax highlighting) - Working directory - Autostart enabled/disabled - Priority value - Number of processes (numprocs) Phase 4 complete (8-10 hours estimated)
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { toast } from 'sonner';
|
||||
import type { ProcessInfo, SystemInfo, LogTailResult } from '@/lib/supervisor/types';
|
||||
import type { ProcessInfo, SystemInfo, LogTailResult, ConfigInfo } from '@/lib/supervisor/types';
|
||||
|
||||
// Query Keys
|
||||
export const supervisorKeys = {
|
||||
@@ -12,6 +12,7 @@ export const supervisorKeys = {
|
||||
process: (name: string) => [...supervisorKeys.processes(), name] as const,
|
||||
logs: (name: string, type: 'stdout' | 'stderr') =>
|
||||
[...supervisorKeys.process(name), 'logs', type] as const,
|
||||
config: () => [...supervisorKeys.all, 'config'] as const,
|
||||
};
|
||||
|
||||
// API Client Functions
|
||||
@@ -456,3 +457,104 @@ export function useRestartAllProcesses() {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Configuration Management
|
||||
|
||||
async function fetchConfig(): Promise<ConfigInfo[]> {
|
||||
const response = await fetch('/api/supervisor/config');
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error.error || 'Failed to fetch configuration');
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
async function reloadConfig(): Promise<{ success: boolean; message: string; result: any }> {
|
||||
const response = await fetch('/api/supervisor/config/reload', {
|
||||
method: 'POST',
|
||||
});
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error.error || 'Failed to reload configuration');
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
async function addProcessGroup(name: string): Promise<{ success: boolean; message: string }> {
|
||||
const response = await fetch('/api/supervisor/config/group', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name }),
|
||||
});
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error.error || 'Failed to add process group');
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
async function removeProcessGroup(name: string): Promise<{ success: boolean; message: string }> {
|
||||
const response = await fetch('/api/supervisor/config/group', {
|
||||
method: 'DELETE',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name }),
|
||||
});
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error.error || 'Failed to remove process group');
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
export function useConfig() {
|
||||
return useQuery({
|
||||
queryKey: supervisorKeys.config(),
|
||||
queryFn: fetchConfig,
|
||||
refetchInterval: 10000, // Refetch every 10 seconds
|
||||
});
|
||||
}
|
||||
|
||||
export function useReloadConfig() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: reloadConfig,
|
||||
onSuccess: (data) => {
|
||||
toast.success(data.message);
|
||||
queryClient.invalidateQueries({ queryKey: supervisorKeys.all });
|
||||
},
|
||||
onError: (error: Error) => {
|
||||
toast.error(`Failed to reload configuration: ${error.message}`);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useAddProcessGroup() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (name: string) => addProcessGroup(name),
|
||||
onSuccess: (data) => {
|
||||
toast.success(data.message);
|
||||
queryClient.invalidateQueries({ queryKey: supervisorKeys.all });
|
||||
},
|
||||
onError: (error: Error) => {
|
||||
toast.error(`Failed to add process group: ${error.message}`);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useRemoveProcessGroup() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (name: string) => removeProcessGroup(name),
|
||||
onSuccess: (data) => {
|
||||
toast.success(data.message);
|
||||
queryClient.invalidateQueries({ queryKey: supervisorKeys.all });
|
||||
},
|
||||
onError: (error: Error) => {
|
||||
toast.error(`Failed to remove process group: ${error.message}`);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user