(null)
- const [status, setStatus] = useState('Connecting to replication health…')
+ const [status, setStatus] = useState('Loading node connection details…')
const [error, setError] = useState('')
const [attempt, setAttempt] = useState(0)
useEffect(() => {
let active = true
- let socket: WebSocket
+ let socket: WebSocket | undefined
+ const controller = new AbortController()
- try {
- const baseUrl = import.meta.env.VITE_WS_BASE_URL
- if (!baseUrl) throw new Error('VITE_WS_BASE_URL is not configured.')
- socket = new WebSocket(`${baseUrl.replace(/\/$/, '')}/ws/node/replication-health`)
- } catch (cause) {
- setStatus('')
- setError(cause instanceof Error ? cause.message : 'Unable to open replication health connection.')
- return
- }
-
- socket.onopen = () => {
- if (!active) return
- setStatus('Waiting for replication health…')
- // Temporary connection details until the API supports the selected node.
- socket.send(JSON.stringify({
- node_name: 'node3',
- host: '172.16.10.178',
- port: 5444,
- dbname: 'postgres',
- user: 'postgres',
- password: 'postgres',
- }))
- }
- socket.onmessage = (event) => {
- if (!active) return
+ const connect = async () => {
try {
- const data: unknown = JSON.parse(event.data)
- if (!isReplicationHealth(data)) throw new Error('Invalid replication health response.')
- setHealth(data)
- setError('')
- setStatus('Live updates connected')
- } catch {
- setHealth(null)
+ const apiBaseUrl = import.meta.env.VITE_API_BASE_URL
+ const baseUrl = import.meta.env.VITE_WS_BASE_URL
+ if (!apiBaseUrl) throw new Error('VITE_API_BASE_URL is not configured.')
+ if (!baseUrl) throw new Error('VITE_WS_BASE_URL is not configured.')
+
+ const token = localStorage.getItem('access_token')
+ const response = await fetch(
+ `${apiBaseUrl.replace(/\/$/, '')}/spock/node-interfaces/${encodeURIComponent(nodeId)}`,
+ {
+ headers: token ? { Authorization: `Bearer ${token}` } : {},
+ signal: controller.signal,
+ },
+ )
+ if (!response.ok) {
+ throw new Error(response.status === 404
+ ? 'Node not found.'
+ : `Failed to load node connection details (${response.status}).`)
+ }
+ const data = await response.json()
+ const node = data?.node
+ if (data?.status !== 'success' || !node
+ || node.uuid !== nodeId
+ || typeof node.if_name !== 'string'
+ || typeof node.host !== 'string'
+ || typeof node.port !== 'number'
+ || typeof node.dbname !== 'string'
+ || typeof node.user !== 'string') {
+ throw new Error('Invalid node connection details response.')
+ }
+ if (!active) return
+
+ setStatus('Connecting to replication health…')
+ const connection = new WebSocket(`${baseUrl.replace(/\/$/, '')}/ws/node/replication-health`)
+ socket = connection
+ connection.onopen = () => {
+ if (!active) return
+ setStatus('Waiting for replication health…')
+ connection.send(JSON.stringify({
+ node_name: node.if_name,
+ host: node.host,
+ port: node.port,
+ dbname: node.dbname,
+ user: node.user,
+ password: 'postgres',
+ }))
+ }
+ connection.onmessage = (event) => {
+ if (!active) return
+ try {
+ const data: unknown = JSON.parse(event.data)
+ if (!isReplicationHealth(data)) throw new Error('Invalid replication health response.')
+ setHealth(data)
+ setError('')
+ setStatus('Live updates connected')
+ } catch {
+ setHealth(null)
+ setStatus('')
+ setError('Received an invalid replication health response.')
+ }
+ }
+ connection.onerror = () => {
+ if (!active) return
+ setHealth(null)
+ setStatus('')
+ setError('Unable to receive replication health. Check that the WebSocket server is running.')
+ }
+ connection.onclose = () => {
+ if (!active) return
+ setHealth(null)
+ setStatus('')
+ setError((current) => current || 'Replication health connection closed. Reconnect to receive updates.')
+ }
+ } catch (cause) {
+ if (!active) return
setStatus('')
- setError('Received an invalid replication health response.')
+ setError(cause instanceof Error ? cause.message : 'Unable to load node connection details.')
}
}
- socket.onerror = () => {
- if (!active) return
- setHealth(null)
- setStatus('')
- setError('Unable to receive replication health. Check that the WebSocket server is running.')
- }
- socket.onclose = () => {
- if (!active) return
- setHealth(null)
- setStatus('')
- setError((current) => current || 'Replication health connection closed. Reconnect to receive updates.')
- }
+ void connect()
return () => {
active = false
- socket.close()
+ controller.abort()
+ socket?.close()
}
- }, [attempt])
+ }, [attempt, nodeId])
return (
@@ -101,7 +135,7 @@ export default function NodeConnections() {