milestone 17: real deadlines, validator holes, upstream editor, trusted proxies, contract samples, badvers
This commit is contained in:
@@ -81,12 +81,6 @@ async function request<T>(path: string, init?: { method?: string; body?: unknown
|
||||
return (await res.json()) as T;
|
||||
}
|
||||
|
||||
async function requestText(path: string): Promise<string> {
|
||||
const res = await fetch(path, { credentials: "same-origin" });
|
||||
if (!res.ok) throw await toApiError(res);
|
||||
return res.text();
|
||||
}
|
||||
|
||||
type QueryValue = string | number | boolean | undefined;
|
||||
|
||||
function qs(params: Record<string, QueryValue>): string {
|
||||
@@ -100,10 +94,8 @@ function qs(params: Record<string, QueryValue>): string {
|
||||
|
||||
// Monitoring + meta
|
||||
|
||||
export const getMetrics = (): Promise<string> => requestText("/metrics");
|
||||
export const getHealth = (): Promise<Health> => request("/api/health");
|
||||
export const getVersion = (): Promise<Version> => request("/api/version");
|
||||
export const getOpenapiYaml = (): Promise<string> => requestText("/api/openapi.yaml");
|
||||
|
||||
// Auth
|
||||
|
||||
@@ -133,7 +125,6 @@ export const getUpstreamHealth = (): Promise<UpstreamHealth> => request("/api/up
|
||||
export const listGroups = async (): Promise<Group[]> => (await request<{ groups: Group[] }>("/api/groups")).groups;
|
||||
export const createGroup = (input: GroupInput): Promise<Group> =>
|
||||
request("/api/groups", { method: "POST", body: input });
|
||||
export const getGroup = (id: number): Promise<Group> => request(`/api/groups/${id}`);
|
||||
export const updateGroup = (id: number, input: GroupInput): Promise<Group> =>
|
||||
request(`/api/groups/${id}`, { method: "PUT", body: input });
|
||||
export const deleteGroup = (id: number): Promise<void> => request(`/api/groups/${id}`, { method: "DELETE" });
|
||||
@@ -156,7 +147,6 @@ export const createBlocklist = (input: BlocklistInput): Promise<BlocklistEcho> =
|
||||
request("/api/blocklists", { method: "POST", body: input });
|
||||
export const updateBlocklistsNow = async (): Promise<SourceStatus[]> =>
|
||||
(await request<{ sources: SourceStatus[] }>("/api/blocklists/update", { method: "POST", body: {} })).sources;
|
||||
export const getBlocklist = (id: number): Promise<Blocklist> => request(`/api/blocklists/${id}`);
|
||||
export const updateBlocklist = (id: number, input: BlocklistInput): Promise<BlocklistEcho> =>
|
||||
request(`/api/blocklists/${id}`, { method: "PUT", body: input });
|
||||
export const deleteBlocklist = (id: number): Promise<void> => request(`/api/blocklists/${id}`, { method: "DELETE" });
|
||||
@@ -166,7 +156,6 @@ export const deleteBlocklist = (id: number): Promise<void> => request(`/api/bloc
|
||||
export const listRules = async (): Promise<Rule[]> => (await request<{ rules: Rule[] }>("/api/rules")).rules;
|
||||
export const createRule = (input: RuleInput): Promise<RuleEcho> =>
|
||||
request("/api/rules", { method: "POST", body: input });
|
||||
export const getRule = (id: number): Promise<Rule> => request(`/api/rules/${id}`);
|
||||
export const updateRule = (id: number, input: RuleInput): Promise<RuleEcho> =>
|
||||
request(`/api/rules/${id}`, { method: "PUT", body: input });
|
||||
export const deleteRule = (id: number): Promise<void> => request(`/api/rules/${id}`, { method: "DELETE" });
|
||||
@@ -177,7 +166,6 @@ export const listLocalRecords = async (): Promise<LocalRecord[]> =>
|
||||
(await request<{ local_records: LocalRecord[] }>("/api/local-records")).local_records;
|
||||
export const createLocalRecord = (input: LocalRecordInput): Promise<LocalRecord> =>
|
||||
request("/api/local-records", { method: "POST", body: input });
|
||||
export const getLocalRecord = (id: number): Promise<LocalRecord> => request(`/api/local-records/${id}`);
|
||||
export const updateLocalRecord = (id: number, input: LocalRecordInput): Promise<LocalRecord> =>
|
||||
request(`/api/local-records/${id}`, { method: "PUT", body: input });
|
||||
export const deleteLocalRecord = (id: number): Promise<void> =>
|
||||
@@ -189,7 +177,6 @@ export const listForwardZones = async (): Promise<ForwardZone[]> =>
|
||||
(await request<{ forward_zones: ForwardZone[] }>("/api/forward-zones")).forward_zones;
|
||||
export const createForwardZone = (input: ForwardZoneInput): Promise<ForwardZone> =>
|
||||
request("/api/forward-zones", { method: "POST", body: input });
|
||||
export const getForwardZone = (id: number): Promise<ForwardZone> => request(`/api/forward-zones/${id}`);
|
||||
export const updateForwardZone = (id: number, input: ForwardZoneInput): Promise<ForwardZone> =>
|
||||
request(`/api/forward-zones/${id}`, { method: "PUT", body: input });
|
||||
export const deleteForwardZone = (id: number): Promise<void> =>
|
||||
@@ -199,7 +186,6 @@ export const deleteForwardZone = (id: number): Promise<void> =>
|
||||
|
||||
export const listClients = async (): Promise<Client[]> =>
|
||||
(await request<{ clients: Client[] }>("/api/clients")).clients;
|
||||
export const getClient = (id: number): Promise<Client> => request(`/api/clients/${id}`);
|
||||
export const updateClient = (id: number, edit: ClientEdit): Promise<Client> =>
|
||||
request(`/api/clients/${id}`, { method: "PUT", body: edit });
|
||||
export const deleteClient = (id: number): Promise<void> => request(`/api/clients/${id}`, { method: "DELETE" });
|
||||
@@ -220,7 +206,6 @@ export const listUpstreams = async (): Promise<Upstream[]> =>
|
||||
(await request<{ upstreams: Upstream[] }>("/api/upstreams")).upstreams;
|
||||
export const createUpstream = (input: UpstreamInput): Promise<UpstreamEcho> =>
|
||||
request("/api/upstreams", { method: "POST", body: input });
|
||||
export const getUpstream = (id: number): Promise<Upstream> => request(`/api/upstreams/${id}`);
|
||||
export const updateUpstream = (id: number, input: UpstreamInput): Promise<UpstreamEcho> =>
|
||||
request(`/api/upstreams/${id}`, { method: "PUT", body: input });
|
||||
export const deleteUpstream = (id: number): Promise<void> => request(`/api/upstreams/${id}`, { method: "DELETE" });
|
||||
|
||||
@@ -0,0 +1,701 @@
|
||||
// Generated file — do not edit by hand.
|
||||
//
|
||||
// Every value below is a real response from the web server, captured by the
|
||||
// contract-sample test in src/web/web_integration_test.zig and canonicalized:
|
||||
// object keys sorted, every number 0, strings and booleans as the deterministic
|
||||
// seed produced them, repeated array elements collapsed to the first. The type
|
||||
// annotations are the ones api.ts hands to its own `request<T>`, so `tsc`
|
||||
// refuses a field the wire does not send, a wire field types.ts does not
|
||||
// declare, and a string outside a literal union.
|
||||
//
|
||||
// Regenerate with:
|
||||
// zig build test -Dintegration -Dcontract-samples-out="$PWD/web/src/lib/contractSamples.gen.ts"
|
||||
|
||||
import type {
|
||||
Blocklist,
|
||||
BlocklistEcho,
|
||||
Client,
|
||||
ClientPrefix,
|
||||
ErrorEnvelope,
|
||||
ForwardZone,
|
||||
Group,
|
||||
Health,
|
||||
LocalRecord,
|
||||
LoginResponse,
|
||||
LogoutResponse,
|
||||
LookupResult,
|
||||
PauseState,
|
||||
QueriesPage,
|
||||
Rule,
|
||||
RuleEcho,
|
||||
SettingsEnvelope,
|
||||
SourceStatus,
|
||||
StatsTimeseries,
|
||||
StatsTotals,
|
||||
Upstream,
|
||||
UpstreamEcho,
|
||||
UpstreamHealth,
|
||||
Version,
|
||||
} from "@/lib/types";
|
||||
|
||||
export const sample_get_health: Health = {
|
||||
disk: {
|
||||
db_bytes: 0,
|
||||
free_bytes: 0,
|
||||
log_bytes: 0,
|
||||
sample_failures: 0,
|
||||
state: "ok",
|
||||
},
|
||||
queries_dropped: 0,
|
||||
refreshes_gated: 0,
|
||||
snapshot_generation: 0,
|
||||
status: "ok",
|
||||
upstreams: {
|
||||
available: 0,
|
||||
total: 0,
|
||||
},
|
||||
writer_failed: false,
|
||||
};
|
||||
|
||||
export const sample_get_version: Version = {
|
||||
git_commit: "<build>",
|
||||
uptime_seconds: 0,
|
||||
version: "w10-test",
|
||||
zig_version: "<build>",
|
||||
};
|
||||
|
||||
export const sample_login: LoginResponse = {
|
||||
auth_required: false,
|
||||
authenticated: true,
|
||||
};
|
||||
|
||||
export const sample_logout: LogoutResponse = {
|
||||
authenticated: false,
|
||||
};
|
||||
|
||||
export const sample_create_blocklist: BlocklistEcho = {
|
||||
enabled: false,
|
||||
id: 0,
|
||||
is_suggested: false,
|
||||
name: "ads",
|
||||
url: "https://lists.example/ads.txt",
|
||||
};
|
||||
|
||||
export const sample_list_blocklists: { blocklists: Blocklist[] } = {
|
||||
blocklists: [
|
||||
{
|
||||
checksum: null,
|
||||
domain_count: 0,
|
||||
enabled: false,
|
||||
id: 0,
|
||||
is_suggested: false,
|
||||
last_updated: null,
|
||||
name: "ads",
|
||||
skipped_regex_count: 0,
|
||||
url: "https://lists.example/ads.txt",
|
||||
wildcard_count: 0,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const sample_update_blocklist: BlocklistEcho = {
|
||||
enabled: false,
|
||||
id: 0,
|
||||
is_suggested: false,
|
||||
name: "ads2",
|
||||
url: "https://lists.example/ads.txt",
|
||||
};
|
||||
|
||||
export const sample_update_blocklists_now: { sources: SourceStatus[] } = {
|
||||
sources: [
|
||||
{
|
||||
domains: 0,
|
||||
id: 0,
|
||||
last_attempt: 0,
|
||||
last_error: "",
|
||||
last_success: 0,
|
||||
loaded: false,
|
||||
skipped_regex: 0,
|
||||
state: "never_fetched",
|
||||
url: "https://lists.example/ads.txt",
|
||||
wildcards: 0,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const sample_list_groups: { groups: Group[] } = {
|
||||
groups: [
|
||||
{
|
||||
id: 0,
|
||||
name: "default",
|
||||
safe_search: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const sample_create_group: Group = {
|
||||
id: 0,
|
||||
name: "kids",
|
||||
safe_search: false,
|
||||
};
|
||||
|
||||
export const sample_update_group: Group = {
|
||||
id: 0,
|
||||
name: "teens",
|
||||
safe_search: true,
|
||||
};
|
||||
|
||||
export const sample_put_group_sources: { source_ids: number[] } = {
|
||||
source_ids: [0],
|
||||
};
|
||||
|
||||
export const sample_get_group_sources: { source_ids: number[] } = {
|
||||
source_ids: [0],
|
||||
};
|
||||
|
||||
export const sample_create_rule: RuleEcho = {
|
||||
action: "block",
|
||||
group_id: 0,
|
||||
id: 0,
|
||||
kind: "exact",
|
||||
pattern: "ads.example",
|
||||
};
|
||||
|
||||
export const sample_list_rules: { rules: Rule[] } = {
|
||||
rules: [
|
||||
{
|
||||
action: "block",
|
||||
created_at: 0,
|
||||
group: "default",
|
||||
group_id: 0,
|
||||
id: 0,
|
||||
kind: "exact",
|
||||
pattern: "ads.example",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const sample_update_rule: RuleEcho = {
|
||||
action: "block",
|
||||
group_id: 0,
|
||||
id: 0,
|
||||
kind: "wildcard",
|
||||
pattern: "*.ads.example",
|
||||
};
|
||||
|
||||
export const sample_get_lookup: LookupResult = {
|
||||
blocked: true,
|
||||
domain: "sub.ads.example",
|
||||
forward_zone: null,
|
||||
group_id: 0,
|
||||
local_records: false,
|
||||
matched: "*.ads.example",
|
||||
reason: "rule_block_wildcard",
|
||||
safe_search_rewrite: null,
|
||||
source_url: null,
|
||||
};
|
||||
|
||||
export const sample_create_local_record: LocalRecord = {
|
||||
id: 0,
|
||||
name: "nas.lan",
|
||||
rtype: "A",
|
||||
ttl: 0,
|
||||
value: "192.168.1.10",
|
||||
};
|
||||
|
||||
export const sample_list_local_records: { local_records: LocalRecord[] } = {
|
||||
local_records: [
|
||||
{
|
||||
id: 0,
|
||||
name: "nas.lan",
|
||||
rtype: "A",
|
||||
ttl: 0,
|
||||
value: "192.168.1.10",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const sample_update_local_record: LocalRecord = {
|
||||
id: 0,
|
||||
name: "nas.lan",
|
||||
rtype: "A",
|
||||
ttl: 0,
|
||||
value: "192.168.1.11",
|
||||
};
|
||||
|
||||
export const sample_create_forward_zone: ForwardZone = {
|
||||
id: 0,
|
||||
resolver: "udp://10.0.0.1:53",
|
||||
zone: "lan",
|
||||
};
|
||||
|
||||
export const sample_list_forward_zones: { forward_zones: ForwardZone[] } = {
|
||||
forward_zones: [
|
||||
{
|
||||
id: 0,
|
||||
resolver: "udp://10.0.0.1:53",
|
||||
zone: "lan",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const sample_update_forward_zone: ForwardZone = {
|
||||
id: 0,
|
||||
resolver: "udp://10.0.0.2:53",
|
||||
zone: "lan",
|
||||
};
|
||||
|
||||
export const sample_list_clients: { clients: Client[] } = {
|
||||
clients: [
|
||||
{
|
||||
first_seen: 0,
|
||||
group: "default",
|
||||
group_id: 0,
|
||||
hand_edited: false,
|
||||
id: 0,
|
||||
ip: "192.168.1.50",
|
||||
last_seen: 0,
|
||||
name: "laptop",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const sample_update_client: Client = {
|
||||
first_seen: 0,
|
||||
group: "default",
|
||||
group_id: 0,
|
||||
hand_edited: true,
|
||||
id: 0,
|
||||
ip: "192.168.1.50",
|
||||
last_seen: 0,
|
||||
name: "laptop-renamed",
|
||||
};
|
||||
|
||||
export const sample_put_client_prefixes: { client_prefixes: ClientPrefix[] } = {
|
||||
client_prefixes: [
|
||||
{
|
||||
group: "default",
|
||||
group_id: 0,
|
||||
id: 0,
|
||||
prefix: "192.168.1.0/24",
|
||||
priority: 0,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const sample_list_client_prefixes: { client_prefixes: ClientPrefix[] } = {
|
||||
client_prefixes: [
|
||||
{
|
||||
group: "default",
|
||||
group_id: 0,
|
||||
id: 0,
|
||||
prefix: "192.168.1.0/24",
|
||||
priority: 0,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const sample_list_upstreams: { upstreams: Upstream[] } = {
|
||||
upstreams: [
|
||||
{
|
||||
enabled: true,
|
||||
id: 0,
|
||||
priority: 0,
|
||||
tls_name: "",
|
||||
url: "https://dns.example/dns-query",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const sample_create_upstream: UpstreamEcho = {
|
||||
enabled: true,
|
||||
id: 0,
|
||||
priority: 0,
|
||||
restart_required: true,
|
||||
tls_name: "",
|
||||
url: "https://dns2.example/dns-query",
|
||||
};
|
||||
|
||||
export const sample_update_upstream: UpstreamEcho = {
|
||||
enabled: true,
|
||||
id: 0,
|
||||
priority: 0,
|
||||
restart_required: true,
|
||||
tls_name: "",
|
||||
url: "https://dns.example/dns-query",
|
||||
};
|
||||
|
||||
export const sample_get_upstream_health: UpstreamHealth = {
|
||||
available: 0,
|
||||
total: 0,
|
||||
upstreams: [
|
||||
{
|
||||
available: true,
|
||||
consecutive_failures: 0,
|
||||
enabled: true,
|
||||
last_error: "",
|
||||
success_rate: 0,
|
||||
total_failures: 0,
|
||||
total_successes: 0,
|
||||
url: "https://dns.example/dns-query",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const sample_get_queries: QueriesPage = {
|
||||
next_before: 0,
|
||||
queries: [
|
||||
{
|
||||
block_reason: "",
|
||||
blocked: false,
|
||||
cache_hit: true,
|
||||
client_ip: "192.0.2.10",
|
||||
domain: "d24.example",
|
||||
id: 0,
|
||||
qtype: 0,
|
||||
response_time_us: 0,
|
||||
ts: 0,
|
||||
upstream: "https://dns.example/dns-query",
|
||||
},
|
||||
{
|
||||
block_reason: "",
|
||||
blocked: false,
|
||||
cache_hit: false,
|
||||
client_ip: "192.0.2.10",
|
||||
domain: "d23.example",
|
||||
id: 0,
|
||||
qtype: 0,
|
||||
response_time_us: 0,
|
||||
ts: 0,
|
||||
upstream: "https://dns.example/dns-query",
|
||||
},
|
||||
{
|
||||
block_reason: "",
|
||||
blocked: false,
|
||||
cache_hit: true,
|
||||
client_ip: "192.0.2.10",
|
||||
domain: "d22.example",
|
||||
id: 0,
|
||||
qtype: 0,
|
||||
response_time_us: 0,
|
||||
ts: 0,
|
||||
upstream: "https://dns.example/dns-query",
|
||||
},
|
||||
{
|
||||
block_reason: "",
|
||||
blocked: false,
|
||||
cache_hit: false,
|
||||
client_ip: "192.0.2.10",
|
||||
domain: "d21.example",
|
||||
id: 0,
|
||||
qtype: 0,
|
||||
response_time_us: 0,
|
||||
ts: 0,
|
||||
upstream: "https://dns.example/dns-query",
|
||||
},
|
||||
{
|
||||
block_reason: "blocklist_domain",
|
||||
blocked: true,
|
||||
cache_hit: null,
|
||||
client_ip: "192.0.2.10",
|
||||
domain: "d20.example",
|
||||
id: 0,
|
||||
qtype: 0,
|
||||
response_time_us: 0,
|
||||
ts: 0,
|
||||
upstream: "",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const sample_get_stats: StatsTotals = {
|
||||
avg_response_time_us: null,
|
||||
blocked: 0,
|
||||
cached: 0,
|
||||
clients: 0,
|
||||
period: "1h",
|
||||
queries: 0,
|
||||
since: 0,
|
||||
until: 0,
|
||||
};
|
||||
|
||||
export const sample_get_stats_timeseries: StatsTimeseries = {
|
||||
bucket_seconds: 0,
|
||||
buckets: [
|
||||
{
|
||||
blocked: 0,
|
||||
cached: 0,
|
||||
queries: 0,
|
||||
ts: 0,
|
||||
},
|
||||
],
|
||||
period: "1h",
|
||||
since: 0,
|
||||
until: 0,
|
||||
};
|
||||
|
||||
export const sample_get_pause: PauseState = {
|
||||
paused: false,
|
||||
until: null,
|
||||
};
|
||||
|
||||
export const sample_post_pause: PauseState = {
|
||||
paused: true,
|
||||
until: 0,
|
||||
};
|
||||
|
||||
export const sample_get_settings: SettingsEnvelope = {
|
||||
restart_required: [
|
||||
"upstream.attempt_timeout_ms",
|
||||
"upstream.read_timeout_ms",
|
||||
"upstream.total_timeout_ms",
|
||||
"dns.bind_ipv4",
|
||||
"dns.bind_ipv6",
|
||||
"dns.port",
|
||||
"dns.rate_limit",
|
||||
"dns.rate_window_seconds",
|
||||
"blocking.response",
|
||||
"blocking.ttl",
|
||||
"cache.size",
|
||||
"cache.negative_ttl_max",
|
||||
"web.enabled",
|
||||
"web.bind",
|
||||
"web.port",
|
||||
"web.session_ttl_hours",
|
||||
"web.api_rate_limit_per_min",
|
||||
"web.api_localhost_exempt",
|
||||
"web.sse_max_connections_per_ip",
|
||||
"web.trusted_proxies",
|
||||
"doh_server.enabled",
|
||||
"doh_server.bind",
|
||||
"doh_server.port",
|
||||
"doh_server.cert_path",
|
||||
"doh_server.key_path",
|
||||
"dot_server.enabled",
|
||||
"dot_server.bind",
|
||||
"dot_server.port",
|
||||
"dot_server.cert_path",
|
||||
"dot_server.key_path",
|
||||
"edns.ecs_mode",
|
||||
"logging.level",
|
||||
"logging.retention_days",
|
||||
"logging.query_log_buffer_max",
|
||||
"logging.hide_domains",
|
||||
"logging.hide_client_ips",
|
||||
"logging.output",
|
||||
"logging.file_path",
|
||||
"logging.max_size_mb",
|
||||
"logging.max_files",
|
||||
"disk.min_free_mb",
|
||||
"disk.warn_free_mb",
|
||||
"blocklist_update.enabled",
|
||||
"blocklist_update.interval_hours",
|
||||
],
|
||||
settings: {
|
||||
blocking: {
|
||||
response: "zero",
|
||||
ttl: 0,
|
||||
},
|
||||
blocklist_update: {
|
||||
enabled: true,
|
||||
interval_hours: 0,
|
||||
},
|
||||
cache: {
|
||||
negative_ttl_max: 0,
|
||||
size: 0,
|
||||
},
|
||||
disk: {
|
||||
min_free_mb: 0,
|
||||
warn_free_mb: 0,
|
||||
},
|
||||
dns: {
|
||||
bind_ipv4: "0.0.0.0",
|
||||
bind_ipv6: "::",
|
||||
port: 0,
|
||||
rate_limit: 0,
|
||||
rate_window_seconds: 0,
|
||||
},
|
||||
doh_server: {
|
||||
bind: "0.0.0.0",
|
||||
cert_path: "/etc/nxdns/cert.pem",
|
||||
enabled: false,
|
||||
key_path: "/etc/nxdns/key.pem",
|
||||
port: 0,
|
||||
},
|
||||
dot_server: {
|
||||
bind: "0.0.0.0",
|
||||
cert_path: "/etc/nxdns/cert.pem",
|
||||
enabled: false,
|
||||
key_path: "/etc/nxdns/key.pem",
|
||||
port: 0,
|
||||
},
|
||||
edns: {
|
||||
ecs_mode: "strip",
|
||||
},
|
||||
logging: {
|
||||
file_path: "/var/log/nxdns/nxdns.log",
|
||||
hide_client_ips: false,
|
||||
hide_domains: false,
|
||||
level: "info",
|
||||
max_files: 0,
|
||||
max_size_mb: 0,
|
||||
output: "stderr",
|
||||
query_log_buffer_max: 0,
|
||||
retention_days: 0,
|
||||
},
|
||||
upstream: {
|
||||
attempt_timeout_ms: 0,
|
||||
read_timeout_ms: 0,
|
||||
total_timeout_ms: 0,
|
||||
},
|
||||
web: {
|
||||
api_localhost_exempt: true,
|
||||
api_rate_limit_per_min: 0,
|
||||
auth_enabled: false,
|
||||
bind: "0.0.0.0",
|
||||
enabled: true,
|
||||
port: 0,
|
||||
session_ttl_hours: 0,
|
||||
sse_max_connections_per_ip: 0,
|
||||
trusted_proxies: "",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const sample_put_settings: SettingsEnvelope = {
|
||||
restart_required: [
|
||||
"upstream.attempt_timeout_ms",
|
||||
"upstream.read_timeout_ms",
|
||||
"upstream.total_timeout_ms",
|
||||
"dns.bind_ipv4",
|
||||
"dns.bind_ipv6",
|
||||
"dns.port",
|
||||
"dns.rate_limit",
|
||||
"dns.rate_window_seconds",
|
||||
"blocking.response",
|
||||
"blocking.ttl",
|
||||
"cache.size",
|
||||
"cache.negative_ttl_max",
|
||||
"web.enabled",
|
||||
"web.bind",
|
||||
"web.port",
|
||||
"web.session_ttl_hours",
|
||||
"web.api_rate_limit_per_min",
|
||||
"web.api_localhost_exempt",
|
||||
"web.sse_max_connections_per_ip",
|
||||
"web.trusted_proxies",
|
||||
"doh_server.enabled",
|
||||
"doh_server.bind",
|
||||
"doh_server.port",
|
||||
"doh_server.cert_path",
|
||||
"doh_server.key_path",
|
||||
"dot_server.enabled",
|
||||
"dot_server.bind",
|
||||
"dot_server.port",
|
||||
"dot_server.cert_path",
|
||||
"dot_server.key_path",
|
||||
"edns.ecs_mode",
|
||||
"logging.level",
|
||||
"logging.retention_days",
|
||||
"logging.query_log_buffer_max",
|
||||
"logging.hide_domains",
|
||||
"logging.hide_client_ips",
|
||||
"logging.output",
|
||||
"logging.file_path",
|
||||
"logging.max_size_mb",
|
||||
"logging.max_files",
|
||||
"disk.min_free_mb",
|
||||
"disk.warn_free_mb",
|
||||
"blocklist_update.enabled",
|
||||
"blocklist_update.interval_hours",
|
||||
],
|
||||
settings: {
|
||||
blocking: {
|
||||
response: "zero",
|
||||
ttl: 0,
|
||||
},
|
||||
blocklist_update: {
|
||||
enabled: true,
|
||||
interval_hours: 0,
|
||||
},
|
||||
cache: {
|
||||
negative_ttl_max: 0,
|
||||
size: 0,
|
||||
},
|
||||
disk: {
|
||||
min_free_mb: 0,
|
||||
warn_free_mb: 0,
|
||||
},
|
||||
dns: {
|
||||
bind_ipv4: "0.0.0.0",
|
||||
bind_ipv6: "::",
|
||||
port: 0,
|
||||
rate_limit: 0,
|
||||
rate_window_seconds: 0,
|
||||
},
|
||||
doh_server: {
|
||||
bind: "0.0.0.0",
|
||||
cert_path: "/etc/nxdns/cert.pem",
|
||||
enabled: false,
|
||||
key_path: "/etc/nxdns/key.pem",
|
||||
port: 0,
|
||||
},
|
||||
dot_server: {
|
||||
bind: "0.0.0.0",
|
||||
cert_path: "/etc/nxdns/cert.pem",
|
||||
enabled: false,
|
||||
key_path: "/etc/nxdns/key.pem",
|
||||
port: 0,
|
||||
},
|
||||
edns: {
|
||||
ecs_mode: "strip",
|
||||
},
|
||||
logging: {
|
||||
file_path: "/var/log/nxdns/nxdns.log",
|
||||
hide_client_ips: false,
|
||||
hide_domains: false,
|
||||
level: "info",
|
||||
max_files: 0,
|
||||
max_size_mb: 0,
|
||||
output: "stderr",
|
||||
query_log_buffer_max: 0,
|
||||
retention_days: 0,
|
||||
},
|
||||
upstream: {
|
||||
attempt_timeout_ms: 0,
|
||||
read_timeout_ms: 0,
|
||||
total_timeout_ms: 0,
|
||||
},
|
||||
web: {
|
||||
api_localhost_exempt: true,
|
||||
api_rate_limit_per_min: 0,
|
||||
auth_enabled: false,
|
||||
bind: "0.0.0.0",
|
||||
enabled: true,
|
||||
port: 0,
|
||||
session_ttl_hours: 0,
|
||||
sse_max_connections_per_ip: 0,
|
||||
trusted_proxies: "",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const sample_error_bad_request: ErrorEnvelope = {
|
||||
error: "logging.level: not one of the values this setting accepts",
|
||||
};
|
||||
|
||||
export const sample_error_conflict: ErrorEnvelope = {
|
||||
error: "an upstream with that url already exists",
|
||||
};
|
||||
|
||||
export const sample_error_not_found: ErrorEnvelope = {
|
||||
error: "not found",
|
||||
};
|
||||
|
||||
export const sample_error_unauthorized: ErrorEnvelope = {
|
||||
error: "authentication required",
|
||||
};
|
||||
|
||||
export const sample_error_rate_limited: ErrorEnvelope = {
|
||||
error: "rate limited",
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
//! Embeds the committed contract-sample golden (milestone-17 ruling 5). Module
|
||||
//! root for the `contract_samples` anonymous import (test builds only) — a
|
||||
//! `.ts` file cannot root one, and @embedFile paths resolve relative to this
|
||||
//! file. The `docs/docs.zig` pattern.
|
||||
|
||||
pub const bytes = @embedFile("contractSamples.gen.ts");
|
||||
|
||||
/// Repo-relative path, so a failing assertion names the file to regenerate.
|
||||
pub const path = "web/src/lib/contractSamples.gen.ts";
|
||||
@@ -184,11 +184,6 @@ export const ruleCreateMutation = (qc: QueryClient) => ({
|
||||
onSuccess: () => invalidateRules(qc),
|
||||
});
|
||||
|
||||
export const ruleUpdateMutation = (qc: QueryClient) => ({
|
||||
mutationFn: ({ id, input }: { id: number; input: RuleInput }) => api.updateRule(id, input),
|
||||
onSuccess: () => invalidateRules(qc),
|
||||
});
|
||||
|
||||
export const ruleDeleteMutation = (qc: QueryClient) => ({
|
||||
mutationFn: (id: number) => api.deleteRule(id),
|
||||
onSuccess: () => invalidateRules(qc),
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { Settings } from "@/lib/types";
|
||||
|
||||
function baseSettings(): Settings {
|
||||
return {
|
||||
upstream: { read_timeout_ms: 3000, total_timeout_ms: 5000 },
|
||||
upstream: { attempt_timeout_ms: 2500, read_timeout_ms: 3000, total_timeout_ms: 5000 },
|
||||
dns: { bind_ipv4: "0.0.0.0", bind_ipv6: "::", port: 53, rate_limit: 100, rate_window_seconds: 60 },
|
||||
blocking: { response: "zero", ttl: 300 },
|
||||
cache: { size: 10000, negative_ttl_max: 300 },
|
||||
@@ -15,6 +15,7 @@ function baseSettings(): Settings {
|
||||
api_rate_limit_per_min: 60,
|
||||
api_localhost_exempt: true,
|
||||
sse_max_connections_per_ip: 2,
|
||||
trusted_proxies: "",
|
||||
auth_enabled: true,
|
||||
},
|
||||
doh_server: { enabled: false, bind: "0.0.0.0", port: 443, cert_path: "", key_path: "" },
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
|
||||
export type Period = "1h" | "24h" | "7d" | "30d";
|
||||
|
||||
/**
|
||||
* Every non-2xx JSON response: `http_util.respondError` writes this one field
|
||||
* and nothing else. `ApiError.message` in api.ts reads `error` out of it.
|
||||
*/
|
||||
export interface ErrorEnvelope {
|
||||
error: string;
|
||||
}
|
||||
|
||||
export interface Health {
|
||||
status: "ok" | "degraded";
|
||||
disk: {
|
||||
@@ -314,6 +322,7 @@ export interface TlsListenerSettings {
|
||||
|
||||
export interface Settings {
|
||||
upstream: {
|
||||
attempt_timeout_ms: number;
|
||||
read_timeout_ms: number;
|
||||
total_timeout_ms: number;
|
||||
};
|
||||
@@ -340,6 +349,8 @@ export interface Settings {
|
||||
api_rate_limit_per_min: number;
|
||||
api_localhost_exempt: boolean;
|
||||
sse_max_connections_per_ip: number;
|
||||
/** Comma-separated IP literals; empty trusts no proxy's X-Forwarded-For. */
|
||||
trusted_proxies: string;
|
||||
/** Derived, read-only; true iff a password hash is stored. Never sent back. */
|
||||
auth_enabled: boolean;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user