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 {
|
.register-form {
|
||||||
|
margin-bottom: 22px;
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background: var(--bg);
|
background: var(--bg);
|
||||||
@@ -400,6 +401,49 @@
|
|||||||
margin-top: 22px;
|
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 {
|
.eyebrow {
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
color: var(--accent);
|
color: var(--accent);
|
||||||
|
|||||||
+133
-7
@@ -1,6 +1,8 @@
|
|||||||
import { useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import './App.css'
|
import './App.css'
|
||||||
|
|
||||||
|
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL
|
||||||
|
|
||||||
const navItems = [
|
const navItems = [
|
||||||
{ id: 'dashboard', label: 'Dashboard' },
|
{ id: 'dashboard', label: 'Dashboard' },
|
||||||
{ id: 'nodes', label: 'Nodes' },
|
{ id: 'nodes', label: 'Nodes' },
|
||||||
@@ -11,6 +13,7 @@ type PageId = (typeof navItems)[number]['id']
|
|||||||
type Theme = 'light' | 'dark'
|
type Theme = 'light' | 'dark'
|
||||||
type TokenStatus = 'idle' | 'loading' | 'success' | 'error'
|
type TokenStatus = 'idle' | 'loading' | 'success' | 'error'
|
||||||
type RegisterStatus = 'idle' | 'loading' | 'success' | 'error'
|
type RegisterStatus = 'idle' | 'loading' | 'success' | 'error'
|
||||||
|
type NodesStatus = 'idle' | 'loading' | 'success' | 'error'
|
||||||
|
|
||||||
type RegisterNodePayload = {
|
type RegisterNodePayload = {
|
||||||
node_name: string
|
node_name: string
|
||||||
@@ -21,6 +24,28 @@ type RegisterNodePayload = {
|
|||||||
password: string
|
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 => {
|
const getSavedTheme = (): Theme => {
|
||||||
try {
|
try {
|
||||||
return localStorage.getItem('theme') === 'dark' ? 'dark' : 'light'
|
return localStorage.getItem('theme') === 'dark' ? 'dark' : 'light'
|
||||||
@@ -58,7 +83,7 @@ const savePage = (page: PageId) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getSessionToken = async () => {
|
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',
|
method: 'POST',
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -84,7 +109,9 @@ const getSessionToken = async () => {
|
|||||||
return storedToken
|
return storedToken
|
||||||
}
|
}
|
||||||
|
|
||||||
const registerNode = async (payload: RegisterNodePayload) => {
|
const registerNode = async (
|
||||||
|
payload: RegisterNodePayload,
|
||||||
|
): Promise<RegisterNodeResponse> => {
|
||||||
const accessToken = localStorage.getItem('access_token')
|
const accessToken = localStorage.getItem('access_token')
|
||||||
const headers: Record<string, string> = {
|
const headers: Record<string, string> = {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
@@ -94,7 +121,7 @@ const registerNode = async (payload: RegisterNodePayload) => {
|
|||||||
headers.Authorization = `Bearer ${accessToken}`
|
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',
|
method: 'POST',
|
||||||
headers,
|
headers,
|
||||||
body: JSON.stringify(payload),
|
body: JSON.stringify(payload),
|
||||||
@@ -104,7 +131,27 @@ const registerNode = async (payload: RegisterNodePayload) => {
|
|||||||
throw new Error('Failed to register node')
|
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() {
|
function App() {
|
||||||
@@ -116,8 +163,42 @@ function App() {
|
|||||||
const [tokenError, setTokenError] = useState('')
|
const [tokenError, setTokenError] = useState('')
|
||||||
const [registerStatus, setRegisterStatus] = useState<RegisterStatus>('idle')
|
const [registerStatus, setRegisterStatus] = useState<RegisterStatus>('idle')
|
||||||
const [registerError, setRegisterError] = useState('')
|
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 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) => {
|
const selectPage = (page: PageId) => {
|
||||||
setActivePage(page)
|
setActivePage(page)
|
||||||
savePage(page)
|
savePage(page)
|
||||||
@@ -140,6 +221,7 @@ function App() {
|
|||||||
try {
|
try {
|
||||||
await getSessionToken()
|
await getSessionToken()
|
||||||
setTokenStatus('success')
|
setTokenStatus('success')
|
||||||
|
await loadNodeInterfaces()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setTokenStatus('error')
|
setTokenStatus('error')
|
||||||
setTokenError(
|
setTokenError(
|
||||||
@@ -152,10 +234,12 @@ function App() {
|
|||||||
event: React.FormEvent<HTMLFormElement>,
|
event: React.FormEvent<HTMLFormElement>,
|
||||||
) => {
|
) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
const form = event.currentTarget
|
||||||
|
|
||||||
setRegisterStatus('loading')
|
setRegisterStatus('loading')
|
||||||
setRegisterError('')
|
setRegisterError('')
|
||||||
|
|
||||||
const formData = new FormData(event.currentTarget)
|
const formData = new FormData(form)
|
||||||
const payload: RegisterNodePayload = {
|
const payload: RegisterNodePayload = {
|
||||||
node_name: String(formData.get('node_name') ?? ''),
|
node_name: String(formData.get('node_name') ?? ''),
|
||||||
host: String(formData.get('host') ?? ''),
|
host: String(formData.get('host') ?? ''),
|
||||||
@@ -170,9 +254,14 @@ function App() {
|
|||||||
|
|
||||||
console.log('Node registration response:', registerResponse)
|
console.log('Node registration response:', registerResponse)
|
||||||
|
|
||||||
|
if (registerResponse.status !== 'success') {
|
||||||
|
throw new Error(registerResponse.message ?? 'Failed to register node')
|
||||||
|
}
|
||||||
|
|
||||||
setRegisterStatus('success')
|
setRegisterStatus('success')
|
||||||
event.currentTarget.reset()
|
form.reset()
|
||||||
setIsRegisterFormOpen(false)
|
setIsRegisterFormOpen(false)
|
||||||
|
window.location.reload()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setRegisterStatus('error')
|
setRegisterStatus('error')
|
||||||
setRegisterError(
|
setRegisterError(
|
||||||
@@ -289,6 +378,10 @@ function App() {
|
|||||||
<p className="status-message error">{registerError}</p>
|
<p className="status-message error">{registerError}</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{nodesStatus === 'error' && (
|
||||||
|
<p className="status-message error">{nodesError}</p>
|
||||||
|
)}
|
||||||
|
|
||||||
{isRegisterFormOpen ? (
|
{isRegisterFormOpen ? (
|
||||||
<form className="register-form" onSubmit={handleRegisterNode}>
|
<form className="register-form" onSubmit={handleRegisterNode}>
|
||||||
<div className="form-grid">
|
<div className="form-grid">
|
||||||
@@ -348,6 +441,39 @@ function App() {
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
) : null}
|
) : 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>
|
</section>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user