import { useCallback, useEffect, useRef, useState } from 'react' import { api, Me, Term } from './api' import Rail, { Group } from './Rail' import TerminalPane, { TermHandle } from './TerminalPane' import KeyBar from './KeyBar' import Dashboard from './Dashboard' import Services from './Services' import Cron from './Cron' const GROUPS: Group[] = [ { id: 'dashboard', label: 'Dashboard', icon: '◔', page: true }, { id: 'terminal', label: 'Terminal', icon: '>_' }, { id: 'services', label: 'Services', icon: '⚙', page: true }, { id: 'cron', label: 'Cron', icon: '⏱', page: true }, { id: 'zfs', label: 'ZFS', icon: '▤', soon: true }, ] type View = 'terminal' | 'dashboard' | 'services' | 'cron' function fmtUptime(s: number): string { const d = Math.floor(s / 86400), h = Math.floor((s % 86400) / 3600), m = Math.floor((s % 3600) / 60) return d > 0 ? `${d}d ${h}h` : h > 0 ? `${h}h ${m}m` : `${m}m` } export default function Shell({ me, onLoggedOut }: { me: Me; onLoggedOut: () => void }) { const [terms, setTerms] = useState([]) const [active, setActive] = useState(null) const [view, setView] = useState('terminal') const [drawer, setDrawer] = useState(false) const [online, setOnline] = useState(true) const handles = useRef>({}) const reload = useCallback(async () => { const list = await api.terminals() setTerms(list) setActive((a) => (a && list.some((t) => t.id === a) ? a : list[0]?.id ?? null)) }, []) useEffect(() => { reload() }, [reload]) async function create() { const t = await api.createTerminal() await reload() setActive(t.id) setView('terminal') setDrawer(false) } async function rename(t: Term) { const name = window.prompt('Rename terminal', t.name) if (name && name.trim() && name !== t.name) { await api.renameTerminal(t.id, name.trim()); await reload() } } async function close(t: Term) { if (t.running && !window.confirm(`Close "${t.name}"? The shell will be hung up.`)) return await api.deleteTerminal(t.id) delete handles.current[t.id] await reload() } async function logout() { await api.logout(); onLoggedOut() } const activeTerm = terms.find((t) => t.id === active) const termView = view === 'terminal' return (
Ham Lab {me.user}@{me.host} {me.load}
{ setActive(id); setView('terminal'); setDrawer(false) }} onView={(id) => { setView(id as View); setDrawer(false) }} onNew={create} onRename={rename} onClose={close} />
setDrawer(false)} />
{view === 'dashboard' && } {view === 'services' && } {view === 'cron' && } {termView && terms.length === 0 && (
No terminals.
)} {terms.map((t) => ( { if (h) handles.current[t.id] = h; else delete handles.current[t.id] }} onStatus={(ok) => { if (t.id === active) setOnline(ok) }} onExit={reload} /> ))}
{termView && activeTerm && handles.current[activeTerm.id]?.input(s)} setCtrl={(v) => handles.current[activeTerm.id]?.armCtrl(v)} />}
) }