feat:
--Added Registered nodes data endpoint
This commit is contained in:
1 parent
0a5899b009
commit
eb16c93fb0
3 files changed
+178
-7
No files matched your search
+44
@@ -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);
|
||||
|
||||
+133
-7
@@ -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<RegisterNodeResponse> => {
|
||||
const accessToken = localStorage.getItem('access_token')
|
||||
const headers: Record<string, string> = {
|
||||
'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<RegisterNodeResponse>
|
||||
}
|
||||
|
||||
const getNodeInterfaces = async (): Promise<NodeInterfacesResponse> => {
|
||||
const accessToken = localStorage.getItem('access_token')
|
||||
const headers: Record<string, string> = {}
|
||||
|
||||
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<NodeInterfacesResponse>
|
||||
}
|
||||
|
||||
function App() {
|
||||
@@ -116,8 +163,42 @@ function App() {
|
||||
const [tokenError, setTokenError] = useState('')
|
||||
const [registerStatus, setRegisterStatus] = useState<RegisterStatus>('idle')
|
||||
const [registerError, setRegisterError] = useState('')
|
||||
const [nodes, setNodes] = useState<NodeInterface[]>([])
|
||||
const [nodesStatus, setNodesStatus] = useState<NodesStatus>('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<HTMLFormElement>,
|
||||
) => {
|
||||
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() {
|
||||
<p className="status-message error">{registerError}</p>
|
||||
)}
|
||||
|
||||
{nodesStatus === 'error' && (
|
||||
<p className="status-message error">{nodesError}</p>
|
||||
)}
|
||||
|
||||
{isRegisterFormOpen ? (
|
||||
<form className="register-form" onSubmit={handleRegisterNode}>
|
||||
<div className="form-grid">
|
||||
@@ -348,6 +441,39 @@ function App() {
|
||||
</div>
|
||||
</form>
|
||||
) : null}
|
||||
|
||||
<div className="nodes-table-wrap">
|
||||
{nodesStatus === 'loading' ? (
|
||||
<p className="table-state">Loading nodes...</p>
|
||||
) : nodes.length > 0 ? (
|
||||
<table className="nodes-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Node ID</th>
|
||||
<th>Node Name</th>
|
||||
<th>Host</th>
|
||||
<th>Port</th>
|
||||
<th>DB Name</th>
|
||||
<th>DB Username</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{nodes.map((node) => (
|
||||
<tr key={node.if_id}>
|
||||
<td>{node.if_nodeid}</td>
|
||||
<td>{node.if_name}</td>
|
||||
<td>{node.host}</td>
|
||||
<td>{node.port}</td>
|
||||
<td>{node.dbname}</td>
|
||||
<td>{node.user}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
) : (
|
||||
<p className="table-state">No nodes found.</p>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
|
||||
Reference in new issue
Block a user