Feedback sweep: spinners on every async button, toasts for action outcomes, top activity bar, loading states on every page, terminal connecting overlay, dashboard save indicator, task pane pinned above the package list with auto-scroll and completion toast
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
d08b246b5f
commit
724f30c02b
+11
-10
@@ -1,30 +1,31 @@
|
||||
import { useState } from 'react'
|
||||
import { api } from './api'
|
||||
import { Btn, Spinner } from './ui'
|
||||
|
||||
// Header power control: a small button, a dialog that needs the hostname typed back.
|
||||
export default function Power({ host, rebootNeeded }: { host: string; rebootNeeded?: boolean }) {
|
||||
const [open, setOpen] = useState(false)
|
||||
const [typed, setTyped] = useState('')
|
||||
const [busy, setBusy] = useState('')
|
||||
const [sent, setSent] = useState<'' | 'reboot' | 'poweroff'>('')
|
||||
const [err, setErr] = useState('')
|
||||
const ok = typed.trim() === host
|
||||
|
||||
async function go(action: 'reboot' | 'poweroff') {
|
||||
setBusy(action); setErr('')
|
||||
try { await api.power(action, typed.trim()); setBusy('sent') }
|
||||
catch (e) { setErr(e instanceof Error ? e.message : 'failed'); setBusy('') }
|
||||
setErr('')
|
||||
try { await api.power(action, typed.trim()); setSent(action) }
|
||||
catch (e) { setErr(e instanceof Error ? e.message : 'failed') }
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<button className={'icon-btn power' + (rebootNeeded ? ' attn' : '')} title={rebootNeeded ? 'reboot required' : 'power'} onClick={() => { setOpen(true); setTyped(''); setBusy(''); setErr('') }}>⏻</button>
|
||||
<button className={'icon-btn power' + (rebootNeeded ? ' attn' : '')} title={rebootNeeded ? 'reboot required' : 'power'} onClick={() => { setOpen(true); setTyped(''); setSent(''); setErr('') }}>⏻</button>
|
||||
{open && (
|
||||
<div className="modal-back" onClick={() => busy !== 'sent' && setOpen(false)}>
|
||||
<div className="modal-back" onClick={() => !sent && setOpen(false)}>
|
||||
<div className="modal" onClick={(e) => e.stopPropagation()}>
|
||||
<h3>Power · {host}</h3>
|
||||
{rebootNeeded && <div className="notice">A reboot is pending from package updates.</div>}
|
||||
{busy === 'sent' ? (
|
||||
<div className="muted">Command sent. The console will reconnect when the host is back.</div>
|
||||
{sent ? (
|
||||
<div className="muted"><Spinner /> {sent === 'reboot' ? 'Rebooting. The console will reconnect when the host is back.' : 'Shutting down. Power it back on by hand.'}</div>
|
||||
) : (
|
||||
<>
|
||||
<label className="muted">Type the hostname to confirm
|
||||
@@ -32,8 +33,8 @@ export default function Power({ host, rebootNeeded }: { host: string; rebootNeed
|
||||
</label>
|
||||
{err && <div className="error">{err}</div>}
|
||||
<div className="svc-actions">
|
||||
<button className="btn on" disabled={!ok || !!busy} onClick={() => go('reboot')}>{busy === 'reboot' ? '…' : 'Reboot'}</button>
|
||||
<button className="btn danger" disabled={!ok || !!busy} onClick={() => go('poweroff')}>{busy === 'poweroff' ? '…' : 'Shut down'}</button>
|
||||
<Btn className="btn on" disabled={!ok} onClick={() => go('reboot')}>Reboot</Btn>
|
||||
<Btn className="btn danger" disabled={!ok} onClick={() => go('poweroff')}>Shut down</Btn>
|
||||
<span className="spacer" />
|
||||
<button className="btn" onClick={() => setOpen(false)}>Cancel</button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user