From 8c6ef5acb8d0bdc48af99fc09801979ca0f468d3 Mon Sep 17 00:00:00 2001 From: Bruno Charest Date: Sat, 14 Mar 2026 00:17:17 -0400 Subject: [PATCH] feat: Create OpenClaw dashboard page with status, filesystem, config, agents, skills, logs, and models tabs. --- frontend/src/pages/OpenClaw.tsx | 410 +++++++++++++++++++++++--------- 1 file changed, 294 insertions(+), 116 deletions(-) diff --git a/frontend/src/pages/OpenClaw.tsx b/frontend/src/pages/OpenClaw.tsx index a83a102..2288147 100644 --- a/frontend/src/pages/OpenClaw.tsx +++ b/frontend/src/pages/OpenClaw.tsx @@ -246,76 +246,81 @@ function formatSize(bytes: number): string { function ConfigTab() { const [config, setConfig] = useState | null>(null); const [loading, setLoading] = useState(true); - const [expandedSections, setExpandedSections] = useState>(new Set(['gateway', 'mcpServers'])); + const [editing, setEditing] = useState(false); - useEffect(() => { + const fetchConfig = () => { + setLoading(true); api.openclawConfig() .then(d => { setConfig(d.config); setLoading(false); }) .catch(() => setLoading(false)); - }, []); + }; + + useEffect(() => { fetchConfig(); }, []); if (loading) return ; if (!config) return ; const sections = Object.entries(config); - const toggleSection = (key: string) => { - setExpandedSections(prev => { - const next = new Set(prev); - next.has(key) ? next.delete(key) : next.add(key); - return next; - }); - }; - return ( -
-
- πŸ“‹ - Lecture de - openclaw.json - β€” {sections.length} sections +
+ {editing && setEditing(false)} onSave={() => { setEditing(false); fetchConfig(); }} />} + + {/* Premium Header */} +
+
+
+

+ βš™οΈ Configuration Globale +

+

+ Paramètres système chargés depuis openclaw.json +

+
+
+ +
- {sections.map(([key, value]) => { - const isObject = typeof value === 'object' && value !== null; - const isExpanded = expandedSections.has(key); - - return ( -
- - {isObject && isExpanded && ( -
-
-                  {JSON.stringify(value, null, 2)}
-                
+
+ {isObject ? ( +
+                    {JSON.stringify(value, null, 2)}
+                  
+ ) : ( +
+ + {renderValue(value)} + +
+ )}
- )} -
- ); - })} +
+ ); + })} +
); } @@ -344,6 +349,7 @@ function AgentsTab() { const [fileContent, setFileContent] = useState(''); const [fileLoading, setFileLoading] = useState(false); const [saving, setSaving] = useState(false); + const [isFullscreen, setIsFullscreen] = useState(false); const loadAgents = () => { setLoading(true); @@ -373,9 +379,11 @@ function AgentsTab() { const openFile = async (agentName: string, filename: string) => { if (activeFile?.agent === agentName && activeFile?.file === filename) { setActiveFile(null); // toggle close + setIsFullscreen(false); return; } setActiveFile({ agent: agentName, file: filename }); + setIsFullscreen(false); setFileLoading(true); try { const data = await api.openclawReadAgentFile(agentName, filename); @@ -429,7 +437,7 @@ function AgentsTab() { >
{ setExpandedAgent(expandedAgent === agent.name ? null : agent.name); setActiveFile(null); }} + onClick={() => { setExpandedAgent(expandedAgent === agent.name ? null : agent.name); setActiveFile(null); setIsFullscreen(false); }} >
@@ -498,27 +506,37 @@ function AgentsTab() { {/* Identity File Editor */} {activeFile?.agent === agent.name && ( -
-
- workspace/{agent.name}/{activeFile.file} -
- +
+
+ workspace/{agent.name}/{activeFile.file} +
+ +
{fileLoading ? ( -
Chargement…
+
Chargement…
) : ( -
- setFileContent(val)} - basicSetup={{ lineNumbers: true, foldGutter: false, highlightActiveLine: true, tabSize: 2 }} - /> +
+
+ setFileContent(val)} + basicSetup={{ lineNumbers: true, foldGutter: false, highlightActiveLine: true, tabSize: 2 }} + /> +
)}
@@ -797,79 +815,123 @@ function LogsTab() { function ModelsTab() { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); + const [editing, setEditing] = useState(false); - useEffect(() => { + const fetchModels = () => { + setLoading(true); api.openclawModels() .then(d => { setData(d); setLoading(false); }) .catch(() => setLoading(false)); - }, []); + }; + + useEffect(() => { fetchModels(); }, []); if (loading) return ; if (!data || data.error) return ; return ( -
- {/* Providers */} - {data.providers.length > 0 && ( -
-

- πŸ”Œ Providers / MCP Servers +
+ {editing && setEditing(false)} onSave={() => { setEditing(false); fetchModels(); }} />} + + {/* Premium Header */} +
+
+
+

+ 🧠 Intelligence Artificielle (LLMs & MCP)

-
+

+ Fournisseurs, serveurs MCP et modèles configurés +

+
+
+ +
+
+ + {/* Providers Grid */} + {data.providers.length > 0 && ( +
+
+

+ πŸ”Œ Providers / MCP Servers +

+
+
{data.providers.map((p, i) => ( -
-
-
- πŸ”Œ +
+
+
+ {p.type === 'mcp' ? '🧱' : 'πŸ’¬'}
-
-

{String(p.name || `Provider ${i + 1}`)}

- {p.type ? {String(p.type)} : null} +
+

+ {String(p.name || `Provider ${i + 1}`)} +

+
+ {Boolean(p.type) && ( + + {String(p.type)} + + )} + {p.enabled !== undefined && ( + + {p.enabled ? 'actif' : 'inactif'} + + )} +
- {p.enabled !== undefined && ( - - {p.enabled ? 'actif' : 'inactif'} - - )}
- {p.model ? ( -
- Modèle : {String(p.model)} + {Boolean(p.model) && ( +
+ modèle: {String(p.model)}
- ) : null} - {p.endpoint ? ( -
+ )} + {Boolean(p.endpoint) && ( +
{String(p.endpoint)}
- ) : null} + )}
))}
)} - {/* Models */} + {/* Models Table */} {data.models.length > 0 && ( -
-

- 🧠 Modèles configurés -

+
+
+

+ πŸ€– ModΓ¨les ConfigurΓ©s +

+
- - - - + + + + - + {data.models.map((m, i) => ( - - + - @@ -883,13 +945,13 @@ function ModelsTab() { {/* Raw config keys */} {data.raw_keys && data.raw_keys.length > 0 && ( -
-

- πŸ”‘ ClΓ©s de configuration dΓ©tectΓ©es +
+

+ πŸ”‘ ClΓ©s racines dΓ©tectΓ©es :

{data.raw_keys.map(k => ( - {k} + {k} ))}
@@ -927,6 +989,111 @@ function EmptyState({ message }: { message: string }) { ); } +// ═════════════════════════════════════════════════════════════════════════════════ +// Shared Config Editor Overlay +// ═════════════════════════════════════════════════════════════════════════════════ + +function ConfigFileEditor({ onClose, onSave }: { onClose: () => void, onSave: () => void }) { + const [content, setContent] = useState(''); + const [loading, setLoading] = useState(true); + const [saving, setSaving] = useState(false); + + useEffect(() => { + // Lock body scroll + document.body.style.overflow = 'hidden'; + + api.openclawReadFile('openclaw.json') + .then(res => { + setContent(res.pretty || res.content || ''); + setLoading(false); + }) + .catch(err => { + console.error(err); + setContent('Erreur de chargement du fichier openclaw.json'); + setLoading(false); + }); + + return () => { + // Restore scroll when unmounting + document.body.style.overflow = ''; + }; + }, []); + + const handleSave = async () => { + setSaving(true); + try { + await api.openclawWriteFile('openclaw.json', content); + onSave(); + } catch (err) { + console.error(err); + alert('Erreur lors de la sauvegarde : ' + String(err)); + } finally { + setSaving(false); + } + }; + + return ( +
+
+
+
+ ✏️ +
+
+

+ Γ‰dition directe +

+
openclaw.json
+
+
+ +
+ + +
+
+ +
+ {loading ? ( +
+ βš™οΈ + Lecture de la configuration brute... +
+ ) : ( +
+ setContent(val)} + basicSetup={{ + lineNumbers: true, + foldGutter: true, + highlightActiveLine: true, + tabSize: 2, + }} + /> +
+ )} +
+
+ ); +} + + // ═════════════════════════════════════════════════════════════════════════════════ // Tab 7 β€” Filesystem (Arborescence) @@ -940,6 +1107,7 @@ function FilesystemTab() { const [editMode, setEditMode] = useState(false); const [editContent, setEditContent] = useState(''); const [saving, setSaving] = useState(false); + const [isFullscreen, setIsFullscreen] = useState(false); useEffect(() => { loadTree(); @@ -1017,7 +1185,10 @@ function FilesystemTab() {

{/* Editor Panel */} -
+
{fileLoading ? (
@@ -1047,6 +1218,13 @@ function FilesystemTab() {
+ {!selectedFile.binary && ( <> {editMode ? ( @@ -1078,7 +1256,7 @@ function FilesystemTab() {
) : editMode ? ( -
+
NomDΓ©tails
Identifiant ModèleDétails configuration (JSON)
- {String(m.name || `Model ${i + 1}`)} +
+
+ 🏷️ + + {String(m.name || `Model ${i + 1}`)} + +
-
+                    
+
                         {JSON.stringify(m, null, 2)}