Files
nxdns/admin/src/features/configuration/search.ts
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

60 lines
2.2 KiB
TypeScript

/**
* The configuration pages' URL state, validated as pure functions so each
* route's `validateSearch` stays a one-liner and every rejection is testable
* without a router.
*
* A tab that is not one of the page's tabs falls back to that page's default:
* a hand-typed `?tab=upstreams` on Protection opens Protection's first tab
* rather than an empty panel.
*/
export const PROTECTION_TABS = ["groups", "sources"] as const;
export type ProtectionTab = (typeof PROTECTION_TABS)[number];
export const RESOLUTION_TABS = ["upstreams", "records", "zones"] as const;
export type ResolutionTab = (typeof RESOLUTION_TABS)[number];
/**
* Both fields are optional so a link to the page need not spell out a tab it
* does not care about — the same shape Overview's `period` uses. Validation
* still resolves an unknown tab to the page default, so a *rendered* page
* always has one.
*/
export interface ProtectionSearch {
tab?: ProtectionTab;
/**
* The selected group. Absent until the page resolves one, which it then
* writes back so a view of the page is a link.
*/
group?: number;
}
export interface ResolutionSearch {
tab?: ResolutionTab;
}
function validateTab<T extends string>(options: readonly T[], value: unknown, fallback: T): T {
return options.includes(value as T) ? (value as T) : fallback;
}
/**
* A group row id. Ids are positive integers, so anything else — a fraction, a
* string, a zero — is not an id that could ever exist and becomes `undefined`.
* An id that is well-formed but unknown is a different case: the page has to
* load the groups before it can tell, so it falls back there, not here.
*/
export function validateGroupId(value: unknown): number | undefined {
return Number.isSafeInteger(value) && (value as number) > 0 ? (value as number) : undefined;
}
export function validateProtectionSearch(search: Record<string, unknown>): ProtectionSearch {
return {
tab: validateTab(PROTECTION_TABS, search["tab"], "groups"),
group: validateGroupId(search["group"]),
};
}
export function validateResolutionSearch(search: Record<string, unknown>): ResolutionSearch {
return { tab: validateTab(RESOLUTION_TABS, search["tab"], "upstreams") };
}