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 <noreply@anthropic.com>
This commit is contained in:
1 parent
5f29a438c9
commit
48b5d9fb15
12 files changed
+360
-23
No files matched your search
@@ -0,0 +1,86 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { Logs } from './Logs'
|
||||
import * as logs from '../../api/logs'
|
||||
import * as apps from '../../api/apps'
|
||||
import type { QueryLogEntry } from '../../api/logs'
|
||||
import { HandoffContext } from '../../app/handoff'
|
||||
|
||||
/* The hub tags the rows it merged with the server they came from. */
|
||||
|
||||
afterEach(() => vi.restoreAllMocks())
|
||||
|
||||
function entry(rowNumber: number, extra: Partial<QueryLogEntry> = {}): QueryLogEntry {
|
||||
return {
|
||||
rowNumber,
|
||||
timestamp: '2026-08-26T05:32:14Z',
|
||||
clientIpAddress: '10.0.0.7',
|
||||
protocol: 'Udp',
|
||||
responseType: 'Recursive',
|
||||
rcode: 'NoError',
|
||||
qname: `r${rowNumber}.example`,
|
||||
qtype: 'A',
|
||||
qclass: 'IN',
|
||||
answer: null,
|
||||
...extra,
|
||||
}
|
||||
}
|
||||
|
||||
async function show(entries: QueryLogEntry[]) {
|
||||
vi.spyOn(apps, 'listApps').mockResolvedValue({
|
||||
kind: 'ok',
|
||||
data: {
|
||||
status: 'ok',
|
||||
response: {
|
||||
apps: [
|
||||
{
|
||||
name: 'Query Logs (Sqlite)',
|
||||
description: '',
|
||||
version: '8.0',
|
||||
dnsApps: [
|
||||
{
|
||||
classPath: 'QueryLogsSqlite.App',
|
||||
description: '',
|
||||
recordDataTemplate: null,
|
||||
isAppRecordRequestHandler: false,
|
||||
isRequestController: false,
|
||||
isAuthoritativeRequestHandler: false,
|
||||
isRequestBlockingHandler: false,
|
||||
isQueryLogger: true,
|
||||
isQueryLogs: true,
|
||||
isPostProcessor: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
} as never)
|
||||
vi.spyOn(logs, 'queryLogs').mockResolvedValue({
|
||||
kind: 'ok',
|
||||
data: { response: { pageNumber: 1, totalPages: 1, totalEntries: entries.length, entries } },
|
||||
} as never)
|
||||
render(
|
||||
<HandoffContext.Provider value={{ queryDnsServer: vi.fn(), showQueryLogs: vi.fn() }}>
|
||||
<Logs token="t" sub="Query Logs" />
|
||||
</HandoffContext.Provider>,
|
||||
)
|
||||
await userEvent.click(await screen.findByRole('button', { name: 'Query' }))
|
||||
await screen.findByText('r1.example')
|
||||
}
|
||||
|
||||
describe('Query Logs: the Server column', () => {
|
||||
it('appears, after Timestamp, when rows carry a node', async () => {
|
||||
await show([entry(1, { node: 'dns' }), entry(2, { node: 'ndns' })])
|
||||
const headers = screen.getAllByRole('columnheader').map((h) => h.textContent)
|
||||
expect(headers.indexOf('Server')).toBe(headers.indexOf('Timestamp') + 1)
|
||||
expect(screen.getByText('dns')).toBeInTheDocument()
|
||||
expect(screen.getByText('ndns')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('is absent when no row has one', async () => {
|
||||
await show([entry(1), entry(2)])
|
||||
expect(screen.queryByRole('columnheader', { name: 'Server' })).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
@@ -948,6 +948,15 @@ export function QueryLogs({ tabs, token, node = '', request }: QueryLogsProps) {
|
||||
td: () => ({ className: `${styles.mono} ${styles.stamp}` }),
|
||||
cell: (e) => (<Stamp text={dateTime(e.timestamp)} />),
|
||||
},
|
||||
/* Only the hub tags rows with the server they came from. */
|
||||
...(page.entries.some((e) => e.node != null)
|
||||
? [{
|
||||
id: 'cNode',
|
||||
header: 'Server',
|
||||
td: () => ({ className: styles.mono }),
|
||||
cell: (e: QueryLogEntry) => e.node ?? '',
|
||||
}]
|
||||
: []),
|
||||
{
|
||||
id: 'c2',
|
||||
header: 'Client IP Address',
|
||||
|
||||
Reference in new issue
Block a user