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 ( {(id) => ( )} ) } /* `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 ?? '' }