~ knotless Search/
↑↓ move↵ open⇧↵ all results

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0
main
117 lines · 3.7 KB Raw History
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>
  );
}