// ─── Scripts Page ──────────────────────────────────────────────────────────── const { useState: uSCS, useEffect: uSCE, useRef: uSCR } = React; function ScriptsPage() { const [scripts, setScripts] = uSCS([]); const [selected, setSelected] = uSCS(null); const [editorOpen, setEditorOpen] = uSCS(false); const [editContent, setEditContent] = uSCS(''); const [editName, setEditName] = uSCS(''); const [editDesc, setEditDesc] = uSCS(''); const [isNew, setIsNew] = uSCS(false); const [runOutput, setRunOutput] = uSCS(null); const [running, setRunning] = uSCS(null); const [filter, setFilter] = uSCS('all'); const [toast, setToast] = uSCS(null); const showToast = (msg, color = '#00ff88') => { setToast({ msg, color }); setTimeout(() => setToast(null), 2500); }; const fetchScripts = () => apiClient('/api/scripts').then(d => { if (d) setScripts(d); }).catch(() => {}); uSCE(() => { fetchScripts(); }, []); const openEdit = async (script) => { try { const data = await apiClient(`/api/scripts/${script.name}`); setSelected(script.name); setEditContent(data.content || ''); setEditName(data.name); setEditDesc(data.desc || ''); setIsNew(false); setEditorOpen(true); } catch (err) { showToast(`Load failed: ${err.message}`, '#ff2244'); } }; const openNew = () => { setSelected(null); setEditContent('#!/bin/bash\n\n'); setEditName('new-script.sh'); setEditDesc(''); setIsNew(true); setEditorOpen(true); }; const saveScript = async () => { try { const method = isNew ? 'POST' : 'PUT'; await apiClient(`/api/scripts/${editName}`, { method, body: JSON.stringify({ content: editContent, desc: editDesc }), }); showToast(`${isNew ? 'Created' : 'Saved'} ${editName}`); setEditorOpen(false); fetchScripts(); } catch (err) { showToast(`Save failed: ${err.message}`, '#ff2244'); } }; const deleteScriptFn = async (name) => { try { await apiClient(`/api/scripts/${name}`, { method: 'DELETE' }); showToast(`Deleted ${name}`, '#ff2244'); fetchScripts(); } catch (err) { showToast(`Delete failed: ${err.message}`, '#ff2244'); } }; const runScript = async (name) => { setRunning(name); setRunOutput({ name, lines: [`[${new Date().toLocaleTimeString('de-DE')}] Starting ${name}...`], done: false }); try { const { run_id } = await apiClient(`/api/scripts/${name}/run`, { method: 'POST' }); // Poll until done const poll = setInterval(async () => { try { const result = await apiClient(`/api/scripts/runs/${run_id}`); const lines = result.output ? result.output.split('\n').filter(Boolean) : []; setRunOutput({ name, lines, done: result.status !== 'running' }); if (result.status !== 'running') { clearInterval(poll); setRunning(null); showToast(`${name} ${result.status === 'success' ? 'completed' : 'failed'}`, result.status === 'success' ? '#00ff88' : '#ff2244'); fetchScripts(); } } catch { clearInterval(poll); setRunning(null); } }, 1500); } catch (err) { setRunning(null); setRunOutput(prev => ({ ...prev, lines: [...(prev?.lines||[]), `ERROR: ${err.message}`], done: true })); } }; const filtered = filter === 'all' ? scripts : scripts.filter(s => s.type === filter || (filter === 'sh' && s.name.endsWith('.sh')) || (filter === 'py' && s.name.endsWith('.py'))); const statusColor = { success: '#00ff88', error: '#ff2244', running: '#ffd700' }; return (
{['all', 'sh', 'py'].map(f => ( ))} + NEW
}>
{filtered.length === 0 && (
NO SCRIPTS FOUND
)} {filtered.map(s => (
e.currentTarget.style.background = 'rgba(0,200,255,0.04)'} onMouseLeave={e => e.currentTarget.style.background = 'transparent'}>
{s.name.endsWith('.py') ? 'PY' : 'SH'}
{s.name}
{s.desc || '—'}
LAST RUN: {s.last_run || '—'} {s.last_status && ● {s.last_status.toUpperCase()}}
runScript(s.name)} disabled={running === s.name}> {running === s.name ? '…' : '▶ RUN'} openEdit(s)}>EDIT deleteScriptFn(s.name)}>DEL
))}
{/* Output Console */}
{!runOutput ? (
SELECT A SCRIPT AND PRESS ▶ RUN
CONSOLE READY_
) : ( <>
── {runOutput.name} ──────────────
{runOutput.lines.map((l, i) => (
{l}
))} {!runOutput.done && } )}
{runOutput?.done && (
setRunOutput(null)}>CLEAR
)}
{/* Editor Modal */} setEditorOpen(false)} title={isNew ? 'NEW SCRIPT' : `EDIT: ${editName}`} width={720}>
CONTENT
setEditorOpen(false)}>CANCEL SAVE
{toast && (
{toast.msg}
)} ); } Object.assign(window, { ScriptsPage });