import { useState, useMemo, useCallback } from 'react'; import { useNavigate } from 'react-router'; import { DataTable, Badge, MonoText, useToast } from '@cameleer/design-system'; import type { Column } from '@cameleer/design-system'; import { useAllApplicationConfigs, useUpdateApplicationConfig } from '../../api/queries/commands'; import type { ApplicationConfig } from '../../api/queries/commands'; import styles from './AppConfigPage.module.css'; type ConfigRow = ApplicationConfig & { id: string }; function timeAgo(iso?: string): string { if (!iso) return '\u2014'; const diff = Date.now() - new Date(iso).getTime(); const secs = Math.floor(diff / 1000); if (secs < 60) return `${secs}s ago`; const mins = Math.floor(secs / 60); if (mins < 60) return `${mins}m ago`; const hours = Math.floor(mins / 60); if (hours < 24) return `${hours}h ago`; return `${Math.floor(hours / 24)}d ago`; } type BadgeColor = 'primary' | 'success' | 'warning' | 'error' | 'running' | 'auto'; function logLevelColor(level?: string): BadgeColor { switch (level?.toUpperCase()) { case 'ERROR': return 'error'; case 'WARN': return 'warning'; case 'DEBUG': return 'running'; default: return 'success'; } } function engineLevelColor(level?: string): BadgeColor { switch (level?.toUpperCase()) { case 'NONE': return 'error'; case 'MINIMAL': return 'warning'; case 'COMPLETE': return 'running'; default: return 'success'; } } function payloadColor(mode?: string): BadgeColor { switch (mode?.toUpperCase()) { case 'INPUT': case 'OUTPUT': return 'warning'; case 'BOTH': return 'running'; default: return 'auto'; } } export default function AppConfigPage() { const navigate = useNavigate(); const { toast } = useToast(); const { data: configs } = useAllApplicationConfigs(); const updateConfig = useUpdateApplicationConfig(); const [editingApp, setEditingApp] = useState(null); const [draft, setDraft] = useState>({}); const startEditing = useCallback((row: ConfigRow) => { setEditingApp(row.application); setDraft({ logForwardingLevel: row.logForwardingLevel ?? 'INFO', engineLevel: row.engineLevel ?? 'REGULAR', payloadCaptureMode: row.payloadCaptureMode ?? 'NONE', metricsEnabled: row.metricsEnabled, }); }, []); const cancelEditing = useCallback(() => { setEditingApp(null); setDraft({}); }, []); const saveEditing = useCallback((row: ConfigRow) => { const updated = { ...row, ...draft }; updateConfig.mutate(updated, { onSuccess: (saved) => { setEditingApp(null); setDraft({}); toast({ title: 'Config updated', description: `${row.application} (v${saved.version})`, variant: 'success' }); }, onError: () => { toast({ title: 'Config update failed', description: row.application, variant: 'error' }); }, }); }, [draft, updateConfig, toast]); const columns: Column[] = useMemo(() => [ { key: '_inspect', header: '', width: '36px', render: (_val, row) => ( ), }, { key: '_edit', header: '', width: '36px', render: (_val, row) => { const isEditing = editingApp === row.application; return isEditing ? ( ) : ( ); }, }, { key: 'application', header: 'Application', sortable: true, render: (_val, row) => {row.application}, }, { key: 'logForwardingLevel', header: 'Log Level', render: (_val, row) => { const val = row.logForwardingLevel ?? 'INFO'; if (editingApp === row.application) { return ( ); } return ; }, }, { key: 'engineLevel', header: 'Engine Level', render: (_val, row) => { const val = row.engineLevel ?? 'REGULAR'; if (editingApp === row.application) { return ( ); } return ; }, }, { key: 'payloadCaptureMode', header: 'Payload Capture', render: (_val, row) => { const val = row.payloadCaptureMode ?? 'NONE'; if (editingApp === row.application) { return ( ); } return ; }, }, { key: 'metricsEnabled', header: 'Metrics', width: '80px', render: (_val, row) => { if (editingApp === row.application) { return ( ); } return ; }, }, { key: 'tracedProcessors', header: 'Traced', width: '70px', render: (_val, row) => { const count = row.tracedProcessors ? Object.keys(row.tracedProcessors).length : 0; return count > 0 ? : 0; }, }, { key: 'version', header: 'v', width: '40px', render: (_val, row) => {row.version}, }, { key: 'updatedAt', header: 'Updated', render: (_val, row) => {timeAgo(row.updatedAt)}, }, ], [navigate, editingApp, draft, startEditing, cancelEditing, saveEditing, updateConfig.isPending]); return (
columns={columns} data={(configs ?? []).map(c => ({ ...c, id: c.application }))} pageSize={50} />
); }