With info.hub the node selector reads Server / All servers, the default-credentials password prompt and the account actions the hub cannot serve are gone, the header status reports the servers an aggregate could not reach (hubWarnings, kept until the next aggregate), and merged Query Logs show a Server column. Without the flag nothing changes. The hub sends hubWarnings on every aggregate answer, empty when all servers answered, and never on a single-node one. Co-Authored-By: Claude Opus 5.5 <[email protected]>
110 lines
3.8 KiB
TypeScript
110 lines
3.8 KiB
TypeScript
import { createContext, useContext } from 'react'
|
|
import { Select } from './Select'
|
|
import { Field } from './Field'
|
|
|
|
/*
|
|
This server's name (`dnsServerDomain`), which the shell provides. With no node
|
|
chosen, upstream's dropdown selects it (`updateClusterNodeDropDown`,
|
|
cluster.js:1041-1051). The value a screen holds stays empty, which the server
|
|
takes as this node, so only what the dropdown shows changes: this node's name
|
|
instead of the empty-value dash.
|
|
*/
|
|
export const ThisServerName = createContext('')
|
|
|
|
/*
|
|
True when the console talks to a hub (`info.hub` in the session) and not to a
|
|
Technitium server. The "nodes" are then independent servers, so the words change:
|
|
"All servers" for the aggregate, "Server" for the selector, and no `(primary)` tag.
|
|
*/
|
|
export const HubMode = createContext(false)
|
|
|
|
/*
|
|
Which cluster node a screen is looking at.
|
|
|
|
Upstream puts one of these on ten surfaces (`cluster.js`,
|
|
`updateAllClusterNodeDropDowns`) and this console had it on none of them. It
|
|
is not decoration: the server proxies the whole request to the chosen
|
|
node (`DnsWebService.cs:2378`), so without it a console can only ever show the
|
|
node it happens to be served from.
|
|
|
|
Three rules, all upstream's:
|
|
|
|
· It exists only when the server says `clusterInitialized`. On a single-server
|
|
install there is nothing to choose and nothing is drawn.
|
|
· Two of the ten (Dashboard and Settings) also offer **Cluster**, the aggregate,
|
|
and it is their default. The other eight start on this server.
|
|
· A node is listed as `name (type)`, with the type lowercased.
|
|
*/
|
|
|
|
export interface ClusterNode {
|
|
name: string
|
|
type: string
|
|
}
|
|
|
|
/** Upstream's word for the aggregate. The API client does not send it. */
|
|
export const AGGREGATE = 'cluster'
|
|
|
|
export function nodeOptions(
|
|
nodes: ClusterNode[],
|
|
{ aggregate = false, hub = false }: { aggregate?: boolean; hub?: boolean } = {},
|
|
): { value: string; label: string }[] {
|
|
const out = aggregate ? [{ value: AGGREGATE, label: hub ? 'All servers' : 'Cluster' }] : []
|
|
for (const n of nodes) out.push({ value: n.name, label: hub ? n.name : `${n.name} (${n.type.toLowerCase()})` })
|
|
return out
|
|
}
|
|
|
|
/** What a screen shows before anyone chooses: the aggregate where there is one,
|
|
* and this server everywhere else. */
|
|
export function defaultNode(aggregate: boolean, thisServer: string): string {
|
|
return aggregate ? AGGREGATE : thisServer
|
|
}
|
|
|
|
export function ClusterNodeSelect({
|
|
nodes,
|
|
initialised,
|
|
aggregate = false,
|
|
value,
|
|
onChange,
|
|
label = 'Node',
|
|
disabled = false,
|
|
}: {
|
|
nodes: ClusterNode[]
|
|
initialised: boolean
|
|
aggregate?: boolean
|
|
value: string
|
|
onChange: (node: string) => void
|
|
label?: string
|
|
/** Upstream disables it where the node is already fixed (a zone open on one). */
|
|
disabled?: boolean
|
|
}) {
|
|
const thisServer = useContext(ThisServerName)
|
|
const hub = useContext(HubMode)
|
|
if (!initialised) return null
|
|
|
|
/* Only this server's own name stands in for the empty value: it is the node the
|
|
empty value means. Upstream's fallback to the first node would also SEND that
|
|
node, so it is not drawn here without being sent. */
|
|
const shown = value === '' && !aggregate && nodes.some((n) => n.name === thisServer) ? thisServer : value
|
|
|
|
return (
|
|
<Field label={hub ? 'Server' : label}>
|
|
{(id) => (
|
|
<Select id={id} value={shown} disabled={disabled} onChange={(e) => onChange(e.target.value)}>
|
|
{nodeOptions(nodes, { aggregate, hub }).map((o) => (
|
|
<option key={o.value} value={o.value}>
|
|
{o.label}
|
|
</option>
|
|
))}
|
|
</Select>
|
|
)}
|
|
</Field>
|
|
)
|
|
}
|
|
|
|
/* `getPrimaryClusterNodeName` (cluster.js:997): the node whose type is `Primary`,
|
|
or empty when the cluster is not initialised. */
|
|
export function primaryNodeName(nodes: readonly ClusterNode[], initialised: boolean): string {
|
|
if (!initialised) return ''
|
|
return nodes.find((n) => n.type === 'Primary')?.name ?? ''
|
|
}
|