Bug bash 2026-09-19: 17 bugs fixed and verified (BUGS.md)
Widgets: a bad pattern no longer silences every widget; malformed layouts load. Shell: lost session returns to login at once; tab titles; Escape closes menus and dialogs. Files: names are one path segment; unsaved edits ask before page/machine/tab changes; only folders that opened are remembered. Terminals: unique default names; 64-char names. API: queries capped at 10 000 values; 32 streams per client, 512 in all; only /embed/widget is frameable. ZFS sensors: one unprivileged zpool list, no sudo journal spam. Phone: 36 px inputs; dataset rows as wide as their cells. Machines: valid name pattern. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
36de4ad4a1
commit
8ee2a2d293
@@ -1,7 +1,8 @@
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { Sensor, fetchCatalog, fmtValue } from './sensors'
|
||||
import { apiPath } from './api'
|
||||
import { WIDGET_TYPES, WidgetSpec, WidgetType } from './widgets'
|
||||
import { Btn, Loading } from './ui'
|
||||
import { Btn, Loading, useEscape } from './ui'
|
||||
|
||||
// Pick a sensor from this machine's catalog (or type a pattern) and how to draw it.
|
||||
export default function WidgetPicker({ onAdd, onClose }: { onAdd: (w: WidgetSpec) => void; onClose: () => void }) {
|
||||
@@ -11,13 +12,23 @@ export default function WidgetPicker({ onAdd, onClose }: { onAdd: (w: WidgetSpec
|
||||
const [sensor, setSensor] = useState('')
|
||||
const [type, setType] = useState<WidgetType>('stat')
|
||||
const [title, setTitle] = useState('')
|
||||
const [bad, setBad] = useState('')
|
||||
// One bad pattern would 400 the stream every widget on the page shares, so ask the API first.
|
||||
const add = async () => {
|
||||
try {
|
||||
const r = await fetch(apiPath('/api/sensors/query'), { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ w: sensor }) })
|
||||
if (!r.ok) { setBad((await r.json().catch(() => ({}))).error ?? `the API refused it (${r.status})`); return }
|
||||
} catch { setBad('could not reach the API'); return }
|
||||
onAdd({ id: 'w' + Date.now().toString(36), type: pattern ? 'list' : type === 'list' ? 'stat' : type, sensor, title: title.trim() || undefined })
|
||||
}
|
||||
useEffect(() => { fetchCatalog().then(setCat).catch((e) => setErr(e instanceof Error ? e.message : 'failed')) }, [])
|
||||
const shown = useMemo(() => {
|
||||
const f = filter.trim().toLowerCase()
|
||||
return (cat ?? []).filter((s) => !f || s.id.includes(f) || s.name.toLowerCase().includes(f))
|
||||
}, [cat, filter])
|
||||
useEscape(onClose)
|
||||
const pattern = /[*?[]/.test(sensor)
|
||||
const pick = (id: string) => { setSensor(id); if (/[*?[]/.test(id)) setType('list') }
|
||||
const pick = (id: string) => { setSensor(id); setBad(''); if (/[*?[]/.test(id)) setType('list') }
|
||||
return (
|
||||
<div className="modal-back" onClick={onClose}>
|
||||
<div className="modal picker" onClick={(e) => e.stopPropagation()}>
|
||||
@@ -44,8 +55,9 @@ export default function WidgetPicker({ onAdd, onClose }: { onAdd: (w: WidgetSpec
|
||||
<label className="muted">Title (optional)
|
||||
<input value={title} onChange={(e) => setTitle(e.target.value)} placeholder="the sensor's own name" />
|
||||
</label>
|
||||
{bad && <div className="error">{bad}</div>}
|
||||
<div className="svc-actions">
|
||||
<Btn className="btn on" disabled={!sensor} onClick={() => onAdd({ id: 'w' + Date.now().toString(36), type: pattern ? 'list' : type === 'list' ? 'stat' : type, sensor, title: title.trim() || undefined })}>Add</Btn>
|
||||
<Btn className="btn on" disabled={!sensor} onClick={add}>Add</Btn>
|
||||
<span className="spacer" />
|
||||
<button className="btn" onClick={onClose}>Cancel</button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user