Files
technitium-console/src/screens/blocking/AllServersActions.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

94 lines
3.6 KiB
TypeScript

import { useContext, useState } from 'react'
import { useAllServersFlow } from '../../lib/hub-all'
import { Button } from '../../ui/Button'
import { AskDialog } from '../../ui/AskDialog'
import { HubMode } from '../../ui/ClusterNodeSelect'
/*
Behind a hub: the block list actions that go to several servers. "Update lists" asks every chosen
server to download its lists now; "Edit list URLs" REPLACES the list of URLs on every chosen
server with the one typed here (one per line), so the dialog says so. The
caller shows this only to a role that may change blocking. Drawn nowhere outside a hub.
*/
/*
What the dialog starts with: the lines of the chosen server's list as it holds them, `!` allow
lists, `#` switched-off lists and headings included, so saving it back changes nothing.
*/
export function plainUrls(lines: readonly string[] | null | undefined): string {
return (lines ?? []).map((l) => l.trim()).filter((l) => l !== '').join('\n')
}
/* The hub's rules (hub/servers-run.ts `cleanUrls`), so the dialog and the hub give the same answer. */
export function checkUrls(text: string): string | null {
const lines = text.split(/\r?\n/).map((l) => l.trim()).filter((l) => l !== '')
if (lines.length === 0) return 'Give at least one block list URL.'
if (lines.length > 300) return 'At most 300 lines.'
for (const line of lines) {
if (line.includes(',')) return `A line cannot contain a comma: ${line.slice(0, 60)}`
if (line.startsWith('#')) {
if (line.length > 200) return 'A comment line is too long.'
continue
}
const body = line.startsWith('!') ? line.slice(1).trim() : line
let ok = line.length <= 2000 && !/\s/.test(body)
if (ok) {
try {
ok = ['http:', 'https:', 'file:'].includes(new URL(body).protocol)
} catch {
ok = false
}
}
if (!ok) return `Not a valid http, https or file address: ${line.slice(0, 60)}`
}
return null
}
export function AllServersActions({ token, initialUrls }: { token: string | null; initialUrls?: readonly string[] | null }) {
const hub = useContext(HubMode)
const { start, flow } = useAllServersFlow(token)
const [editing, setEditing] = useState(false)
if (!hub) return null
return (
<>
<Button
onClick={() =>
start({
action: 'blocking.updateLists',
title: 'Update block lists on several servers',
intro: 'Each chosen server downloads its block lists now.',
confirmLabel: 'Update lists',
})
}
>
Update lists on all servers
</Button>
<Button onClick={() => setEditing(true)}>Edit list URLs on all servers</Button>
{editing && (
<AskDialog
title="Edit list URLs on all servers"
label="Block list URLs"
intro="One address per line: a list, `!` before an allow list, `#` before a switched-off list or a heading. This replaces the whole list of block list URLs on every server you choose next."
initial={plainUrls(initialUrls)}
multiline
placeholder="https://example.com/hosts.txt"
confirmLabel="Choose servers"
check={checkUrls}
onClose={() => setEditing(false)}
onSubmit={(text) => {
setEditing(false)
start({
action: 'blocking.setListUrls',
params: { urls: text },
title: 'Replace block list URLs on several servers',
intro: 'The list of URLs on each chosen server is replaced by the one you typed.',
confirmLabel: 'Replace URLs',
})
}}
/>
)}
{flow}
</>
)
}