import { useCallback, useEffect, useRef, useState } from 'react' import { describeCron } from './crontext' import { api, CronJob, CronOverview, CronRun, CronTimer, LogEntry } from './api' import { Logs } from './Services' import { askYes, Btn, CloseBtn, Loading, Spinner, toast } from './ui' import { LIST_FLOOR, useRefreshMs } from './prefs' import { useHashSel } from './hashsel' const fmtTime = (ms: number) => { const d = new Date(ms); return d.toLocaleDateString(undefined, { weekday: 'short', month: 'short', day: '2-digit' }) + ' ' + d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) } function rel(ms: number): string { const s = Math.round((ms - Date.now()) / 1000), a = Math.abs(s) const d = Math.floor(a / 86400), h = Math.floor((a % 86400) / 3600), m = Math.floor((a % 3600) / 60) const txt = d > 0 ? `${d}d ${h}h` : h > 0 ? `${h}h ${m}m` : m > 0 ? `${m}m` : `${a}s` return s >= 0 ? `in ${txt}` : `${txt} ago` } const describe = (expr: string) => describeCron(expr) || expr const srcLabel = (s: string) => (s.startsWith('crontab:') ? `${s.slice(8)}'s crontab` : s) // ---- source editor ---- function SourceEditor({ name, onClose, onSaved }: { name: string; onClose: () => void; onSaved: () => void }) { const [text, setText] = useState(null) const [editable, setEditable] = useState(false) const [err, setErr] = useState('') const [busy, setBusy] = useState(false) useEffect(() => { api.cronSource(name).then((r) => { setText(r.text); setEditable(r.editable) }).catch((e) => setErr(String(e))) }, [name]) async function save() { if (text === null) return setBusy(true); setErr('') try { await api.cronWriteSource(name, text); toast(`${srcLabel(name)} saved`); onSaved(); onClose() } catch (e) { setErr(e instanceof Error ? e.message : 'save failed') } finally { setBusy(false) } } return (

{srcLabel(name)}

{!editable &&
Read-only: writing this file needs passwordless sudo on the host.
} {text === null ? :