Files
nxdns/admin/src/features/configuration/UpstreamForm.tsx
T
mokhtar 7e0df5fd94
Gates / frontend (push) Successful in 1m22s
Gates / test (push) Successful in 1m54s
Gates / test-aarch64 (push) Successful in 7m57s
Gates / package (push) Successful in 5m29s
Gates / container (push) Successful in 10s
CI / gates (push) Successful in 32m51s
milestone 32: task-shaped configuration, file mode as a rendering, config status api
2026-08-22 22:42:50 +02:00

162 lines
4.2 KiB
TypeScript

import { useState, type FormEvent } from "react";
import * as stylex from "@stylexjs/stylex";
import InlineError from "@/lib/InlineError";
import type { Upstream, UpstreamInput } from "@/lib/types";
import { styles as shared } from "@/ui/styles";
import { colors } from "@/ui/tokens.stylex";
const DEFAULT_PRIORITY = "100";
interface UpstreamFormProps {
initial?: Upstream;
busy: boolean;
error: Error | null;
onSubmit: (input: UpstreamInput) => Promise<void>;
onCancel?: () => void;
}
const styles = stylex.create({
form: {
display: "flex",
flexDirection: "column",
gap: "0.75rem",
marginTop: "1rem",
maxWidth: "36rem",
},
heading: {
fontSize: "1.125rem",
lineHeight: "1.75rem",
fontWeight: 500,
},
fieldLabel: {
display: "block",
fontSize: "0.875rem",
lineHeight: "1.25rem",
fontWeight: 500,
},
hint: {
marginTop: "0.25rem",
fontSize: "0.75rem",
lineHeight: "1rem",
color: colors.textMuted,
},
checkboxLabel: {
display: "flex",
alignItems: "center",
gap: "0.5rem",
fontSize: "0.875rem",
lineHeight: "1.25rem",
fontWeight: 500,
},
actions: {
display: "flex",
alignItems: "center",
gap: "0.5rem",
},
cancelButton: {
fontWeight: 500,
},
});
export default function UpstreamForm({ initial, busy, error, onSubmit, onCancel }: UpstreamFormProps) {
const [url, setUrl] = useState(initial?.url ?? "");
const [priority, setPriority] = useState(initial === undefined ? DEFAULT_PRIORITY : String(initial.priority));
const [enabled, setEnabled] = useState(initial?.enabled ?? true);
const [tlsName, setTlsName] = useState(initial?.tls_name ?? "");
async function handleSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
const parsed = Number(priority);
try {
// PUT replaces the row, so every field goes on every submit.
await onSubmit({
url: url.trim(),
priority: Number.isFinite(parsed) ? parsed : 0,
enabled,
tls_name: tlsName.trim(),
});
if (initial === undefined) {
setUrl("");
setPriority(DEFAULT_PRIORITY);
setEnabled(true);
setTlsName("");
}
} catch {
// The page renders the mutation error inline below the form.
}
}
return (
<form onSubmit={handleSubmit} {...stylex.props(styles.form)}>
<h2 {...stylex.props(styles.heading)}>{initial === undefined ? "Add upstream" : `Edit ${initial.url}`}</h2>
<div>
<label htmlFor="upstream-url" {...stylex.props(styles.fieldLabel)}>
URL
</label>
<input
id="upstream-url"
type="text"
required
value={url}
onChange={(event) => setUrl(event.target.value)}
placeholder="udp://1.1.1.1:53"
{...stylex.props(shared.input, shared.focusRing)}
/>
</div>
<div>
<label htmlFor="upstream-priority" {...stylex.props(styles.fieldLabel)}>
Priority
</label>
<input
id="upstream-priority"
type="number"
min={0}
value={priority}
onChange={(event) => setPriority(event.target.value)}
{...stylex.props(shared.input, shared.focusRing)}
/>
</div>
<div>
<label htmlFor="upstream-tls-name" {...stylex.props(styles.fieldLabel)}>
TLS name
</label>
<input
id="upstream-tls-name"
type="text"
value={tlsName}
onChange={(event) => setTlsName(event.target.value)}
placeholder="one.one.one.one"
{...stylex.props(shared.input, shared.focusRing)}
/>
<p {...stylex.props(styles.hint)}>
The SNI and certificate name for a <code>tls://</code> upstream. Leave empty for every other scheme.
</p>
</div>
<label {...stylex.props(styles.checkboxLabel)}>
<input
type="checkbox"
checked={enabled}
onChange={(event) => setEnabled(event.target.checked)}
{...stylex.props(shared.focusRing)}
/>
Enabled
</label>
<div {...stylex.props(styles.actions)}>
<button type="submit" disabled={busy} {...stylex.props(shared.primaryButton, shared.focusRing)}>
{initial === undefined ? "Add upstream" : "Save changes"}
</button>
{onCancel !== undefined && (
<button
type="button"
onClick={onCancel}
{...stylex.props(shared.button, styles.cancelButton, shared.focusRing)}
>
Cancel
</button>
)}
</div>
<InlineError error={error} />
</form>
);
}