chadtmiller.com/pds.js
An atproto PDS in JavaScript, for Cloudflare Workers and Node
0
0
import { useQuery } from '@tanstack/react-query';
import { useState } from 'react';
import { Notice } from '#/components/atoms/notice.jsx';
import { Panel } from '#/components/atoms/panel.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 { TextLink } from '#/components/molecules/text-link.jsx';
import { get, post } from '#/lib/api.js';
import { queryKeys, useApiMutation } from '#/lib/query.js';
/**
* The update page for this deployment, with this server's hostname, worker
* script name and release channel carried along so the page can find the
* worker and install from the channel this server follows.
* @param {string} updateUrl
* @param {{scriptName?: string|null, channel?: string}} [version]
*/
export function updateHref(updateUrl, version = {}) {
const url = new URL(updateUrl);
url.searchParams.set('host', window.location.host);
if (version.scriptName) url.searchParams.set('script', version.scriptName);
if (version.channel === 'canary') url.searchParams.set('channel', 'canary');
return url.toString();
}
const CHANNELS = [
['stable', 'Stable releases'],
['canary', 'Canary builds'],
];
/**
* The pds.js release this server runs, whether a newer one is published, and
* which release channel the check follows. The server does the lookup and
* caches it, so rendering this card costs one same-origin request.
* Installing a release is a redeploy of the server: with an update page
* configured the card links there, and without one it names the release and
* leaves the redeploy to the operator.
* @param {{data: {features: {settings: boolean}}}} props
*/
export function VersionCard({ data: overview }) {
const { data } = useQuery({
queryKey: queryKeys.version,
queryFn: () => get('/version'),
});
const canStore = overview.features.settings;
const { run, busy, error } = useApiMutation([queryKeys.version]);
const [pending, setPending] = useState(null);
const channel = pending ?? data?.channel ?? 'stable';
const changeChannel = (next) => {
const previous = channel;
setPending(next);
run(async () => {
try {
await post('/settings/release-channel', { channel: next });
} catch (err) {
setPending(previous);
throw err;
}
});
};
let status;
if (!data) {
status = (
<p className="text-[13px] text-muted-foreground">
Checking for the latest release.
</p>
);
} else if (data.updateAvailable) {
status = (
<Notice variant="info">
<span className="font-bold text-foreground">
Version {data.latest} is available.
</span>{' '}
{data.updateUrl ? (
<>
<TextLink href={updateHref(data.updateUrl, data)} external>
Update this server
</TextLink>{' '}
to install it.{' '}
</>
) : (
<>Update the server deployment to install it. </>
)}
<TextLink href={data.releaseNotesUrl} external>
Release notes
</TextLink>
</Notice>
);
} else if (data.latest) {
status = (
<p className="text-[13px] text-muted-foreground">
{data.channel === 'canary'
? 'This is the newest canary build.'
: 'This is the latest release.'}
</p>
);
} else {
status = (
<p className="text-[13px] text-muted-foreground">
The release registry could not be reached. Checking again later.
</p>
);
}
return (
<Panel>
<Section
title="Server software"
description="The pds.js release this server runs."
>
<div className="flex flex-col gap-4">
<p className="font-bold text-[15px] text-foreground">
{data ? `Version ${data.current}` : ' '}
</p>
{status}
<SelectField
label="Release channel"
options={CHANNELS}
value={channel}
onChange={changeChannel}
disabled={busy || !data || !canStore}
/>
{channel === 'canary' && (
<p className="text-[13px] text-muted-foreground">
Canary follows the newest build of the main branch. It changes
often and gets less testing than a release.
</p>
)}
{!canStore && (
<p className="text-[13px] text-muted-foreground">
This server does not store settings.
</p>
)}
<ErrorNotice error={error} />
</div>
</Section>
</Panel>
);
}