Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
50 lines
2.3 KiB
TypeScript
50 lines
2.3 KiB
TypeScript
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.
|
|
// `label` is what the user types (the machine name on a remote); `host` is what the node checks.
|
|
export default function Power({ host, label, rebootNeeded }: { host: string; label: string; rebootNeeded?: boolean }) {
|
|
const [open, setOpen] = useState(false)
|
|
const [typed, setTyped] = useState('')
|
|
const [sent, setSent] = useState<'' | 'reboot' | 'poweroff'>('')
|
|
const [err, setErr] = useState('')
|
|
const ok = typed.trim() === label
|
|
|
|
async function go(action: 'reboot' | 'poweroff') {
|
|
setErr('')
|
|
try { await api.power(action, host); 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(''); setSent(''); setErr('') }}>⏻</button>
|
|
{open && (
|
|
<div className="modal-back" onClick={() => !sent && setOpen(false)}>
|
|
<div className="modal" onClick={(e) => e.stopPropagation()}>
|
|
<h3>Power · {label}</h3>
|
|
{rebootNeeded && <div className="notice">A reboot is pending from package updates.</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 <b>{label}</b> to confirm
|
|
<input autoFocus value={typed} onChange={(e) => setTyped(e.target.value)} placeholder={label} />
|
|
</label>
|
|
{err && <div className="error">{err}</div>}
|
|
<div className="svc-actions">
|
|
<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>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</>
|
|
)
|
|
}
|