import { useState, useEffect } from 'react'; import { Server, Box, Cpu, HardDrive, List, MessageCircle } from 'lucide-react'; import { useClusterState } from '../../hooks/useClusterState'; import apiClient from '../../api/client'; import type { Workflow } from '../../types'; interface LeftPanelProps { activeTab: string; setActiveTab: (tab: string) => void; selectedWorkflow: string | null; setSelectedWorkflow: (id: string | null) => void; } export function LeftPanel({ activeTab, setActiveTab, selectedWorkflow, setSelectedWorkflow }: LeftPanelProps) { const { nodes } = useClusterState(); const [workflows, setWorkflows] = useState([]); const [loadingWorkflows, setLoadingWorkflows] = useState(false); const totalNodes = nodes.length; const aliveNodes = nodes.filter(n => n.alive).length; let totalCpu = 0; let usedCpu = 0; let totalMemory = 0; let usedMemory = 0; nodes.forEach(node => { const nodeTotalCpu = node.resources?.CPU || 0; const nodeRemainingCpu = node.remaining?.CPU || 0; totalCpu += nodeTotalCpu; usedCpu += (nodeTotalCpu - nodeRemainingCpu); const nodeTotalMem = node.resources?.memory || 0; const nodeRemainingMem = node.remaining?.memory || 0; totalMemory += nodeTotalMem; usedMemory += (nodeTotalMem - nodeRemainingMem); }); const cpuPercent = totalCpu > 0 ? (usedCpu / totalCpu) * 100 : 0; const memPercent = totalMemory > 0 ? (usedMemory / totalMemory) * 100 : 0; useEffect(() => { if (activeTab === 'workflows') { const fetchWorkflows = async () => { setLoadingWorkflows(true); try { const response = await apiClient.get('/api/v1/workflow/list'); // Fallback parsing just in case it returns an object or array const data = response.data; let parsedWorkflows: Workflow[] = []; if (Array.isArray(data)) { parsedWorkflows = data; } else if (data && typeof data === 'object') { // Suppose backend sends { workflows: [...] } parsedWorkflows = data.workflows || Object.values(data); } setWorkflows(parsedWorkflows); } catch (error) { console.error("Failed to fetch workflows", error); setWorkflows([]); } finally { setLoadingWorkflows(false); } }; fetchWorkflows(); } }, [activeTab]); return (
{/* Top: Cluster Status */}

Cluster Status

Active Nodes
{aliveNodes} / {totalNodes || 0}
Cluster CPU
{cpuPercent.toFixed(1)}%
Cluster RAM
{(totalMemory > 0 ? usedMemory / (1024 ** 3) : 0).toFixed(1)} GB
{/* Bottom: Tabs for Workflows & Basic Chats */}
{activeTab === 'workflows' && (
{loadingWorkflows ? (
Loading workflows...
) : workflows.length === 0 ? (
暂无工作流
) : ( workflows.map((wf) => (
setSelectedWorkflow(wf.event_id)} className={`p-3 rounded-lg border cursor-pointer transition-all ${selectedWorkflow === wf.event_id ? 'border-blue-200 bg-blue-50 shadow-sm' : 'border-slate-100 hover:border-blue-200 hover:bg-slate-50'}`} >
{wf.workflow_title || 'Unnamed Workflow'}

ID: {wf.event_id}

)) )}
)} {activeTab === 'chats' && (
)}
); }