milestone 32: task-shaped configuration, file mode as a rendering, config status api
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
import { infiniteQueryOptions, keepPreviousData, queryOptions, type QueryClient } from "@tanstack/react-query";
|
||||
import * as api from "@/lib/api";
|
||||
import { setRefreshStatus } from "@/features/blocklists/refreshStore";
|
||||
import type {
|
||||
BlocklistInput,
|
||||
ClientEdit,
|
||||
@@ -46,6 +45,7 @@ export const queryKeys = {
|
||||
clientPrefixes: ["client-prefixes"] as const,
|
||||
upstreams: ["upstreams"] as const,
|
||||
settings: ["settings"] as const,
|
||||
configStatus: ["configStatus"] as const,
|
||||
};
|
||||
|
||||
export const healthQuery = () =>
|
||||
@@ -150,6 +150,19 @@ export const upstreamsQuery = () => queryOptions({ queryKey: queryKeys.upstreams
|
||||
|
||||
export const settingsQuery = () => queryOptions({ queryKey: queryKeys.settings, queryFn: api.getSettings });
|
||||
|
||||
// Restart truth must not depend on the tab that caused it: a notice raised by
|
||||
// another tab, another API client or a mutation elsewhere in this one has to
|
||||
// surface here too, and a process restart has to clear it. Hence never stale,
|
||||
// always refetched when the window regains focus, and polled once a minute.
|
||||
export const configStatusQuery = () =>
|
||||
queryOptions({
|
||||
queryKey: queryKeys.configStatus,
|
||||
queryFn: api.getConfigStatus,
|
||||
staleTime: 0,
|
||||
refetchOnWindowFocus: "always",
|
||||
refetchInterval: 60_000,
|
||||
});
|
||||
|
||||
// Mutation option factories. Usage: useMutation(groupCreateMutation(useQueryClient())).
|
||||
// Group membership and names feed lookup verdicts and the group columns on
|
||||
// clients, prefixes and rules, hence the wide invalidation on group mutations.
|
||||
@@ -223,16 +236,18 @@ export const blocklistDeleteMutation = (qc: QueryClient) => ({
|
||||
onSuccess: () => invalidateBlocklistWorld(qc),
|
||||
});
|
||||
|
||||
/** Ruling 12: the 202 snapshot REPLACES the refresh store; counters refresh. */
|
||||
// A runtime action, not a configuration write: it re-fetches the sources the
|
||||
// running process already declares, so it works under file authority too. The
|
||||
// 202 body's per-source snapshot is deliberately dropped — refresh outcomes are
|
||||
// durable counters on the source rows and episodes in Diagnostics, not an
|
||||
// ephemeral readout that survives one navigation.
|
||||
export const blocklistsUpdateNowMutation = (qc: QueryClient) => ({
|
||||
mutationFn: () => api.updateBlocklistsNow(),
|
||||
onSuccess: (sources: Awaited<ReturnType<typeof api.updateBlocklistsNow>>) => {
|
||||
setRefreshStatus(sources);
|
||||
return Promise.all([
|
||||
onSuccess: () =>
|
||||
Promise.all([
|
||||
qc.invalidateQueries({ queryKey: queryKeys.blocklists }),
|
||||
qc.invalidateQueries({ queryKey: queryKeys.lookupAll }),
|
||||
]);
|
||||
},
|
||||
]),
|
||||
});
|
||||
|
||||
function invalidateRules(qc: QueryClient): Promise<unknown> {
|
||||
@@ -313,8 +328,14 @@ export const clientPrefixesPutMutation = (qc: QueryClient) => ({
|
||||
},
|
||||
});
|
||||
|
||||
// The pool builds its clients at startup, so every upstream write leaves the
|
||||
// server owing a restart. The flag it sets lives on /api/config/status, and the
|
||||
// shell notice reads it there — hence the second invalidation.
|
||||
function invalidateUpstreams(qc: QueryClient): Promise<unknown> {
|
||||
return qc.invalidateQueries({ queryKey: queryKeys.upstreams });
|
||||
return Promise.all([
|
||||
qc.invalidateQueries({ queryKey: queryKeys.upstreams }),
|
||||
qc.invalidateQueries({ queryKey: queryKeys.configStatus }),
|
||||
]);
|
||||
}
|
||||
|
||||
export const upstreamCreateMutation = (qc: QueryClient) => ({
|
||||
@@ -341,10 +362,16 @@ export const pauseMutation = (qc: QueryClient) => ({
|
||||
onSuccess: () => qc.invalidateQueries({ queryKey: queryKeys.health }),
|
||||
});
|
||||
|
||||
// Whether the patch needs a restart is the server's decision (it owns the
|
||||
// restart-required key set), so the client re-reads the status rather than
|
||||
// deciding for itself.
|
||||
export const settingsPutMutation = (qc: QueryClient) => ({
|
||||
mutationFn: (patch: SettingsPatch) => api.putSettings(patch),
|
||||
onSuccess: (envelope: Awaited<ReturnType<typeof api.putSettings>>) => {
|
||||
qc.setQueryData(queryKeys.settings, envelope);
|
||||
return qc.invalidateQueries({ queryKey: queryKeys.settings });
|
||||
return Promise.all([
|
||||
qc.invalidateQueries({ queryKey: queryKeys.settings }),
|
||||
qc.invalidateQueries({ queryKey: queryKeys.configStatus }),
|
||||
]);
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user