Advanced pages. Model-driven controls.

This commit is contained in:
2026-07-11 02:31:14 +00:00
parent cd2f28df34
commit 1f3fcf50b4
23 changed files with 1600 additions and 444 deletions

View File

@@ -0,0 +1,145 @@
import type { LucideIcon } from 'lucide-react';
import { Card, CardContent } from './ui/card';
import { Button } from './ui/button';
import { Alert, AlertDescription } from './ui/alert';
import { Badge } from './ui/badge';
import { Textarea } from './ui/textarea';
import { Loader2, CheckCircle, AlertCircle, Play, RotateCw } from 'lucide-react';
import { useCentralStatus } from '../hooks/useCentralStatus';
interface MetadataItem {
label: string;
value: string | number | undefined;
}
interface SubsystemPageProps {
title: string;
description: string;
icon: LucideIcon;
daemonKey: string;
configContent?: string;
isLoadingConfig?: boolean;
rawStatusLabel?: string;
rawStatusContent?: string;
onGenerate?: () => void;
isGenerating?: boolean;
generateLabel?: string;
onRestart?: () => void;
isRestarting?: boolean;
restartLabel?: string;
successMessage?: string | null;
errorMessage?: string | null;
metadata?: MetadataItem[];
}
export function SubsystemPage({
title, description, icon: Icon, daemonKey,
configContent, isLoadingConfig,
rawStatusLabel, rawStatusContent,
onGenerate, isGenerating, generateLabel = 'Generate & Apply',
onRestart, isRestarting, restartLabel = 'Restart',
successMessage, errorMessage,
metadata,
}: SubsystemPageProps) {
const { data: centralStatus } = useCentralStatus();
const active = centralStatus?.daemons?.[daemonKey]?.active;
return (
<div className="space-y-4">
{/* Header */}
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-4">
<div className="p-2 bg-primary/10 rounded-lg">
<Icon className="h-6 w-6 text-primary" />
</div>
<div>
<h2 className="text-2xl font-semibold">{title}</h2>
<p className="text-muted-foreground">{description}</p>
</div>
</div>
{active !== undefined && (
<Badge variant={active ? 'success' : 'destructive'} className="gap-1">
{active ? <CheckCircle className="h-3 w-3" /> : <AlertCircle className="h-3 w-3" />}
{active ? 'Active' : 'Inactive'}
</Badge>
)}
</div>
{/* Alerts */}
{successMessage && (
<Alert className="border-green-200 bg-green-50 dark:border-green-800 dark:bg-green-950/20">
<CheckCircle className="h-4 w-4 text-green-600" />
<AlertDescription className="text-green-700 dark:text-green-300">{successMessage}</AlertDescription>
</Alert>
)}
{errorMessage && (
<Alert variant="error">
<AlertCircle className="h-4 w-4" />
<AlertDescription>{errorMessage}</AlertDescription>
</Alert>
)}
{/* Actions + Metadata */}
{(onGenerate || onRestart || metadata?.length) && (
<Card>
<CardContent className="p-4 space-y-4">
{(onGenerate || onRestart) && (
<div className="flex gap-2">
{onGenerate && (
<Button onClick={onGenerate} disabled={isGenerating} size="sm" className="gap-1">
{isGenerating ? <Loader2 className="h-3 w-3 animate-spin" /> : <Play className="h-3 w-3" />}
{generateLabel}
</Button>
)}
{onRestart && (
<Button variant="outline" onClick={onRestart} disabled={isRestarting} size="sm" className="gap-1">
{isRestarting ? <Loader2 className="h-3 w-3 animate-spin" /> : <RotateCw className="h-3 w-3" />}
{restartLabel}
</Button>
)}
</div>
)}
{metadata && metadata.length > 0 && (
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 text-sm">
{metadata.map(m => (
<div key={m.label}>
<span className="text-muted-foreground">{m.label}</span>
<div className="font-mono mt-0.5">{m.value ?? '--'}</div>
</div>
))}
</div>
)}
</CardContent>
</Card>
)}
{/* Config */}
{isLoadingConfig ? (
<Card className="p-8 text-center">
<Loader2 className="h-8 w-8 text-primary mx-auto mb-2 animate-spin" />
<p className="text-sm text-muted-foreground">Loading configuration...</p>
</Card>
) : configContent !== undefined ? (
<Textarea value={configContent} readOnly className="font-mono text-xs min-h-[300px]" />
) : null}
{/* Raw status */}
{rawStatusLabel && rawStatusContent && (
<Card>
<CardContent className="p-4 space-y-2">
<span className="text-sm font-medium">{rawStatusLabel}</span>
<pre className="bg-muted p-3 rounded-lg overflow-x-auto text-xs font-mono">
{rawStatusContent}
</pre>
</CardContent>
</Card>
)}
</div>
);
}