Files
technitium-console/src/lib/hub-all.tsx
T
DenozordecandClaude Opus 5.5 34de910e04 Hub stage 6: blocking and cache actions on several servers
One endpoint, hub/servers/run (operate), runs a fixed set of actions on
the chosen servers in parallel with each server's admin token: add or
remove a domain in Blocked or Allowed, disable blocking for a time or
enable it, update block lists, replace the block list URLs, flush the
cache or delete a domain from it. Each server gets its own result and
the console offers Retry failed.

In "All servers", Blocking, Cache and Query Logs rows open this flow
instead of being disabled. The hub validates domains, minutes and the
server's list grammar (allow lists with !, switched-off lines with #)
and logs no URL list. Operators can now use the blocking switch.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-31 22:50:09 +07:00

65 lines
2.5 KiB
TypeScript

import { createContext, useContext, useState, type ReactNode } from 'react'
import { runOnServers, type ServerAction, type ServerParams } from '../api/hub-run'
import { RunFlow, pickerServers, type RunRequest } from '../screens/apps/RunFlow'
import { HubWritable } from './hub-write'
/*
Behind a hub, a change that belongs on several servers at once (Blocked and Allowed, the blocking
switch, the block lists, the cache) goes through the same three steps as the apps: pick the
servers, wait, read what happened on each, with "Retry failed". This is the glue:
· `HubServerIds`: every server the hub fronts, given by the shell. The picker lists all of them
and disables the ones the hub holds no admin token for, with the reason.
· `useAllServersFlow`: `start(spec)` opens the flow, `flow` is the element to render.
· `HubAll`: what a section provides to the controls inside it when "All servers" is chosen and
the role may make the change. A control that finds `null` there behaves as it always did.
*/
export const HubServerIds = createContext<readonly string[]>([])
export interface AllSpec {
action: ServerAction
params?: ServerParams
title: string
intro?: ReactNode
confirmLabel?: string
/** The servers ticked at the start; every writable one when absent. */
preselected?: string[]
/** After the flow closes, told whether any server changed. */
onDone?: (changed: boolean) => void
}
export type StartAll = (spec: AllSpec) => void
export const HubAll = createContext<StartAll | null>(null)
export function useAllServersFlow(token: string | null): { start: StartAll; flow: ReactNode } {
const ids = useContext(HubServerIds)
const writable = useContext(HubWritable)
const [open, setOpen] = useState<{ spec: AllSpec; request: RunRequest } | null>(null)
const start: StartAll = (spec) => {
const request: RunRequest = {
title: spec.title,
intro: spec.intro,
confirmLabel: spec.confirmLabel ?? 'Run',
servers: pickerServers(ids.map((id) => ({ id, writable: writable.includes(id) }))),
preselected: spec.preselected ?? ids.filter((id) => writable.includes(id)),
run: (targets) => runOnServers(token, spec.action, spec.params ?? {}, targets),
}
setOpen({ spec, request })
}
const flow =
open === null ? null : (
<RunFlow
request={open.request}
onClose={(changed) => {
const done = open.spec.onDone
setOpen(null)
done?.(changed)
}}
/>
)
return { start, flow }
}