Newer
Older
cortex-hub / ui / client-app / src / pages / NodesPage.js
import React, { useState, useEffect, useCallback } from 'react';
import {
    getAdminNodes, adminCreateNode, adminUpdateNode, adminDeleteNode,
    adminGrantNodeAccess, adminRevokeNodeAccess,
    adminDownloadNodeBundle, getUserAccessibleNodes,
    getAdminGroups, getNodeStreamUrl
} from '../services/apiService';
import NodeTerminal from '../components/NodeTerminal';
import FileSystemNavigator from '../components/FileSystemNavigator';

const NodesPage = ({ user }) => {
    const [activeTab, setActiveTab] = useState(user?.role === 'admin' ? 'manage' : 'status');
    const [nodes, setNodes] = useState([]);
    const [groups, setGroups] = useState([]);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState(null);
    const [showCreateModal, setShowCreateModal] = useState(false);
    const [newNode, setNewNode] = useState({ node_id: '', display_name: '', description: '', skill_config: { shell: { enabled: true }, browser: { enabled: true }, sync: { enabled: true } } });
    const [expandedTerminals, setExpandedTerminals] = useState({}); // node_id -> boolean
    const [expandedNodes, setExpandedNodes] = useState({}); // node_id -> boolean
    const [editingNodeId, setEditingNodeId] = useState(null);
    const [editForm, setEditForm] = useState({ display_name: '', description: '', skill_config: {} });

    // WebSocket state for live updates
    const [meshStatus, setMeshStatus] = useState({}); // node_id -> { status, stats }
    const [recentEvents, setRecentEvents] = useState([]); // Array of event objects

    const isAdmin = user?.role === 'admin';

    const fetchData = useCallback(async () => {
        setLoading(true);
        try {
            if (isAdmin) {
                const [nodesData, groupsData] = await Promise.all([getAdminNodes(), getAdminGroups()]);
                setNodes(nodesData);
                setGroups(groupsData);
            } else {
                const nodesData = await getUserAccessibleNodes();
                setNodes(nodesData);
            }
        } catch (err) {
            setError(err.message);
        } finally {
            setLoading(false);
        }
    }, [isAdmin]);

    useEffect(() => {
        fetchData();
    }, [fetchData]);

    // WebSocket Connection for Live Mesh Status
    useEffect(() => {
        const wsUrl = getNodeStreamUrl();
        const ws = new WebSocket(wsUrl);

        ws.onmessage = (event) => {
            const msg = JSON.parse(event.data);
            if (msg.event === 'initial_snapshot') {
                const statusMap = {};
                msg.data.nodes.forEach(n => {
                    statusMap[n.node_id] = { status: n.status, stats: n.stats };
                });
                setMeshStatus(statusMap);
            } else if (msg.event === 'mesh_heartbeat') {
                const statusMap = { ...meshStatus };
                msg.data.nodes.forEach(n => {
                    statusMap[n.node_id] = { status: n.status, stats: n.stats };
                });
                setMeshStatus(statusMap);
            } else if (['task_start', 'task_complete', 'task_error', 'info'].includes(msg.event)) {
                // Add to recent events log
                setRecentEvents(prev => [msg, ...prev].slice(0, 50));
            }
        };

        return () => ws.close();
    }, [user?.id]);

    const handleCreateNode = async (e) => {
        e.preventDefault();
        try {
            await adminCreateNode(newNode);
            setShowCreateModal(false);
            fetchData();
        } catch (err) {
            alert(err.message);
        }
    };

    const toggleNodeActive = async (node) => {
        try {
            await adminUpdateNode(node.node_id, { is_active: !node.is_active });
            fetchData();
        } catch (err) {
            alert(err.message);
        }
    };

    const handleDeleteNode = async (nodeId) => {
        if (!window.confirm(`Are you sure you want to deregister node ${nodeId}? This will remove all access grants.`)) return;
        try {
            await adminDeleteNode(nodeId);
            fetchData();
        } catch (err) {
            alert(err.message);
        }
    };

    const startEditing = (node) => {
        setEditingNodeId(node.node_id);
        setEditForm({
            display_name: node.display_name,
            description: node.description,
            skill_config: node.skill_config || {}
        });
    };

    const handleUpdateNode = async (nodeId) => {
        try {
            await adminUpdateNode(nodeId, editForm);
            setEditingNodeId(null);
            fetchData();
        } catch (err) {
            alert(err.message);
        }
    };

    return (
        <div className="flex flex-col h-full bg-gray-50 dark:bg-gray-900 overflow-hidden">
            {/* Header */}
            <header className="bg-white dark:bg-gray-800 border-b dark:border-gray-700 px-8 py-6">
                <div className="flex justify-between items-center">
                    <div>
                        <h1 className="text-2xl font-bold text-gray-900 dark:text-white flex items-center">
                            <span className="mr-2">🚀</span> Agent Node Mesh
                        </h1>
                        <p className="text-gray-500 dark:text-gray-400 text-sm mt-1">
                            {isAdmin
                                ? "Manage distributed execution nodes and monitor live health."
                                : "Monitor the health and availability of your accessible agent nodes."}
                        </p>
                    </div>
                    <div className="flex space-x-2">
                        <button
                            onClick={fetchData}
                            className="p-2 text-gray-500 hover:text-indigo-600 dark:hover:text-indigo-400 transition-colors"
                            title="Refresh List"
                        >
                            <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                                <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
                            </svg>
                        </button>
                        {isAdmin && (
                            <button
                                onClick={() => setShowCreateModal(true)}
                                className="bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded-lg text-sm font-semibold transition-all flex items-center"
                            >
                                <svg className="w-4 h-4 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                                    <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
                                </svg>
                                Register Node
                            </button>
                        )}
                    </div>
                </div>

                {/* Tabs */}
                <div className="flex space-x-8 mt-8 border-b dark:border-gray-700">
                    {isAdmin && (
                        <button
                            onClick={() => setActiveTab('manage')}
                            className={`pb-4 px-1 text-sm font-medium transition-colors border-b-2 ${activeTab === 'manage' ? 'border-indigo-500 text-indigo-600 dark:text-indigo-400' : 'border-transparent text-gray-500 hover:text-gray-700 dark:hover:text-gray-300'}`}
                        >
                            Management
                        </button>
                    )}
                    <button
                        onClick={() => setActiveTab('status')}
                        className={`pb-4 px-1 text-sm font-medium transition-colors border-b-2 ${activeTab === 'status' ? 'border-indigo-500 text-indigo-600 dark:text-indigo-400' : 'border-transparent text-gray-500 hover:text-gray-700 dark:hover:text-gray-300'}`}
                    >
                        Live Monitor
                    </button>
                </div>
            </header>

            {/* Main Content */}
            <main className="flex-1 overflow-auto p-8">
                {loading ? (
                    <div className="flex items-center justify-center h-64">
                        <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-indigo-500"></div>
                    </div>
                ) : error ? (
                    <div className="bg-red-50 dark:bg-red-900/20 text-red-600 p-4 rounded-xl border border-red-200 dark:border-red-800">
                        Error: {error}
                    </div>
                ) : activeTab === 'manage' ? (
                    /* ADMIN MANAGEMENT VIEW */
                    <div className="space-y-6">
                        <div className="grid grid-cols-1 gap-6">
                            {nodes.map(node => (
                                <div key={node.node_id} className="bg-white dark:bg-gray-800 rounded-2xl shadow-sm border dark:border-gray-700 overflow-hidden transition-all duration-300">
                                    {/* Top Row: Basic Info & Actions */}
                                    <div className="p-5 flex items-center justify-between hover:bg-gray-50 dark:hover:bg-gray-800/50 transition-colors">
                                        <div className="flex items-center space-x-4 flex-1 min-w-0">
                                            <div className={`p-2 rounded-xl flex-shrink-0 ${node.is_active ? 'bg-green-100 text-green-600' : 'bg-gray-100 text-gray-400'}`}>
                                                <svg className="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                                                    <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2m-7 0V4" />
                                                </svg>
                                            </div>
                                            <div className="min-w-0 truncate">
                                                <h3 className="text-base font-bold text-gray-900 dark:text-white truncate">{node.display_name}</h3>
                                                <div className="flex items-center space-x-2 mt-0.5">
                                                    <span className={`text-[10px] font-bold uppercase tracking-widest ${node.is_active ? 'text-green-500' : 'text-gray-400'}`}>
                                                        {node.is_active ? 'Active' : 'Disabled'}
                                                    </span>
                                                    <span className="text-gray-300 dark:text-gray-600">•</span>
                                                    <span className="text-[10px] font-mono text-gray-500 truncate">{node.node_id}</span>
                                                </div>
                                            </div>
                                        </div>

                                        <div className="flex items-center space-x-2 ml-4">
                                            <button
                                                onClick={() => toggleNodeActive(node)}
                                                className={`p-2 rounded-lg transition-colors ${node.is_active ? 'text-yellow-500 hover:bg-yellow-50' : 'text-green-500 hover:bg-green-50'}`}
                                                title={node.is_active ? 'Disable Node' : 'Enable Node'}
                                            >
                                                {node.is_active ? (
                                                    <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 9v6m4-6v6m-9-4h18" /></svg>
                                                ) : (
                                                    <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" /><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
                                                )}
                                            </button>
                                            <button
                                                onClick={() => setExpandedTerminals(prev => ({ ...prev, [node.node_id]: !prev[node.node_id] }))}
                                                className={`p-2 rounded-lg transition-colors ${expandedTerminals[node.node_id] ? 'bg-indigo-50 text-indigo-600' : 'text-gray-500 hover:text-indigo-600 hover:bg-gray-100'}`}
                                                title="Interactive Terminal"
                                            >
                                                <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 9l3 3-3 3m5 0h3M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8v-4m0 4l-4-4m4 4l4-4" /></svg>
                                            </button>
                                            <button
                                                onClick={() => setExpandedNodes(prev => ({ ...prev, [node.node_id]: !prev[node.node_id] }))}
                                                className={`p-2 rounded-lg transition-colors ${expandedNodes[node.node_id] ? 'bg-gray-100 text-gray-800' : 'text-gray-500 hover:bg-gray-100 hover:text-gray-800'}`}
                                                title="Settings & Details"
                                            >
                                                <svg className={`w-5 h-5 transition-transform duration-200 ${expandedNodes[node.node_id] ? 'rotate-180' : ''}`} fill="none" viewBox="0 0 24 24" stroke="currentColor">
                                                    <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
                                                </svg>
                                            </button>
                                            <div className="w-px h-6 bg-gray-200 dark:bg-gray-700 mx-1"></div>
                                            <button
                                                onClick={() => handleDeleteNode(node.node_id)}
                                                className="p-2 text-red-400 hover:text-red-600 hover:bg-red-50 rounded-lg transition-colors"
                                                title="Deregister"
                                            >
                                                <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" /></svg>
                                            </button>
                                        </div>
                                    </div>

                                    {/* Expanded Panels */}
                                    {(expandedNodes[node.node_id] || expandedTerminals[node.node_id]) && (
                                        <div className="border-t dark:border-gray-700 bg-gray-50/50 dark:bg-gray-800/30 p-5 space-y-6">
                                            {/* Settings Content */}
                                            {expandedNodes[node.node_id] && (
                                                <>
                                                    <div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
                                                        {/* Left: General Settings */}
                                                        <div className="space-y-5">
                                                            <div className="flex justify-between items-center mb-1">
                                                                <h4 className="text-xs font-black uppercase tracking-widest text-gray-500">General Configuration</h4>
                                                                {editingNodeId !== node.node_id ? (
                                                                    <button onClick={() => startEditing(node)} className="text-xs font-bold text-indigo-600 hover:underline">Edit Node</button>
                                                                ) : (
                                                                    <div className="space-x-4">
                                                                        <button onClick={() => handleUpdateNode(node.node_id)} className="text-xs font-bold text-green-600 hover:underline">Save Changes</button>
                                                                        <button onClick={() => setEditingNodeId(null)} className="text-xs font-bold text-gray-500 hover:underline">Cancel</button>
                                                                    </div>
                                                                )}
                                                            </div>

                                                            <div className="space-y-4">
                                                                <div>
                                                                    <label className="block text-[10px] font-bold text-gray-400 uppercase mb-1 px-1">Node Display Name</label>
                                                                    {editingNodeId === node.node_id ? (
                                                                        <input
                                                                            className="w-full bg-white dark:bg-gray-700 border border-gray-200 dark:border-gray-600 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-indigo-500 transition-all dark:text-white"
                                                                            value={editForm.display_name}
                                                                            onChange={e => setEditForm({ ...editForm, display_name: e.target.value })}
                                                                        />
                                                                    ) : (
                                                                        <div className="text-sm font-medium text-gray-700 dark:text-gray-300 px-3 py-2 bg-white/50 dark:bg-gray-800/50 rounded-lg border border-transparent italic">
                                                                            {node.display_name}
                                                                        </div>
                                                                    )}
                                                                </div>
                                                                <div>
                                                                    <label className="block text-[10px] font-bold text-gray-400 uppercase mb-1 px-1">Description</label>
                                                                    {editingNodeId === node.node_id ? (
                                                                        <textarea
                                                                            className="w-full bg-white dark:bg-gray-700 border border-gray-200 dark:border-gray-600 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-indigo-500 transition-all dark:text-white h-20"
                                                                            value={editForm.description}
                                                                            onChange={e => setEditForm({ ...editForm, description: e.target.value })}
                                                                        />
                                                                    ) : (
                                                                        <div className="text-sm text-gray-600 dark:text-gray-400 px-3 py-2 bg-white/50 dark:bg-gray-800/50 rounded-lg border border-transparent min-h-[5rem]">
                                                                            {node.description || 'No description provided.'}
                                                                        </div>
                                                                    )}
                                                                </div>
                                                            </div>
                                                        </div>

                                                        {/* Right: Policy & Operations */}
                                                        <div className="space-y-5">
                                                            <h4 className="text-xs font-black uppercase tracking-widest text-gray-500 mb-1">Capabilities & Security</h4>

                                                            <div className="bg-white dark:bg-gray-800 border dark:border-gray-700 rounded-xl p-4 space-y-4">
                                                                <div>
                                                                    <label className="block text-[10px] font-bold text-gray-400 uppercase mb-3 px-1">Skill Policy & Security</label>

                                                                    {editingNodeId === node.node_id ? (
                                                                        <div className="space-y-3">
                                                                            {['shell', 'browser', 'sync'].map(skill => (
                                                                                <div key={skill} className="p-3 border dark:border-gray-700 rounded-xl bg-gray-50/50 dark:bg-gray-800/50">
                                                                                    <div className="flex items-center justify-between mb-2">
                                                                                        <span className="text-xs font-bold capitalize text-gray-700 dark:text-gray-300">{skill} Skill</span>
                                                                                        <label className="relative inline-flex items-center cursor-pointer">
                                                                                            <input
                                                                                                type="checkbox"
                                                                                                className="sr-only peer"
                                                                                                checked={editForm.skill_config?.[skill]?.enabled}
                                                                                                onChange={e => setEditForm({
                                                                                                    ...editForm,
                                                                                                    skill_config: {
                                                                                                        ...editForm.skill_config,
                                                                                                        [skill]: { ...editForm.skill_config?.[skill], enabled: e.target.checked }
                                                                                                    }
                                                                                                })}
                                                                                            />
                                                                                            <div className="w-9 h-5 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-indigo-300 dark:peer-focus:ring-indigo-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all dark:border-gray-600 peer-checked:bg-indigo-600"></div>
                                                                                        </label>
                                                                                    </div>

                                                                                    {skill === 'shell' && editForm.skill_config?.shell?.enabled && (
                                                                                        <div className="mt-3 space-y-3 pl-3 border-l-2 border-indigo-400">
                                                                                            <div className="grid grid-cols-2 gap-2">
                                                                                                <div>
                                                                                                    <label className="block text-[9px] font-bold text-gray-400 uppercase mb-1">Mode</label>
                                                                                                    <select
                                                                                                        className="text-[11px] w-full bg-white dark:bg-gray-900 border dark:border-gray-600 rounded px-1.5 py-1 outline-none focus:ring-1 focus:ring-indigo-500"
                                                                                                        value={editForm.skill_config?.shell?.sandbox?.mode || 'STRICT'}
                                                                                                        onChange={e => setEditForm({
                                                                                                            ...editForm,
                                                                                                            skill_config: {
                                                                                                                ...editForm.skill_config,
                                                                                                                shell: {
                                                                                                                    ...editForm.skill_config.shell,
                                                                                                                    sandbox: { ...editForm.skill_config.shell.sandbox, mode: e.target.value }
                                                                                                                }
                                                                                                            }
                                                                                                        })}
                                                                                                    >
                                                                                                        <option value="STRICT">STRICT (Whitelist)</option>
                                                                                                        <option value="PERMISSIVE">PERMISSIVE (Blacklist)</option>
                                                                                                    </select>
                                                                                                </div>
                                                                                                <div>
                                                                                                    <label className="block text-[9px] font-bold text-gray-400 uppercase mb-1">WD Jail</label>
                                                                                                    <input
                                                                                                        className="text-[11px] w-full bg-white dark:bg-gray-900 border dark:border-gray-600 rounded px-1.5 py-1 outline-none focus:ring-1 focus:ring-indigo-500"
                                                                                                        placeholder="/home/jail"
                                                                                                        value={editForm.skill_config?.shell?.cwd_jail || ''}
                                                                                                        onChange={e => setEditForm({
                                                                                                            ...editForm,
                                                                                                            skill_config: {
                                                                                                                ...editForm.skill_config,
                                                                                                                shell: { ...editForm.skill_config.shell, cwd_jail: e.target.value }
                                                                                                            }
                                                                                                        })}
                                                                                                    />
                                                                                                </div>
                                                                                            </div>
                                                                                            <div>
                                                                                                <label className="block text-[9px] font-bold text-gray-400 uppercase mb-1">Allowed Commands (csv)</label>
                                                                                                <input
                                                                                                    className="text-[11px] w-full bg-white dark:bg-gray-900 border dark:border-gray-600 rounded px-1.5 py-1 outline-none focus:ring-1 focus:ring-indigo-500 font-mono"
                                                                                                    value={(editForm.skill_config?.shell?.sandbox?.allowed_commands || []).join(', ')}
                                                                                                    onChange={e => setEditForm({
                                                                                                        ...editForm,
                                                                                                        skill_config: {
                                                                                                            ...editForm.skill_config,
                                                                                                            shell: {
                                                                                                                ...editForm.skill_config.shell,
                                                                                                                sandbox: {
                                                                                                                    ...editForm.skill_config.shell.sandbox,
                                                                                                                    allowed_commands: e.target.value.split(',').map(s => s.trim()).filter(s => s)
                                                                                                                }
                                                                                                            }
                                                                                                        }
                                                                                                    })}
                                                                                                />
                                                                                            </div>
                                                                                            <div>
                                                                                                <label className="block text-[9px] font-bold text-gray-400 uppercase mb-1">Denied Commands (csv)</label>
                                                                                                <input
                                                                                                    className="text-[11px] w-full bg-white dark:bg-gray-900 border dark:border-gray-600 rounded px-1.5 py-1 outline-none focus:ring-1 focus:ring-indigo-500 font-mono"
                                                                                                    value={(editForm.skill_config?.shell?.sandbox?.denied_commands || []).join(', ')}
                                                                                                    onChange={e => setEditForm({
                                                                                                        ...editForm,
                                                                                                        skill_config: {
                                                                                                            ...editForm.skill_config,
                                                                                                            shell: {
                                                                                                                ...editForm.skill_config.shell,
                                                                                                                sandbox: {
                                                                                                                    ...editForm.skill_config.shell.sandbox,
                                                                                                                    denied_commands: e.target.value.split(',').map(s => s.trim()).filter(s => s)
                                                                                                                }
                                                                                                            }
                                                                                                        }
                                                                                                    })}
                                                                                                />
                                                                                            </div>
                                                                                        </div>
                                                                                    )}
                                                                                </div>
                                                                            ))}
                                                                        </div>
                                                                    ) : (
                                                                        <div className="grid grid-cols-2 gap-2">
                                                                            {Object.entries(node.skill_config || {}).map(([skill, cfg]) => (
                                                                                <div key={skill} className="flex items-center justify-between p-2 rounded-lg bg-gray-50 dark:bg-gray-700/50 transition-colors">
                                                                                    <div className="flex flex-col">
                                                                                        <span className="text-xs font-bold text-gray-600 dark:text-gray-300 capitalize">{skill}</span>
                                                                                        {skill === 'shell' && cfg?.sandbox?.mode && (
                                                                                            <span className="text-[10px] text-indigo-500 font-bold opacity-80">{cfg.sandbox.mode}</span>
                                                                                        )}
                                                                                    </div>
                                                                                    <div className={`w-2 h-2 rounded-full ${cfg?.enabled ? 'bg-indigo-500 shadow-[0_0_8px_rgba(99,102,241,0.6)]' : 'bg-gray-300'}`}></div>
                                                                                </div>
                                                                            ))}
                                                                        </div>
                                                                    )}
                                                                    <p className="text-[10px] text-gray-400 mt-3 italic">* Changes to sandbox policy require node reconnection or sync.</p>
                                                                </div>

                                                                <div className="pt-2 border-t dark:border-gray-700 flex flex-wrap gap-2">
                                                                    <button
                                                                        onClick={() => adminDownloadNodeBundle(node.node_id)}
                                                                        className="flex-1 bg-white dark:bg-gray-700 border dark:border-gray-600 text-gray-700 dark:text-gray-200 px-4 py-2 rounded-lg text-xs font-bold hover:bg-gray-50 dark:hover:bg-gray-600 transition-colors flex items-center justify-center"
                                                                    >
                                                                        <svg className="w-4 h-4 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" /></svg>
                                                                        Download Bundle (M5)
                                                                    </button>
                                                                </div>
                                                            </div>
                                                        </div>
                                                    </div>

                                                    {/* File System Explorer Section (Modular Navigator) */}
                                                    <div className="mt-8 border-t dark:border-gray-700 pt-6">
                                                        <div className="flex items-center justify-between mb-4 px-1">
                                                            <div>
                                                                <h4 className="text-sm font-bold text-gray-800 dark:text-white flex items-center">
                                                                    <svg className="w-4 h-4 mr-2 text-indigo-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" /></svg>
                                                                    Node File Explorer
                                                                </h4>
                                                                <p className="text-[10px] text-gray-500 dark:text-gray-400 mt-1 uppercase tracking-wider">Modular file navigation & sync verification</p>
                                                            </div>
                                                            <div className="flex items-center px-3 py-1 bg-green-500/10 border border-green-500/20 rounded-full">
                                                                <div className="w-1.5 h-1.5 bg-green-500 rounded-full mr-2 animate-pulse"></div>
                                                                <span className="text-[10px] font-bold text-green-600 dark:text-green-400 uppercase tracking-tighter">Live Connection</span>
                                                            </div>
                                                        </div>
                                                        <div className="h-[400px]">
                                                            <FileSystemNavigator nodeId={node.node_id} />
                                                        </div>
                                                    </div>
                                                </>
                                            )}

                                            {/* Terminal Section */}
                                            {expandedTerminals[node.node_id] && (
                                                <div className="pt-5 border-t dark:border-gray-700">
                                                    <div className="flex justify-between items-center mb-4">
                                                        <h4 className="text-xs font-black uppercase tracking-widest text-indigo-500">Node Interactive Shell</h4>
                                                        <span className="text-[10px] font-mono text-gray-400">Direct gRPC Proxy</span>
                                                    </div>
                                                    <NodeTerminal nodeId={node.node_id} />
                                                </div>
                                            )}
                                        </div>
                                    )}
                                </div>
                            ))}
                        </div>
                    </div>
                ) : (
                    /* LIVE MONITOR VIEW (M6) */
                    <div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
                        {/* Status Cards */}
                        <div className="lg:col-span-2 grid grid-cols-1 md:grid-cols-2 gap-6 content-start">
                            {nodes.map(node => {
                                const live = meshStatus[node.node_id];
                                const isOnline = live?.status === 'online' || live?.status === 'idle' || live?.status === 'busy';

                                return (
                                    <div key={node.node_id} className="bg-white dark:bg-gray-800 rounded-2xl p-6 shadow-sm border dark:border-gray-700 relative overflow-hidden group">
                                        <div className={`absolute top-0 left-0 w-1 h-full ${isOnline ? 'bg-green-500' : 'bg-gray-300'}`}></div>

                                        <div className="flex justify-between items-start">
                                            <div>
                                                <h4 className="font-bold text-gray-900 dark:text-white uppercase tracking-tight">{node.display_name}</h4>
                                                <p className="text-[10px] text-gray-400 font-mono">{node.node_id}</p>
                                            </div>
                                            <div className="flex items-center">
                                                <span className={`w-2 h-2 rounded-full mr-2 ${isOnline ? 'bg-green-500 animate-pulse' : 'bg-gray-400'}`}></span>
                                                <span className={`text - [10px] font - bold uppercase tracking - wider ${isOnline ? 'text-green-600' : 'text-gray-500'} `}>
                                                    {live?.status || (node.last_status || 'offline')}
                                                </span>
                                            </div>
                                        </div>

                                        <div className="mt-6 flex justify-between items-end">
                                            <div className="space-y-1">
                                                <div className="text-[10px] text-gray-500 font-bold uppercase">Tasks Running</div>
                                                <div className="text-2xl font-black text-gray-900 dark:text-white">{live?.stats?.active_worker_count || 0}</div>
                                            </div>
                                            <div className="space-y-1 text-right">
                                                <div className="text-[10px] text-gray-500 font-bold uppercase">Uptime Score</div>
                                                <div className="text-xl font-bold text-indigo-500">{(live?.stats?.success_rate ? (live.stats.success_rate * 100).toFixed(1) : "100")}%</div>
                                            </div>
                                        </div>

                                        <div className="mt-4 pt-4 border-t dark:border-gray-700 grid grid-cols-2 gap-4">
                                            <div className="text-[10px] text-gray-400">
                                                <span className="block font-bold mb-0.5">CPU</span>
                                                <div className="w-full bg-gray-100 dark:bg-gray-700 rounded-full h-1 overflow-hidden">
                                                    <div className="bg-indigo-500 h-full" style={{ width: `${live?.stats?.cpu_usage_percent || 0}% ` }}></div>
                                                </div>
                                            </div>
                                            <div className="text-[10px] text-gray-400">
                                                <span className="block font-bold mb-0.5">MEM</span>
                                                <div className="w-full bg-gray-100 dark:bg-gray-700 rounded-full h-1 overflow-hidden">
                                                    <div className="bg-pink-500 h-full" style={{ width: `${live?.stats?.memory_usage_percent || 0}% ` }}></div>
                                                </div>
                                            </div>
                                        </div>
                                    </div>
                                );
                            })}
                        </div>

                        {/* Event Timeline */}
                        <div className="bg-white dark:bg-gray-800 rounded-2xl shadow-sm border dark:border-gray-700 overflow-hidden flex flex-col h-[500px]">
                            <div className="p-4 border-b dark:border-gray-700 bg-gray-50 dark:bg-gray-800/50">
                                <h4 className="text-xs font-black uppercase tracking-widest text-gray-500 flex items-center">
                                    <span className="w-2 h-2 rounded-full bg-indigo-500 mr-2"></span>
                                    Execution Live Bus
                                </h4>
                            </div>
                            <div className="flex-1 overflow-y-auto p-4 space-y-3 font-mono text-[11px]">
                                {recentEvents.length === 0 && (
                                    <div className="text-gray-400 text-center py-8 italic">Listening for mesh events...</div>
                                )}
                                {recentEvents.map((evt, i) => (
                                    <div key={i} className="flex space-x-2 animate-in slide-in-from-left duration-300">
                                        <span className="text-indigo-400 flex-shrink-0">[{evt.timestamp?.split('T')[1].split('.')[0]}]</span>
                                        <span className="text-gray-500 flex-shrink-0">{evt.node_id.slice(0, 8)}</span>
                                        <span className={`flex - grow ${evt.event === 'task_error' ? 'text-red-500' : 'text-gray-700 dark:text-gray-300'} `}>
                                            {evt.label || evt.event}: {JSON.stringify(evt.data)}
                                        </span>
                                    </div>
                                ))}
                            </div>
                        </div>
                    </div>
                )
                }
            </main>

            {/* CREATE NODE MODAL */}
            {
                showCreateModal && (
                    <div className="fixed inset-0 z-[100] flex items-center justify-center bg-black/50 backdrop-blur-sm p-4">
                        <div className="bg-white dark:bg-gray-800 rounded-2xl w-full max-w-lg shadow-2xl overflow-hidden animate-in zoom-in-95 duration-200">
                            <div className="px-6 py-4 border-b dark:border-gray-700 flex justify-between items-center">
                                <h3 className="font-bold text-gray-900 dark:text-white">Register Agent Node</h3>
                                <button onClick={() => setShowCreateModal(false)} className="text-gray-400 hover:text-gray-600 transition-colors">
                                    <svg className="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" /></svg>
                                </button>
                            </div>
                            <form onSubmit={handleCreateNode} className="p-6 space-y-4">
                                <div>
                                    <label className="block text-xs font-bold text-gray-500 uppercase tracking-widest mb-1">Node identifier (Slug)</label>
                                    <input
                                        required
                                        className="w-full bg-gray-50 dark:bg-gray-700 border-none rounded-xl px-4 py-3 text-sm focus:ring-2 focus:ring-indigo-500 transition-all dark:text-white"
                                        placeholder="e.g. macbook-m3-local"
                                        value={newNode.node_id}
                                        onChange={e => setNewNode({ ...newNode, node_id: e.target.value })}
                                    />
                                </div>
                                <div>
                                    <label className="block text-xs font-bold text-gray-500 uppercase tracking-widest mb-1">Display Name</label>
                                    <input
                                        required
                                        className="w-full bg-gray-50 dark:bg-gray-700 border-none rounded-xl px-4 py-3 text-sm focus:ring-2 focus:ring-indigo-500 transition-all dark:text-white"
                                        placeholder="e.g. My Primary MacBook"
                                        value={newNode.display_name}
                                        onChange={e => setNewNode({ ...newNode, display_name: e.target.value })}
                                    />
                                </div>
                                <div>
                                    <label className="block text-xs font-bold text-gray-500 uppercase tracking-widest mb-1">Description</label>
                                    <textarea
                                        className="w-full bg-gray-50 dark:bg-gray-700 border-none rounded-xl px-4 py-3 text-sm focus:ring-2 focus:ring-indigo-500 transition-all dark:text-white h-24"
                                        placeholder="What is this node used for?"
                                        value={newNode.description}
                                        onChange={e => setNewNode({ ...newNode, description: e.target.value })}
                                    />
                                </div>

                                <div className="flex justify-end space-x-2 pt-4">
                                    <button type="button" onClick={() => setShowCreateModal(false)} className="px-5 py-2 text-sm font-semibold text-gray-500 hover:text-gray-700">Cancel</button>
                                    <button type="submit" className="bg-indigo-600 hover:bg-indigo-700 text-white px-6 py-2 rounded-xl text-sm font-bold shadow-lg shadow-indigo-600/20 active:scale-95 transition-all">
                                        Register & Close
                                    </button>
                                </div>
                            </form>
                        </div>
                    </div>
                )
            }
        </div >
    );
};

export default NodesPage;