feat(ui): add type filter toggles
This commit is contained in:
141
src/App.tsx
Normal file
141
src/App.tsx
Normal file
@@ -0,0 +1,141 @@
|
||||
import { useEffect, useRef, useCallback, useState } from 'react';
|
||||
import { ReactFlowProvider } from '@xyflow/react';
|
||||
import { useTopologyStore } from './store/topologyStore';
|
||||
import {
|
||||
defaultNetworkInfo,
|
||||
defaultHosts,
|
||||
discoverHosts,
|
||||
convertToTopology
|
||||
} from './services/discovery';
|
||||
import Header from './components/Header';
|
||||
import LeftPanel from './components/LeftPanel';
|
||||
import RightPanel from './components/RightPanel';
|
||||
import TopologyGraph from './components/Graph/TopologyGraph';
|
||||
|
||||
const POLLING_INTERVAL_MS = 30000;
|
||||
|
||||
function App() {
|
||||
const {
|
||||
setNodes,
|
||||
setEdges,
|
||||
setNetworkInfo,
|
||||
setHosts,
|
||||
setLastUpdated,
|
||||
setIsLoading,
|
||||
leftPanelOpen,
|
||||
rightPanelOpen,
|
||||
isLoading
|
||||
} = useTopologyStore();
|
||||
|
||||
const isLoadingRef = useRef(isLoading);
|
||||
isLoadingRef.current = isLoading;
|
||||
|
||||
const loadData = useCallback(async () => {
|
||||
if (isLoadingRef.current) return;
|
||||
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const hostNames = ['ubuntu', 'grizzley', 'truenas', 'ice', 'panda', 'proxmox'];
|
||||
const discoveryResult = await discoverHosts(hostNames);
|
||||
|
||||
const { nodes, edges } = convertToTopology(
|
||||
discoveryResult.hosts,
|
||||
defaultNetworkInfo
|
||||
);
|
||||
|
||||
const hosts = discoveryResult.hosts.map(h => ({
|
||||
name: h.name,
|
||||
ip: h.ip,
|
||||
type: (h.name === 'ubuntu' ? 'vm' :
|
||||
h.name === 'proxmox' || h.name === 'truenas' ? 'physical' : 'rpi5') as 'vm' | 'physical' | 'rpi5' | 'container',
|
||||
role: h.name === 'ubuntu' ? 'Primary Docker Host' :
|
||||
h.name === 'grizzley' ? 'Edge Services' :
|
||||
h.name === 'truenas' ? 'Storage (NAS)' :
|
||||
h.name === 'proxmox' ? 'Hypervisor' : 'Host',
|
||||
containers: h.containers.map(c => c.name)
|
||||
}));
|
||||
|
||||
setNodes(nodes);
|
||||
setEdges(edges);
|
||||
setNetworkInfo(defaultNetworkInfo);
|
||||
setHosts(hosts);
|
||||
setLastUpdated(new Date());
|
||||
} catch (error) {
|
||||
console.error('Discovery failed:', error);
|
||||
setNodes([]);
|
||||
setEdges([]);
|
||||
setNetworkInfo(defaultNetworkInfo);
|
||||
setHosts(defaultHosts);
|
||||
setLastUpdated(new Date());
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [setNodes, setEdges, setNetworkInfo, setHosts, setLastUpdated, setIsLoading]);
|
||||
|
||||
useEffect(() => {
|
||||
loadData();
|
||||
|
||||
const intervalId = setInterval(loadData, POLLING_INTERVAL_MS);
|
||||
|
||||
return () => clearInterval(intervalId);
|
||||
}, [loadData]);
|
||||
|
||||
return (
|
||||
<ReactFlowProvider>
|
||||
<div className="h-screen w-screen flex flex-col bg-slate-900">
|
||||
<Header onRefresh={loadData} isLoading={isLoading} />
|
||||
|
||||
<div className="flex-1 flex overflow-hidden">
|
||||
{leftPanelOpen && (
|
||||
<LeftPanel />
|
||||
)}
|
||||
|
||||
<div className="flex-1">
|
||||
<TopologyGraph />
|
||||
</div>
|
||||
|
||||
{rightPanelOpen && (
|
||||
<RightPanel />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
</ReactFlowProvider>
|
||||
);
|
||||
}
|
||||
|
||||
function Footer() {
|
||||
const { lastUpdated, nodes } = useTopologyStore();
|
||||
const [countdown, setCountdown] = useState(30);
|
||||
|
||||
const formatTime = (date: Date | null) => {
|
||||
if (!date) return 'Never';
|
||||
return date.toLocaleTimeString();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setCountdown(30);
|
||||
|
||||
const intervalId = setInterval(() => {
|
||||
setCountdown(prev => {
|
||||
if (prev <= 1) return 30;
|
||||
return prev - 1;
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
return () => clearInterval(intervalId);
|
||||
}, [lastUpdated]);
|
||||
|
||||
return (
|
||||
<div className="h-8 bg-slate-800 border-t border-slate-700 px-4 flex items-center justify-between text-xs text-slate-400">
|
||||
<span>Nodes: {nodes.length}</span>
|
||||
<div className="flex items-center gap-4">
|
||||
<span>Next refresh: {countdown}s</span>
|
||||
<span>Last updated: {formatTime(lastUpdated)}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
Reference in New Issue
Block a user