import { useEffect, useState } from 'react'; import type { AppConfig, DeployServer, GitServer } from '../api/client'; import { api } from '../api/client'; export default function SettingsPage() { const [config, setConfig] = useState(null); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [message, setMessage] = useState(''); useEffect(() => { api.getConfig().then(c => { setConfig(c); setLoading(false); }).catch(() => setLoading(false)); }, []); async function handleSave(e: React.FormEvent) { e.preventDefault(); if (!config) return; setSaving(true); setMessage(''); try { await api.updateConfig({ FOXY_WORKSPACE: config.FOXY_WORKSPACE, GITEA_SERVER: config.GITEA_SERVER, DEPLOYMENT_SERVER: config.DEPLOYMENT_SERVER, DEPLOYMENT_USER: config.DEPLOYMENT_USER, LOG_LEVEL: config.LOG_LEVEL, }); setMessage('βœ… Configuration sauvegardΓ©e'); } catch (err) { setMessage('❌ ' + (err instanceof Error ? err.message : 'Erreur')); } setSaving(false); } if (loading || !config) { return
🦊
; } const fields: { key: keyof AppConfig; label: string; icon: string; editable: boolean; secret?: boolean }[] = [ { key: 'FOXY_WORKSPACE', label: 'Workspace Foxy (Conteneur)', icon: 'πŸ“', editable: true }, { key: 'GITEA_SERVER', label: 'Serveur Gitea', icon: '🌐', editable: true }, { key: 'GITEA_OPENCLAW_TOKEN', label: 'Token Gitea', icon: 'πŸ”‘', editable: false, secret: true }, { key: 'DEPLOYMENT_SERVER', label: 'Serveur de dΓ©ploiement', icon: 'πŸ–₯️', editable: true }, { key: 'DEPLOYMENT_USER', label: 'Utilisateur dΓ©ploiement', icon: 'πŸ‘€', editable: true }, { key: 'DEPLOYMENT_PWD', label: 'Mot de passe dΓ©ploiement', icon: 'πŸ”’', editable: false, secret: true }, { key: 'TELEGRAM_BOT_TOKEN', label: 'Token Telegram', icon: 'πŸ€–', editable: false, secret: true }, { key: 'TELEGRAM_CHAT_ID', label: 'Chat ID Telegram', icon: 'πŸ’¬', editable: true }, { key: 'LOG_LEVEL', label: 'Niveau de log', icon: 'πŸ“Š', editable: true }, ]; return (

βš™οΈ Configuration

{/* Global config */}

πŸ”§ ParamΓ¨tres gΓ©nΓ©raux

{message && (
{message}
)} {fields.map(f => (
setConfig({ ...config, [f.key]: e.target.value })} disabled={!f.editable} type={f.secret ? 'password' : 'text'} />
))}
{/* Deploy Servers */} {/* Git Servers */} {/* Workflows info */}
); } // ═══════════════════════════════════════════════════════════════════════════════ // Deploy Server Manager // ═══════════════════════════════════════════════════════════════════════════════ function DeployServerManager() { const [servers, setServers] = useState([]); const [showAdd, setShowAdd] = useState(false); const [form, setForm] = useState({ name: '', host: '', user: 'deploy', password: '', ssh_port: 22, description: '' }); const [error, setError] = useState(''); async function fetchServers() { try { setServers(await api.listDeployServers()); } catch { /* ignore */ } } useEffect(() => { fetchServers(); }, []); async function handleAdd(e: React.FormEvent) { e.preventDefault(); setError(''); try { await api.createDeployServer({ ...form, password: form.password || undefined, description: form.description || undefined, }); setForm({ name: '', host: '', user: 'deploy', password: '', ssh_port: 22, description: '' }); setShowAdd(false); fetchServers(); } catch (err) { setError(err instanceof Error ? err.message : 'Erreur'); } } async function handleDelete(id: number, name: string) { if (!confirm(`Supprimer le serveur "${name}" ?`)) return; try { await api.deleteDeployServer(id); fetchServers(); } catch (err) { alert(err instanceof Error ? err.message : 'Erreur'); } } return (

πŸ–₯️ Serveurs de dΓ©ploiement

{/* Add form */} {showAdd && (
{error &&
{error}
}
setForm({...form, name: e.target.value})} required placeholder="prod-server" />
setForm({...form, host: e.target.value})} required placeholder="192.168.1.100" />
setForm({...form, user: e.target.value})} placeholder="deploy" />
setForm({...form, password: e.target.value})} placeholder="(optionnel)" />
setForm({...form, ssh_port: Number(e.target.value)})} />
setForm({...form, description: e.target.value})} placeholder="Serveur production" />
)} {/* Server list */} {servers.length === 0 ? (

Aucun serveur de dΓ©ploiement configurΓ©.

) : (
{servers.map(s => (
{s.name} {s.user}@{s.host}:{s.ssh_port} {s.description && β€” {s.description}}
))}
)}
); } // ═══════════════════════════════════════════════════════════════════════════════ // Git Server Manager // ═══════════════════════════════════════════════════════════════════════════════ function GitServerManager() { const [servers, setServers] = useState([]); const [showAdd, setShowAdd] = useState(false); const [form, setForm] = useState({ name: '', url: '', token: '', org: 'openclaw', description: '' }); const [error, setError] = useState(''); async function fetchServers() { try { setServers(await api.listGitServers()); } catch { /* ignore */ } } useEffect(() => { fetchServers(); }, []); async function handleAdd(e: React.FormEvent) { e.preventDefault(); setError(''); try { await api.createGitServer({ ...form, token: form.token || undefined, description: form.description || undefined, }); setForm({ name: '', url: '', token: '', org: 'openclaw', description: '' }); setShowAdd(false); fetchServers(); } catch (err) { setError(err instanceof Error ? err.message : 'Erreur'); } } async function handleDelete(id: number, name: string) { if (!confirm(`Supprimer le serveur Git "${name}" ?`)) return; try { await api.deleteGitServer(id); fetchServers(); } catch (err) { alert(err instanceof Error ? err.message : 'Erreur'); } } return (

🌐 Serveurs Git

{/* Add form */} {showAdd && (
{error &&
{error}
}
setForm({...form, name: e.target.value})} required placeholder="gitea-local" />
setForm({...form, url: e.target.value})} required placeholder="https://git.example.com" />
setForm({...form, org: e.target.value})} placeholder="openclaw" />
setForm({...form, token: e.target.value})} placeholder="(optionnel)" />
setForm({...form, description: e.target.value})} placeholder="Gitea local" />
)} {/* Server list */} {servers.length === 0 ? (

Aucun serveur Git configurΓ©.

) : (
{servers.map(s => (
{s.name} {s.url} (org: {s.org}) {s.description && β€” {s.description}}
))}
)}
); } // ═══════════════════════════════════════════════════════════════════════════════ // Workflows Info // ═══════════════════════════════════════════════════════════════════════════════ function WorkflowsInfo() { const [workflows, setWorkflows] = useState<{ type: string; label: string; path: string; steps: { agent: string; model: string }[] }[]>([]); useEffect(() => { api.listWorkflows().then(setWorkflows).catch(() => {}); }, []); if (workflows.length === 0) return null; return (

πŸ”„ Workflows disponibles

{workflows.map(wf => (

{wf.label}

{wf.path}

{wf.steps.map((s, i) => (
{s.agent} {i < wf.steps.length - 1 && β†’}
))}
))}
); }