Hub stage 3: the console in hub mode
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]>
This commit is contained in:
1 parent
5f29a438c9
commit
48b5d9fb15
12 files changed
+360
-23
No files matched your search
@@ -0,0 +1,36 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import { ClusterNodeSelect, HubMode, nodeOptions, AGGREGATE } from './ClusterNodeSelect'
|
||||
|
||||
const NODES = [
|
||||
{ name: 'dns', type: 'Primary' },
|
||||
{ name: 'ndns', type: 'Secondary' },
|
||||
]
|
||||
|
||||
/* Behind a hub the nodes are independent servers, not a cluster. */
|
||||
describe('hub mode', () => {
|
||||
it('names the aggregate "All servers" and lists servers without a type', () => {
|
||||
expect(nodeOptions(NODES, { aggregate: true, hub: true })).toEqual([
|
||||
{ value: AGGREGATE, label: 'All servers' },
|
||||
{ value: 'dns', label: 'dns' },
|
||||
{ value: 'ndns', label: 'ndns' },
|
||||
])
|
||||
})
|
||||
|
||||
it('shows All servers and labels the selector Server, whatever label the screen asks for', () => {
|
||||
render(
|
||||
<HubMode.Provider value>
|
||||
<ClusterNodeSelect nodes={NODES} initialised aggregate value={AGGREGATE} onChange={() => {}} label="Cluster Node" />
|
||||
</HubMode.Provider>,
|
||||
)
|
||||
expect(screen.getByText('All servers')).toBeInTheDocument()
|
||||
expect(screen.getByText('Server')).toBeInTheDocument()
|
||||
expect(screen.queryByText('Cluster Node')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('keeps the cluster wording outside the hub', () => {
|
||||
render(<ClusterNodeSelect nodes={NODES} initialised aggregate value={AGGREGATE} onChange={() => {}} label="Cluster Node" />)
|
||||
expect(screen.getByText('Cluster')).toBeInTheDocument()
|
||||
expect(screen.getByText('Cluster Node')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
@@ -11,6 +11,13 @@ 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.
|
||||
|
||||
@@ -39,10 +46,10 @@ export const AGGREGATE = 'cluster'
|
||||
|
||||
export function nodeOptions(
|
||||
nodes: ClusterNode[],
|
||||
{ aggregate = false }: { aggregate?: boolean } = {},
|
||||
{ aggregate = false, hub = false }: { aggregate?: boolean; hub?: boolean } = {},
|
||||
): { value: string; label: string }[] {
|
||||
const out = aggregate ? [{ value: AGGREGATE, label: 'Cluster' }] : []
|
||||
for (const n of nodes) out.push({ value: n.name, label: `${n.name} (${n.type.toLowerCase()})` })
|
||||
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
|
||||
}
|
||||
|
||||
@@ -71,6 +78,7 @@ export function ClusterNodeSelect({
|
||||
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
|
||||
@@ -79,10 +87,10 @@ export function ClusterNodeSelect({
|
||||
const shown = value === '' && !aggregate && nodes.some((n) => n.name === thisServer) ? thisServer : value
|
||||
|
||||
return (
|
||||
<Field label={label}>
|
||||
<Field label={hub ? 'Server' : label}>
|
||||
{(id) => (
|
||||
<Select id={id} value={shown} disabled={disabled} onChange={(e) => onChange(e.target.value)}>
|
||||
{nodeOptions(nodes, { aggregate }).map((o) => (
|
||||
{nodeOptions(nodes, { aggregate, hub }).map((o) => (
|
||||
<option key={o.value} value={o.value}>
|
||||
{o.label}
|
||||
</option>
|
||||
|
||||
Reference in new issue
Block a user