0.2.71
This commit is contained in:
@@ -65,6 +65,16 @@ export interface Maintenance {
|
||||
updatedAt?: string;
|
||||
}
|
||||
|
||||
export interface QuietTime {
|
||||
enabled: boolean;
|
||||
active: boolean;
|
||||
startTime: string;
|
||||
endTime: string;
|
||||
message: string;
|
||||
timeZone: string;
|
||||
updatedAt?: string;
|
||||
}
|
||||
|
||||
export interface UpdatePolicy {
|
||||
enabled: boolean;
|
||||
latestVersion: string;
|
||||
@@ -218,6 +228,7 @@ export interface AdminStatus {
|
||||
serverVersion: string;
|
||||
currentUser?: string;
|
||||
maintenance: Maintenance;
|
||||
quietTime: QuietTime;
|
||||
updatePolicy: UpdatePolicy;
|
||||
library: LibraryStats;
|
||||
syncRunning: boolean;
|
||||
|
||||
@@ -139,9 +139,10 @@ export function Layout() {
|
||||
const [confirmingOffline, setConfirmingOffline] = useState(false);
|
||||
const location = useLocation();
|
||||
const offline = Boolean(status?.maintenance?.enabled);
|
||||
const quiet = Boolean(status?.quietTime?.active);
|
||||
const account = useRef<HTMLDivElement>(null);
|
||||
const initial = Array.from(currentUser.trim())[0]?.toLocaleUpperCase('en-NZ') || 'A';
|
||||
const statusTone = status && online && !offline ? 'ok' : status || !loading ? 'bad' : 'checking';
|
||||
const statusTone = status && online && !offline && !quiet ? 'ok' : status || !loading ? 'bad' : 'checking';
|
||||
|
||||
const toggleAvailability = async () => {
|
||||
if (changingAvailability || !status) return;
|
||||
@@ -227,9 +228,9 @@ export function Layout() {
|
||||
className="topbar-status"
|
||||
data-tone={statusTone}
|
||||
aria-pressed={offline}
|
||||
disabled={!status || !online || changingAvailability}
|
||||
title={!status ? 'Checking Memby status' : offline ? 'Bring Memby back online' : online ? 'Take Memby offline' : 'Memby is not responding'}
|
||||
aria-label={offline ? 'Memby is offline. Bring it online' : status && online ? 'Memby is online. Take it offline' : loading ? 'Checking Memby status' : 'Memby is not responding'}
|
||||
disabled={!status || !online || changingAvailability || quiet}
|
||||
title={!status ? 'Checking Memby status' : quiet ? 'Memby quiet time is active' : offline ? 'Bring Memby back online' : online ? 'Take Memby offline' : 'Memby is not responding'}
|
||||
aria-label={quiet ? 'Memby quiet time is active' : offline ? 'Memby is offline. Bring it online' : status && online ? 'Memby is online. Take it offline' : loading ? 'Checking Memby status' : 'Memby is not responding'}
|
||||
onClick={() => offline ? void toggleAvailability() : setConfirmingOffline(true)}
|
||||
>
|
||||
<span className="dot" aria-hidden="true" />
|
||||
|
||||
+1
-1
@@ -265,7 +265,7 @@ export const nav: NavGroup[] = [
|
||||
path: '/admin/maintenance',
|
||||
label: 'Maintenance',
|
||||
title: 'Maintenance',
|
||||
intro: 'Take Memby offline for every television.',
|
||||
intro: 'Take Memby offline now or schedule daily quiet time.',
|
||||
icon: 'wrench',
|
||||
},
|
||||
{
|
||||
|
||||
@@ -3,7 +3,7 @@ import { api } from '../api/client';
|
||||
import { useAction } from '../lib/hooks';
|
||||
import { useGateway } from '../lib/gateway';
|
||||
import { useToast } from '../lib/toast';
|
||||
import { Banner, Button, Card, Confirm, Field, Loading, PageHead, Tag } from '../components/ui';
|
||||
import { Banner, Button, Card, Confirm, Field, Loading, PageHead, Tag, Toggle } from '../components/ui';
|
||||
|
||||
export function MaintenancePage() {
|
||||
const { status, error, loading, reload } = useGateway();
|
||||
@@ -12,6 +12,11 @@ export function MaintenancePage() {
|
||||
const [message, setMessage] = useState('');
|
||||
const [confirming, setConfirming] = useState(false);
|
||||
const [touched, setTouched] = useState(false);
|
||||
const [quietEnabled, setQuietEnabled] = useState(false);
|
||||
const [quietStart, setQuietStart] = useState('23:00');
|
||||
const [quietEnd, setQuietEnd] = useState('07:00');
|
||||
const [quietMessage, setQuietMessage] = useState('');
|
||||
const [quietTouched, setQuietTouched] = useState(false);
|
||||
|
||||
const enabled = Boolean(status?.maintenance?.enabled);
|
||||
|
||||
@@ -23,6 +28,14 @@ export function MaintenancePage() {
|
||||
if (!touched && status) setMessage(status.maintenance?.message ?? '');
|
||||
}, [status, touched]);
|
||||
|
||||
useEffect(() => {
|
||||
if (quietTouched || !status?.quietTime) return;
|
||||
setQuietEnabled(status.quietTime.enabled);
|
||||
setQuietStart(status.quietTime.startTime);
|
||||
setQuietEnd(status.quietTime.endTime);
|
||||
setQuietMessage(status.quietTime.message);
|
||||
}, [status, quietTouched]);
|
||||
|
||||
const set = (next: boolean) =>
|
||||
run(next ? 'on' : 'off', async () => {
|
||||
await wrap(
|
||||
@@ -34,9 +47,24 @@ export function MaintenancePage() {
|
||||
await reload();
|
||||
});
|
||||
|
||||
const saveQuietTime = () =>
|
||||
run('quiet', async () => {
|
||||
await wrap(
|
||||
() => api.post('/admin/api/quiet-time', {
|
||||
enabled: quietEnabled,
|
||||
startTime: quietStart,
|
||||
endTime: quietEnd,
|
||||
message: quietMessage,
|
||||
}),
|
||||
quietEnabled ? 'Quiet time saved.' : 'Quiet time turned off.',
|
||||
);
|
||||
setQuietTouched(false);
|
||||
await reload();
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHead title="Maintenance" intro="Take Memby offline for every television." />
|
||||
<PageHead title="Maintenance" intro="Take Memby offline now or schedule daily quiet time." />
|
||||
<Banner message={error} />
|
||||
|
||||
{loading ? (
|
||||
@@ -76,6 +104,38 @@ export function MaintenancePage() {
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{!loading ? (
|
||||
<Card
|
||||
title="Quiet time"
|
||||
intro={`Pause new television requests and server background work every day in ${status?.quietTime?.timeZone ?? 'the household timezone'}. Work already under way finishes safely. The admin console and health checks stay available so the schedule can always be changed.`}
|
||||
icon="clock"
|
||||
tone={status?.quietTime?.active ? 'warn' : 'info'}
|
||||
actions={status?.quietTime?.active ? <Tag tone="warn">active now</Tag> : quietEnabled ? <Tag tone="ok">scheduled</Tag> : <Tag>off</Tag>}
|
||||
footer={
|
||||
<Button variant="primary" busy={busy === 'quiet'} onClick={() => void saveQuietTime()}>
|
||||
Save quiet time
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Toggle
|
||||
label="Pause server activity during quiet time"
|
||||
checked={quietEnabled}
|
||||
onChange={(next) => { setQuietEnabled(next); setQuietTouched(true); }}
|
||||
/>
|
||||
<div className="fields">
|
||||
<Field label="Starts" hint="Uses the household's 24-hour clock.">
|
||||
<input type="time" value={quietStart} onChange={(event) => { setQuietStart(event.target.value); setQuietTouched(true); }} />
|
||||
</Field>
|
||||
<Field label="Ends" hint="May be on the following day, for example 23:00 to 07:00.">
|
||||
<input type="time" value={quietEnd} onChange={(event) => { setQuietEnd(event.target.value); setQuietTouched(true); }} />
|
||||
</Field>
|
||||
</div>
|
||||
<Field label="Message shown on the television" hint="Shown when a television contacts Memby during quiet time.">
|
||||
<input type="text" value={quietMessage} placeholder="Quiet time — try again after 7 am" onChange={(event) => { setQuietMessage(event.target.value); setQuietTouched(true); }} />
|
||||
</Field>
|
||||
</Card>
|
||||
) : null}
|
||||
|
||||
{confirming ? (
|
||||
<Confirm
|
||||
title="Take Memby offline?"
|
||||
|
||||
@@ -108,6 +108,8 @@ export function OverviewPage() {
|
||||
label: 'Availability',
|
||||
value: status.maintenance?.enabled ? (
|
||||
<Tag tone="bad">offline for maintenance</Tag>
|
||||
) : status.quietTime?.active ? (
|
||||
<Tag tone="warn">quiet time active</Tag>
|
||||
) : (
|
||||
<Tag tone="ok">online</Tag>
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user