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]>
65 lines
2.5 KiB
TypeScript
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 }
|
|
}
|