Files
proxy-manager/web/PlannedSection.tsx
T
pastilhasandClaude Opus 5 07cf3317f8 domains: what you own, who runs its DNS, and the keys
one section, not two. a provider serves many domains so the lists differ, but a
person thinks in DOMAINS — the provider is something the check discovers, and a
key only matters as the answer to "can officer do anything for THIS domain".
two tabs would make somebody hold that join in their head, and the providers
tab would be two rows most of the time. so the key is entered from the row that
needs one and stored against its provider: add it for one cloudflare domain and
every other cloudflare domain turns green at once.

THE CHECK runs before you commit, because its answer is usually the reason you
would or would not add it, and a list of unchecked rows is a list somebody has
to remember to revisit.

it reports five verdicts, not two, and the extra three are the interesting
ones. `private` — atelier.officer.dev resolves to 100.64.x, which is CORRECT
and reads as broken against any public-IP comparison. `unknown` — we could not
establish our own address, so "is that us" has no answer and saying so beats a
confident wrong one. `nowhere` — a name bought ten minutes ago. plus
maybeProxied, because a cloudflare domain resolving to cloudflare is a working
setup rather than a misconfiguration.

NS gives the DNS HOST, not the registrar. officer.dev is godaddy on both counts
here, but a domain bought at godaddy and delegated to cloudflare answers
cloudflare — and that is the one that matters, since its API is what places an
ACME challenge. the UI says so where it would otherwise be read as wrong.

only an apex has NS records, so the lookup walks up to find them. verified
live: platform.officer.dev now answers godaddy instead of nothing.

CREDENTIALS are keyed on the provider, encrypted under this plugin's purpose
key, never returned by any route. scope is narrow|broad and NOT read|write —
DNS-01 works by WRITING a TXT record, so no read-only key can ever issue a
certificate, and offering one would produce a setup that looks configured and
fails at the first renewal sixty days later. the dialog says that out loud.

the form is generated from NPM's own dns-providers template, so the field names
are the provider's and it stays right for providers we have never heard of.
the routes live under /_config, which the manifest already marks ownerOnly: a
DNS key can rewrite every record for a domain, which is strictly more than the
NPM login kept there, and declaring it once beats checking it twice.

tables verified by query rather than by db:push's exit code.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
2026-08-25 19:27:06 +00:00

42 lines
1.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Hammer } from 'lucide-react';
import type { Section } from './sections';
/**
* A section that is agreed and not built.
*
* It says so plainly, and then says what it will be. Both halves matter: "not built" alone reads as an
* error, and a description alone reads as a feature that is broken. Together they read as a plan, which is
* what they are.
*
* The alternative was to hide these until they exist, and that loses the thing the nav is good at — a
* person opening this plugin can see what it is FOR, not only what it currently does. `README.md` carries
* the full design; this is the two sentences that fit on a screen.
*/
/**
* **Empty, and the component stays.**
*
* `domains` and `dns-providers` were both here and both got built, which is the outcome this was for. The
* mechanism is worth keeping for the next agreed-and-unbuilt section: a nav that shows where the plugin is
* going is more useful than one that only shows where it is.
*/
const COPY: Partial<Record<Section['key'], { what: string; why: string }>> = {};
export const PlannedSection = ({ section }: { section: Section }) => {
const copy = COPY[section.key];
return (
<div className="flex size-full flex-col items-center justify-center gap-3 p-8 text-center">
<Hammer className="size-8 text-muted-foreground" />
<p className="text-sm font-medium">{section.label} is not built yet.</p>
{copy && (
<>
<p className="max-w-md text-sm text-muted-foreground">{copy.what}</p>
<p className="max-w-md text-[11px] leading-relaxed text-muted-foreground">{copy.why}</p>
</>
)}
<p className="text-[11px] text-muted-foreground">The design is in this plugin’s README.</p>
</div>
);
};