121 lines
4.7 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useEffect, useState } from 'react';
import type { AppConfig } from '../api/client';
import { api } from '../api/client';
export default function SettingsPage() {
const [config, setConfig] = useState<AppConfig | null>(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({
OPENCLAW_WORKSPACE: config.OPENCLAW_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 <div className="flex items-center justify-center h-64"><div className="text-fox-500 animate-spin-slow text-4xl">🦊</div></div>;
}
const fields: { key: keyof AppConfig; label: string; icon: string; editable: boolean; secret?: boolean }[] = [
{ key: 'OPENCLAW_WORKSPACE', label: 'Workspace OpenClaw', 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 (
<div className="space-y-6 animate-fade-in">
<h1 className="text-xl font-bold text-white"> Configuration</h1>
<form onSubmit={handleSave} className="glass-card p-6 space-y-4">
{message && (
<div className={`text-sm p-3 rounded-lg ${message.startsWith('✅') ? 'bg-green-500/10 text-green-400' : 'bg-red-500/10 text-red-400'}`}>
{message}
</div>
)}
{fields.map(f => (
<div key={f.key}>
<label className="text-xs text-gray-400 uppercase tracking-wider mb-1 flex items-center gap-2">
<span>{f.icon}</span> {f.label}
{f.secret && <span className="text-fox-500 text-[10px]">(protégé)</span>}
</label>
<input
className="input"
value={config[f.key]}
onChange={e => setConfig({ ...config, [f.key]: e.target.value })}
disabled={!f.editable}
type={f.secret ? 'password' : 'text'}
/>
</div>
))}
<div className="pt-2">
<button type="submit" className="btn btn-primary" disabled={saving}>
{saving ? '⏳ Sauvegarde...' : '💾 Sauvegarder'}
</button>
</div>
</form>
{/* Workflows info */}
<WorkflowsInfo />
</div>
);
}
function WorkflowsInfo() {
const [workflows, setWorkflows] = useState<{ type: string; label: string; steps: { agent: string; model: string }[] }[]>([]);
useEffect(() => {
api.listWorkflows().then(setWorkflows).catch(() => {});
}, []);
if (workflows.length === 0) return null;
return (
<div className="glass-card p-6">
<h2 className="text-lg font-bold text-white mb-4">🔄 Workflows disponibles</h2>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
{workflows.map(wf => (
<div key={wf.type} className="p-4 rounded-xl bg-surface-800/50">
<h3 className="text-white font-semibold mb-3">{wf.label}</h3>
<div className="flex items-center gap-1 flex-wrap">
{wf.steps.map((s, i) => (
<div key={i} className="flex items-center gap-1">
<span className="badge bg-fox-600/15 text-fox-400 text-[10px]">{s.agent}</span>
{i < wf.steps.length - 1 && <span className="text-gray-600"></span>}
</div>
))}
</div>
</div>
))}
</div>
</div>
);
}