Quality of life: in-app dialogs, selections in the URL, editable widgets, token dialog, gallery search, status history, stream errors that say why, tab reordering and shortcuts, log out everywhere

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Mitchell-Johnson
2026-09-19 10:34:59 -07:00
co-authored by Claude Fable 5.1
parent 8ee2a2d293
commit d7ed4d8549
24 changed files with 361 additions and 101 deletions
+5 -3
View File
@@ -1,7 +1,8 @@
import { useCallback, useEffect, useRef, useState } from 'react'
import { api, LogEntry, ServiceDetail, ServiceUnit, wsURLPath } from './api'
import { Btn, Loading, Spinner, toast } from './ui'
import { Btn, Loading, Spinner, askYes, toast } from './ui'
import { LIST_FLOOR, useRefreshMs } from './prefs'
import { useHashSel } from './hashsel'
type Filter = 'all' | 'running' | 'failed' | 'enabled'
@@ -118,7 +119,7 @@ function Detail({ name, onChanged, onBack }: { name: string; onChanged: () => vo
async function act(action: string) {
const confirmFor: Record<string, string> = { stop: 'Stop', restart: 'Restart', disable: 'Disable' }
if (confirmFor[action] && !window.confirm(`${confirmFor[action]} ${name}?`)) return
if (confirmFor[action] && !await askYes(`${confirmFor[action]} ${name}?`)) return
setBusy(action); setErr('')
try { await api.serviceAction(name, action); await load(); onChanged(); toast(`${name}: ${action} ok`) }
catch (e) { setErr(e instanceof Error ? e.message : 'failed'); toast(`${name}: ${action} failed`, 'err') }
@@ -176,7 +177,8 @@ export default function Services() {
const [err, setErr] = useState('')
const [q, setQ] = useState('')
const [filter, setFilter] = useState<Filter>('all')
const [sel, setSel] = useState<string | null>(null)
const [hashSel, setHashSel] = useHashSel() // the selected unit lives in the URL
const sel = hashSel || null, setSel = (v: string | null) => setHashSel(v ?? '')
const [loaded, setLoaded] = useState(false)
const load = useCallback(async () => {