--Added cluster view page
	--Added cluster node view page
This commit is contained in:
george committed 2026-09-16 15:57:04 +05:30
1 parent 0832a2318a
commit 2f677c36b3
2 files changed
+164 -1

No files matched your search

+37
View File
@@ -468,6 +468,43 @@
white-space: nowrap;
}
.cluster-list {
display: grid;
gap: 10px;
padding: 0;
list-style: none;
}
.cluster-node-list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(180px, 100%), 1fr));
gap: 14px;
padding: 0;
list-style: none;
}
.cluster-node-card {
border: 1px solid var(--border);
border-radius: 8px;
background: var(--surface);
color: var(--text-h);
padding: 20px;
font-weight: 600;
overflow-wrap: anywhere;
}
.cluster-list .secondary-button {
width: 100%;
justify-content: flex-start;
text-align: left;
overflow-wrap: anywhere;
}
.cluster-list .secondary-button[aria-pressed='true'] {
border-color: var(--accent);
background: var(--accent-bg);
}
.cluster-steps {
display: flex;
gap: 32px;
+127 -1
View File
@@ -9,13 +9,39 @@ const navItems = [
{ id: 'clusters', label: 'Clusters' },
] as const
type PageId = (typeof navItems)[number]['id']
type PageId = (typeof navItems)[number]['id'] | 'cluster-details'
type Theme = 'light' | 'dark'
type TokenStatus = 'idle' | 'loading' | 'success' | 'error'
type RegisterStatus = 'idle' | 'loading' | 'success' | 'error'
type NodesStatus = 'idle' | 'loading' | 'success' | 'error'
type ClusterStatus = 'idle' | 'loading' | 'success' | 'error'
type Cluster = {
uuid: string
cluster_name: string
}
type ClusterNodesResponse = {
cluster_name: string
nodes: {
node_name: string
uuid: string
}[]
}
const getClusterResource = async (path: string) => {
const accessToken = localStorage.getItem('access_token')
const response = await fetch(`${API_BASE_URL}/spock/${path}`, {
headers: accessToken ? { Authorization: `Bearer ${accessToken}` } : {},
})
if (!response.ok) {
throw new Error('Failed to load cluster data')
}
return response
}
type RegisterNodePayload = {
node_name: string
host: string
@@ -250,6 +276,13 @@ function App() {
const [deletingNodeId, setDeletingNodeId] = useState<number | null>(null)
const [clusterStatus, setClusterStatus] = useState<ClusterStatus>('idle')
const [clusterError, setClusterError] = useState('')
const [clusters, setClusters] = useState<Cluster[]>([])
const [clustersStatus, setClustersStatus] = useState<ClusterStatus>('idle')
const [clustersError, setClustersError] = useState('')
const [clusterNodesStatus, setClusterNodesStatus] = useState<ClusterStatus>('idle')
const [clusterNodesError, setClusterNodesError] = useState('')
const [selectedCluster, setSelectedCluster] = useState<Cluster | null>(null)
const [clusterDetails, setClusterDetails] = useState<ClusterNodesResponse | null>(null)
const nextTheme = theme === 'light' ? 'dark' : 'light'
const selectedClusterNodes = nodes.filter((node) =>
selectedClusterNodeIds.includes(node.uuid),
@@ -257,6 +290,36 @@ function App() {
const currentConnectionNode = selectedClusterNodes[connectionNodeIndex]
const loadClusters = async () => {
setClustersStatus('loading')
setClustersError('')
try {
const response = await getClusterResource('clusters')
setClusters(await response.json() as Cluster[])
setClustersStatus('success')
} catch (error) {
setClustersStatus('error')
setClustersError(error instanceof Error ? error.message : 'Failed to load clusters')
}
}
const handleClusterClick = async (cluster: Cluster) => {
setSelectedCluster(cluster)
setClusterNodesStatus('loading')
setClusterNodesError('')
try {
const response = await getClusterResource(`cluster-nodes/${encodeURIComponent(cluster.uuid)}`)
setClusterDetails(await response.json() as ClusterNodesResponse)
setClusterNodesStatus('success')
selectPage('cluster-details')
} catch (error) {
setClusterNodesStatus('error')
setClusterNodesError(error instanceof Error ? error.message : 'Failed to load cluster nodes')
}
}
const loadNodeInterfaces = async () => {
setNodesStatus('loading')
setNodesError('')
@@ -283,6 +346,9 @@ function App() {
}
useEffect(() => {
if (activePage === 'clusters') {
void loadClusters()
}
if (activePage === 'nodes' || activePage === 'clusters') {
void loadNodeInterfaces()
}
@@ -433,6 +499,7 @@ function App() {
setClusterStatus('success')
setIsClusterFormOpen(false)
await loadClusters()
} catch (error) {
setClusterStatus('error')
setClusterError(
@@ -494,6 +561,33 @@ function App() {
</aside>
<main className="main-content">
{activePage === 'cluster-details' && clusterDetails && (
<section className="content-panel" id="cluster-details">
<div className="page-header">
<h1>{clusterDetails.cluster_name}</h1>
<button
type="button"
className="secondary-button"
onClick={() => selectPage('clusters')}
>
Back to clusters
</button>
</div>
<h2>Nodes</h2>
{clusterDetails.nodes.length > 0 ? (
<ul className="cluster-node-list" aria-label="Cluster nodes">
{clusterDetails.nodes.map((node) => (
<li className="cluster-node-card" key={node.uuid}>
{node.node_name}
</li>
))}
</ul>
) : (
<p>No nodes found in this cluster.</p>
)}
</section>
)}
{activePage === 'dashboard' && (
<section className="content-panel" id="dashboard">
<p className="eyebrow">Dashboard</p>
@@ -684,6 +778,38 @@ function App() {
</button>
</div>
{!isClusterFormOpen && (
<div>
{clustersStatus === 'loading' && <p role="status">Loading clusters...</p>}
{clustersStatus === 'error' && (
<p className="status-message error" role="alert">{clustersError}</p>
)}
{clustersStatus === 'success' && (
clusters.length > 0 ? (
<ul className="cluster-list">
{clusters.map((cluster) => (
<li key={cluster.uuid}>
<button
type="button"
className="secondary-button"
onClick={() => void handleClusterClick(cluster)}
disabled={clusterNodesStatus === 'loading'}
aria-pressed={selectedCluster?.uuid === cluster.uuid}
>
{cluster.cluster_name}
</button>
</li>
))}
</ul>
) : <p>No clusters found.</p>
)}
{clusterNodesStatus === 'loading' && <p role="status">Loading cluster nodes...</p>}
{clusterNodesStatus === 'error' && (
<p className="status-message error" role="alert">{clusterNodesError}</p>
)}
</div>
)}
{isClusterFormOpen && (
<ol className="cluster-steps" aria-label="Cluster creation progress">
<li aria-current={clusterStep === 1 ? 'step' : undefined}>