diff --git a/.env b/.env new file mode 100644 index 0000000..7972a46 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +VITE_API_BASE_URL=http://127.0.0.1:8000 \ No newline at end of file diff --git a/src/App.css b/src/App.css index dacf55e..c6a506f 100644 --- a/src/App.css +++ b/src/App.css @@ -355,6 +355,7 @@ } .register-form { + margin-bottom: 22px; border: 1px solid var(--border); border-radius: 8px; background: var(--bg); @@ -400,6 +401,49 @@ margin-top: 22px; } +.nodes-table-wrap { + overflow-x: auto; + border: 1px solid var(--border); + border-radius: 8px; + background: var(--bg); +} + +.nodes-table { + width: 100%; + min-width: 720px; + border-collapse: collapse; + color: var(--text-h); + font-size: 14px; +} + +.nodes-table th, +.nodes-table td { + border-bottom: 1px solid var(--border); + padding: 13px 14px; + text-align: left; + white-space: nowrap; +} + +.nodes-table th { + background: var(--surface); + color: var(--text); + font-size: 12px; + font-weight: 700; + letter-spacing: 0; + text-transform: uppercase; +} + +.nodes-table tbody tr:last-child td { + border-bottom: 0; +} + +.table-state { + margin: 0; + color: var(--text); + font-size: 15px; + padding: 18px; +} + .eyebrow { margin-bottom: 12px; color: var(--accent); diff --git a/src/App.tsx b/src/App.tsx index 0af3de1..8629521 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,6 +1,8 @@ -import { useState } from 'react' +import { useEffect, useState } from 'react' import './App.css' +const API_BASE_URL = import.meta.env.VITE_API_BASE_URL + const navItems = [ { id: 'dashboard', label: 'Dashboard' }, { id: 'nodes', label: 'Nodes' }, @@ -11,6 +13,7 @@ type PageId = (typeof navItems)[number]['id'] type Theme = 'light' | 'dark' type TokenStatus = 'idle' | 'loading' | 'success' | 'error' type RegisterStatus = 'idle' | 'loading' | 'success' | 'error' +type NodesStatus = 'idle' | 'loading' | 'success' | 'error' type RegisterNodePayload = { node_name: string @@ -21,6 +24,28 @@ type RegisterNodePayload = { password: string } +type RegisterNodeResponse = { + status: string + message?: string +} + +type NodeInterface = { + if_id: number + if_name: string + if_nodeid: number + host: string + port: number + dbname: string + user: string +} + +type NodeInterfacesResponse = { + status: string + count: number + nodes: NodeInterface[] + message?: string +} + const getSavedTheme = (): Theme => { try { return localStorage.getItem('theme') === 'dark' ? 'dark' : 'light' @@ -58,7 +83,7 @@ const savePage = (page: PageId) => { } const getSessionToken = async () => { - const response = await fetch('http://127.0.0.1:8000/get-token', { + const response = await fetch(`${API_BASE_URL}/get-token`, { method: 'POST', }) @@ -84,7 +109,9 @@ const getSessionToken = async () => { return storedToken } -const registerNode = async (payload: RegisterNodePayload) => { +const registerNode = async ( + payload: RegisterNodePayload, +): Promise => { const accessToken = localStorage.getItem('access_token') const headers: Record = { 'Content-Type': 'application/json', @@ -94,7 +121,7 @@ const registerNode = async (payload: RegisterNodePayload) => { headers.Authorization = `Bearer ${accessToken}` } - const response = await fetch('http://127.0.0.1:8000/register-node', { + const response = await fetch(`${API_BASE_URL}/register-node`, { method: 'POST', headers, body: JSON.stringify(payload), @@ -104,7 +131,27 @@ const registerNode = async (payload: RegisterNodePayload) => { throw new Error('Failed to register node') } - return response.json() + return response.json() as Promise +} + +const getNodeInterfaces = async (): Promise => { + const accessToken = localStorage.getItem('access_token') + const headers: Record = {} + + if (accessToken) { + headers.Authorization = `Bearer ${accessToken}` + } + + const response = await fetch(`${API_BASE_URL}/spock/node-interfaces`, { + method: 'GET', + headers, + }) + + if (!response.ok) { + throw new Error('Failed to get node interfaces') + } + + return response.json() as Promise } function App() { @@ -116,8 +163,42 @@ function App() { const [tokenError, setTokenError] = useState('') const [registerStatus, setRegisterStatus] = useState('idle') const [registerError, setRegisterError] = useState('') + const [nodes, setNodes] = useState([]) + const [nodesStatus, setNodesStatus] = useState('idle') + const [nodesError, setNodesError] = useState('') const nextTheme = theme === 'light' ? 'dark' : 'light' + const loadNodeInterfaces = async () => { + setNodesStatus('loading') + setNodesError('') + + try { + const nodeInterfacesResponse = await getNodeInterfaces() + + if (nodeInterfacesResponse.status !== 'success') { + throw new Error( + nodeInterfacesResponse.message ?? 'Failed to get node interfaces', + ) + } + + setNodes(nodeInterfacesResponse.nodes) + setNodesStatus('success') + } catch (error) { + setNodesStatus('error') + setNodesError( + error instanceof Error + ? error.message + : 'Failed to get node interfaces', + ) + } + } + + useEffect(() => { + if (activePage === 'nodes') { + void loadNodeInterfaces() + } + }, [activePage]) + const selectPage = (page: PageId) => { setActivePage(page) savePage(page) @@ -140,6 +221,7 @@ function App() { try { await getSessionToken() setTokenStatus('success') + await loadNodeInterfaces() } catch (error) { setTokenStatus('error') setTokenError( @@ -152,10 +234,12 @@ function App() { event: React.FormEvent, ) => { event.preventDefault() + const form = event.currentTarget + setRegisterStatus('loading') setRegisterError('') - const formData = new FormData(event.currentTarget) + const formData = new FormData(form) const payload: RegisterNodePayload = { node_name: String(formData.get('node_name') ?? ''), host: String(formData.get('host') ?? ''), @@ -170,9 +254,14 @@ function App() { console.log('Node registration response:', registerResponse) + if (registerResponse.status !== 'success') { + throw new Error(registerResponse.message ?? 'Failed to register node') + } + setRegisterStatus('success') - event.currentTarget.reset() + form.reset() setIsRegisterFormOpen(false) + window.location.reload() } catch (error) { setRegisterStatus('error') setRegisterError( @@ -289,6 +378,10 @@ function App() {

{registerError}

)} + {nodesStatus === 'error' && ( +

{nodesError}

+ )} + {isRegisterFormOpen ? (
@@ -348,6 +441,39 @@ function App() {
) : null} + +
+ {nodesStatus === 'loading' ? ( +

Loading nodes...

+ ) : nodes.length > 0 ? ( + + + + + + + + + + + + + {nodes.map((node) => ( + + + + + + + + + ))} + +
Node IDNode NameHostPortDB NameDB Username
{node.if_nodeid}{node.if_name}{node.host}{node.port}{node.dbname}{node.user}
+ ) : ( +

No nodes found.

+ )} +
)}