chadtmiller.com/pds.js
An atproto PDS in JavaScript, for Cloudflare Workers and Node
0
0
import { useQueryClient } from '@tanstack/react-query';
import { useState } from 'react';
import { Button } from '#/components/atoms/button.jsx';
import { Panel } from '#/components/atoms/panel.jsx';
import { CheckboxField } from '#/components/molecules/checkbox-field.jsx';
import { DetailList } from '#/components/molecules/detail-list.jsx';
import { ErrorNotice } from '#/components/molecules/error-notice.jsx';
import { Section } from '#/components/molecules/section.jsx';
import { SelectField } from '#/components/molecules/select-field.jsx';
import { post } from '#/lib/api.js';
import { dateTime, timeAgo } from '#/lib/format.js';
import { queryKeys } from '#/lib/query.js';
const INTERVALS = [
['6', 'Every 6 hours'],
['12', 'Every 12 hours'],
['24', 'Every 24 hours'],
['48', 'Every 48 hours'],
['168', 'Weekly'],
];
const RETAIN = [
['3', '3 snapshots'],
['5', '5 snapshots'],
['10', '10 snapshots'],
['30', '30 snapshots'],
];
/**
* The schedule: whether backups run on their own, how often, and how many
* snapshots stay around. Saving posts the whole trio and the server answers
* with the fresh backups payload, which replaces the cached one directly.
* @param {{data: {settings: object, nextRunAt: number|null, runs: object[]}}} props
*/
export function BackupSettingsCard({ data }) {
const qc = useQueryClient();
const { settings, nextRunAt, runs } = data;
const [enabled, setEnabled] = useState(settings.enabled);
const [intervalHours, setIntervalHours] = useState(
String(settings.intervalHours),
);
const [retain, setRetain] = useState(String(settings.retain));
const [busy, setBusy] = useState(false);
const [error, setError] = useState('');
const lastOk = (runs || []).find((run) => run.status === 'ok');
const save = async () => {
setError('');
setBusy(true);
try {
const result = await post('/backups/settings', {
enabled,
intervalHours: Number(intervalHours),
retain: Number(retain),
});
qc.setQueryData(queryKeys.backups, result);
} catch (err) {
setError(err instanceof Error ? err.message : String(err));
} finally {
setBusy(false);
}
};
return (
<Panel>
<Section
title="Automatic backups"
description="Snapshots of your whole account — records, uploaded files and settings — copied to this server's backup target on a schedule."
>
<DetailList
rows={[
[
'Last backup',
lastOk ? timeAgo(lastOk.finishedAt || lastOk.startedAt) : 'Never',
{ plain: true },
],
[
'Next run',
settings.enabled
? nextRunAt
? dateTime(new Date(nextRunAt).toISOString())
: '—'
: 'Backups are off',
{ plain: true },
],
]}
/>
<div className="mt-5 flex flex-col gap-4">
<CheckboxField
label="Back up on a schedule"
checked={enabled}
onCheckedChange={(next) => setEnabled(!!next)}
/>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<SelectField
label="How often"
options={INTERVALS}
value={intervalHours}
onChange={setIntervalHours}
/>
<SelectField
label="Snapshots to keep"
options={RETAIN}
value={retain}
onChange={setRetain}
/>
</div>
<ErrorNotice error={error} />
<Button size="block" disabled={busy} onClick={save}>
Save settings
</Button>
</div>
</Section>
</Panel>
);
}