Users, with their own settings, and a list of where each is signed in
The login is a user name and a password. The old single password becomes the account named after the OS user on first boot, along with its sessions and the machine-wide look. Settings gains Users (add, remove, reset a password — your own password confirms) and Sessions (device, IP, last seen; revoke one or sign out everywhere else). Theme, accents and the dashboard layout follow the account; the refresh rate, graph history and alerts stay the machine's. Pairing a machine takes a user on it. Log lines name the actor. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
db214ebc2b
commit
2b54a06821
+11
-3
@@ -2,7 +2,10 @@ import { FormEvent, useState } from 'react'
|
||||
import { api } from './api'
|
||||
import { Spinner } from './ui'
|
||||
|
||||
const USER_KEY = 'hamlab.user' // the last account signed in on this device, offered again
|
||||
|
||||
export default function Login({ onLogin }: { onLogin: () => void }) {
|
||||
const [user, setUser] = useState(localStorage.getItem(USER_KEY) ?? '')
|
||||
const [pw, setPw] = useState('')
|
||||
const [err, setErr] = useState('')
|
||||
const [busy, setBusy] = useState(false)
|
||||
@@ -10,15 +13,20 @@ export default function Login({ onLogin }: { onLogin: () => void }) {
|
||||
async function submit(e: FormEvent) {
|
||||
e.preventDefault()
|
||||
setBusy(true); setErr('')
|
||||
try { await api.login(pw); onLogin() }
|
||||
catch (e) { setErr((e instanceof Error && e.message) || 'login failed'); setBusy(false) }
|
||||
try {
|
||||
const r = await api.login(user.trim(), pw)
|
||||
localStorage.setItem(USER_KEY, r?.user ?? user.trim())
|
||||
onLogin()
|
||||
} catch (e) { setErr((e instanceof Error && e.message) || 'login failed'); setBusy(false) }
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="center">
|
||||
<form className="login" onSubmit={submit}>
|
||||
<img className="login-logo" src="/icons/logo.png" alt="Ham Lab" width={176} height={176} />
|
||||
<input type="password" autoFocus autoComplete="current-password" placeholder="password"
|
||||
<input type="text" autoFocus={!user} autoComplete="username" placeholder="user" spellCheck={false} autoCapitalize="off" autoCorrect="off"
|
||||
value={user} onChange={(e) => setUser(e.target.value)} disabled={busy} />
|
||||
<input type="password" autoFocus={!!user} autoComplete="current-password" placeholder="password"
|
||||
value={pw} onChange={(e) => setPw(e.target.value)} disabled={busy} />
|
||||
<button disabled={busy || !pw}>{busy && <Spinner />}{busy ? 'Signing in…' : 'Sign in'}</button>
|
||||
{err && <div className="error">{err}</div>}
|
||||
|
||||
Reference in New Issue
Block a user