Skip to main content
Glama

NetBrain MCP

by NorthLaneMS
index.html155 kB
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>{{ title }}</title> <!-- CSS样式 --> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"> <link rel="stylesheet" href="{{ url_for('static', path='js/xterm/xterm.css') }}"> <link rel="stylesheet" href="{{ url_for('static', path='css/styles.css') }}"> <link rel="stylesheet" href="{{ url_for('static', path='css/terminal.css') }}"> <link rel="stylesheet" href="{{ url_for('static', path='css/topology.css') }}"> <!-- JavaScript依赖 --> <script src="https://d3js.org/d3.v7.min.js"></script> <script src="{{ url_for('static', path='js/xterm/xterm.js') }}"></script> <script src="{{ url_for('static', path='js/xterm/xterm-addon-fit.js') }}"></script> <script src="{{ url_for('static', path='js/xterm/xterm-addon-web-links.js') }}"></script> <script src="{{ url_for('static', path='js/session-manager.js') }}"></script> <script src="{{ url_for('static', path='js/terminal.js') }}"></script> <script src="{{ url_for('static', path='js/topology.js') }}"></script> <script src="{{ url_for('static', path='js/dashboard.js') }}"></script> <!-- 全局主题初始化脚本 --> <script> // 立即应用保存的主题,避免闪烁 (function() { const savedTheme = localStorage.getItem('theme') || 'light'; document.documentElement.setAttribute('data-theme', savedTheme); // 页面加载完成后应用logo切换 document.addEventListener('DOMContentLoaded', function() { const loginLogo = document.getElementById('login-logo'); const headerLogo = document.getElementById('header-logo'); if (savedTheme === 'dark') { // 暗色主题使用logod.png if (loginLogo) { loginLogo.src = loginLogo.src.replace('logo.png', 'logod.png'); } if (headerLogo) { headerLogo.src = headerLogo.src.replace('logo.png', 'logod.png'); } } }); })(); </script> </head> <body> <!-- 容器包含所有页面内容,通过JS控制显示/隐藏 --> <div id="app"> <!-- 登录页面 --> <div id="login-page" class="page"> <div class="login-container"> <div class="login-logo"> <img id="login-logo" src="{{ url_for('static', path='images/logo.png') }}" alt="NetBrain MCP" class="logo"> </div> <h1 class="login-title">NetBrain MCP</h1> <p class="login-description"> 通过MCP协议连接大型语言模型与网络设备 </p> <button class="btn btn-large" id="start-btn">开始使用</button> <div class="copyright"> © 2025 NetBrain MCP </div> </div> </div> <!-- 主页内容 --> <div id="home-page" class="page" style="display: none;"> <header class="header"> <div class="header-container"> <div class="header-logo"> <img id="header-logo" src="{{ url_for('static', path='images/logo.png') }}" alt="NetBrain MCP"> <h1>NetBrain MCP</h1> </div> <nav class="nav-links"> <a href="#dashboard" class="nav-link active" data-page="dashboard"><i class="fas fa-gauge-high"></i> 仪表盘</a> <a href="#devices" class="nav-link" data-page="devices"><i class="fas fa-server"></i> 设备</a> <a href="#terminal" class="nav-link" data-page="terminal"><i class="fas fa-terminal"></i> 终端</a> <a href="#topology" class="nav-link" data-page="topology"><i class="fas fa-network-wired"></i> 拓扑</a> </nav> </div> </header> <div class="main-content"> <div class="container"> <!-- 仪表盘页面 --> <div id="dashboard-content" class="page-content"> <div class="dashboard-header"> <h1 class="page-title"> <i class="fas fa-gauge-high"></i> 仪表盘 </h1> <div class="dashboard-controls"> <div class="theme-switcher"> <label class="switch"> <input type="checkbox" id="theme-toggle"> <span class="slider"> <i class="fas fa-sun"></i> <i class="fas fa-moon"></i> </span> </label> <span class="theme-label">主题模式</span> </div> <button id="refresh-dashboard" class="btn btn-sm"> <i class="fas fa-sync"></i> 刷新 </button> </div> </div> <!-- 统计卡片网格 --> <div class="stats-cards-grid"> <div class="stat-card"> <div class="stat-icon devices"> <i class="fas fa-server"></i> </div> <div class="stat-content"> <div class="stat-number" id="dashboard-devices-count">0</div> <div class="stat-label">网络设备</div> <div class="stat-change positive" id="dashboard-devices-change">+0</div> </div> </div> <div class="stat-card"> <div class="stat-icon connections"> <i class="fas fa-link"></i> </div> <div class="stat-content"> <div class="stat-number" id="dashboard-connections-count">0</div> <div class="stat-label">活跃连接</div> <div class="stat-change neutral" id="dashboard-connections-change">0</div> </div> </div> <div class="stat-card"> <div class="stat-icon topology"> <i class="fas fa-project-diagram"></i> </div> <div class="stat-content"> <div class="stat-number" id="dashboard-topology-nodes">0</div> <div class="stat-label">拓扑节点</div> <div class="stat-change positive" id="dashboard-topology-change">+0</div> </div> </div> <div class="stat-card"> <div class="stat-icon sessions"> <i class="fas fa-terminal"></i> </div> <div class="stat-content"> <div class="stat-number" id="dashboard-sessions-count">0</div> <div class="stat-label">终端会话</div> <div class="stat-change positive" id="dashboard-sessions-change">+0</div> </div> </div> </div> <!-- 主要内容区域 --> <div class="dashboard-main-content"> <!-- 左侧列 --> <div class="dashboard-left-column"> <!-- 系统状态卡片 --> <div class="dashboard-card"> <div class="card-header"> <h3><i class="fas fa-heartbeat"></i> 系统状态</h3> <div class="card-actions"> <button class="btn-icon" onclick="refreshSystemStatus()"> <i class="fas fa-sync"></i> </button> </div> </div> <div class="card-content"> <div class="status-grid"> <div class="status-item"> <div class="status-icon online"> <i class="fas fa-check-circle"></i> </div> <div class="status-info"> <div class="status-label">MCP服务器</div> <div class="status-value">运行正常</div> </div> </div> <div class="status-item"> <div class="status-icon online"> <i class="fas fa-check-circle"></i> </div> <div class="status-info"> <div class="status-label">Web服务</div> <div class="status-value">运行正常</div> </div> </div> <div class="status-item"> <div class="status-icon" id="database-status"> <i class="fas fa-check-circle"></i> </div> <div class="status-info"> <div class="status-label">数据存储</div> <div class="status-value" id="database-status-text">正常</div> </div> </div> <div class="status-item"> <div class="status-icon" id="network-status"> <i class="fas fa-wifi"></i> </div> <div class="status-info"> <div class="status-label">网络连接</div> <div class="status-value" id="network-status-text">检查中...</div> </div> </div> </div> </div> </div> </div> <!-- 右侧列 --> <div class="dashboard-right-column"> <!-- 近期活动卡片 --> <div class="dashboard-card"> <div class="card-header"> <h3><i class="fas fa-clock"></i> 近期活动</h3> <div class="card-actions"> <button class="btn-icon" onclick="clearActivities()"> <i class="fas fa-trash"></i> </button> </div> </div> <div class="card-content"> <div id="recent-activities" class="activities-list"> <div class="activity-item"> <div class="activity-icon info"> <i class="fas fa-info-circle"></i> </div> <div class="activity-content"> <div class="activity-text">系统启动成功</div> <div class="activity-time">刚刚</div> </div> </div> </div> </div> </div> <!-- 设备概览卡片 --> <div class="dashboard-card"> <div class="card-header"> <h3><i class="fas fa-network-wired"></i> 设备概览</h3> <div class="card-actions"> <button class="btn-icon" onclick="refreshDevicesOverview()"> <i class="fas fa-sync"></i> </button> </div> </div> <div class="card-content"> <div id="devices-overview" class="devices-overview"> <div class="no-devices"> <i class="fas fa-server"></i> <p>暂无设备</p> <button class="btn btn-sm" onclick="goToDevices()"> <i class="fas fa-plus"></i> 添加设备 </button> </div> </div> </div> </div> </div> </div> <!-- 系统信息footer --> <div class="dashboard-footer"> <div class="system-info"> <span><i class="fas fa-code"></i> NetBrain MCP v1.0</span> <span><i class="fas fa-calendar"></i> <span id="current-time"></span></span> <span><i class="fas fa-user"></i> 管理员</span> </div> </div> </div> <!-- 设备管理页面 --> <div id="devices-content" class="page-content" style="display: none;"> <h1 class="page-title">设备管理</h1> <div class="device-management-container"> <!-- 设备管理卡片 --> <div class="card device-card"> <div class="device-manager"> <div class="device-list-header"> <h2><i class="fas fa-server"></i> 设备列表</h2> <button id="add-device-btn" class="btn"><i class="fas fa-plus"></i> 添加设备</button> </div> <div id="device-list" class="item-list"> <div class="device-list-empty"> <p>尚未添加任何设备</p> </div> <!-- 设备列表会通过JavaScript动态加载 --> </div> </div> </div> <!-- 凭据管理卡片 --> <div class="card credential-card"> <div class="credential-manager"> <div class="credential-list-header"> <h2><i class="fas fa-key"></i> 凭据列表</h2> <button id="add-credential-btn" class="btn"><i class="fas fa-plus"></i> 添加凭据</button> </div> <div id="credential-list" class="item-list"> <div class="credential-list-empty"> <p>尚未添加任何凭据</p> </div> <!-- 凭据列表会通过JavaScript动态加载 --> </div> </div> </div> </div> </div> <!-- 终端页面 - 多标签页终端实现 --> <div id="terminal-content" class="page-content"> <h1 class="page-title">终端控制台</h1> <div class="terminal-layout"> <!-- 会话管理面板 - 移到左侧 --> <div class="sessions-card"> <div class="sessions-header"> <h2><i class="fas fa-network-wired"></i> 会话管理</h2> <div class="sessions-actions"> <button id="new-session-btn" class="btn btn-sm"><i class="fas fa-plus"></i> 新建会话</button> <button id="save-session-btn" class="btn btn-sm"><i class="fas fa-save"></i> 保存会话</button> <button id="manage-groups-btn" class="btn btn-sm"><i class="fas fa-folder"></i> 管理分组</button> </div> </div> <div class="sessions-container"> <!-- 活跃会话 --> <div class="sessions-section"> <h3><i class="fas fa-bolt"></i> 活跃会话</h3> <div id="active-sessions-list" class="sessions-list"> <!-- 会话项将通过JavaScript动态生成 --> <div class="session-empty">暂无活跃会话</div> </div> </div> <!-- 保存的会话 --> <div class="sessions-section"> <h3><i class="fas fa-bookmark"></i> 保存的会话</h3> <div id="saved-sessions-list" class="sessions-list"> <!-- 保存的会话项将通过JavaScript动态生成 --> <div class="session-empty">暂无保存的会话</div> </div> </div> <!-- 会话组 --> <div class="sessions-section"> <h3><i class="fas fa-folder"></i> 会话组</h3> <div id="session-groups-list" class="sessions-list"> <!-- 会话组项将通过JavaScript动态生成 --> <div class="session-empty">暂无会话组</div> </div> </div> </div> </div> <!-- 终端窗口 - 移到右侧 --> <div class="terminal-card"> <div class="terminal-container"> <!-- 终端工具栏 --> <div class="terminal-toolbar"> <div class="terminal-tabs"> <!-- 标签页将由JavaScript动态创建 --> </div> <div class="terminal-actions"> <button id="reconnect-terminal" class="btn btn-sm"><i class="fas fa-sync"></i> 重新连接</button> <button id="connect-device" class="btn btn-sm"><i class="fas fa-plug"></i> 连接设备</button> </div> </div> <!-- 终端窗口容器 --> <div class="terminal-windows"> <!-- 终端实例将由JavaScript动态创建 --> </div> </div> </div> </div> </div> <!-- 网络拓扑页面 --> <div id="topology-content" class="page-content" style="display: none;"> <h1 class="page-title">网络拓扑</h1> <!-- 拓扑控制工具栏 --> <div class="topology-toolbar"> <div class="toolbar-section"> <h3><i class="fas fa-play"></i> 发现控制</h3> <div class="toolbar-actions"> <button id="discover-topology-btn" class="btn btn-primary"> <i class="fas fa-search"></i> 发现拓扑 </button> <button id="refresh-topology-btn" class="btn"> <i class="fas fa-sync"></i> 刷新 </button> <button id="clear-topology-btn" class="btn btn-outline"> <i class="fas fa-trash"></i> 清空 </button> </div> </div> <div class="toolbar-section"> <h3><i class="fas fa-eye"></i> 视图控制</h3> <div class="toolbar-actions"> <button id="fit-topology-btn" class="btn"> <i class="fas fa-expand"></i> 适应窗口 </button> <button id="zoom-in-btn" class="btn"> <i class="fas fa-search-plus"></i> 放大 </button> <button id="zoom-out-btn" class="btn"> <i class="fas fa-search-minus"></i> 缩小 </button> <button id="center-topology-btn" class="btn"> <i class="fas fa-crosshairs"></i> 居中 </button> </div> </div> <div class="toolbar-section"> <h3><i class="fas fa-filter"></i> 过滤选项</h3> <div class="toolbar-actions"> <select id="vendor-filter" class="form-control form-control-sm"> <option value="">所有厂商</option> <option value="cisco">Cisco</option> <option value="huawei">华为</option> <option value="h3c">H3C</option> <option value="juniper">Juniper</option> </select> <select id="device-type-filter" class="form-control form-control-sm"> <option value="">所有类型</option> <option value="router">路由器</option> <option value="switch">交换机</option> <option value="firewall">防火墙</option> </select> <button id="apply-filter-btn" class="btn btn-sm"> <i class="fas fa-check"></i> 应用 </button> </div> </div> </div> <!-- 拓扑主界面 --> <div class="topology-main"> <!-- 拓扑可视化区域 --> <div class="topology-visualization"> <div id="topology-canvas" class="topology-canvas"> <!-- 拓扑加载状态 --> <div id="topology-loading" class="topology-loading"> <div class="loading-spinner"> <i class="fas fa-spinner fa-spin"></i> </div> <div class="loading-text">正在加载拓扑数据...</div> </div> <!-- 拓扑空状态 --> <div id="topology-empty" class="topology-empty" style="display: none;"> <div class="empty-icon"> <i class="fas fa-project-diagram"></i> </div> <div class="empty-title">暂无拓扑数据</div> <div class="empty-description"> 请先添加网络设备,然后点击"发现拓扑"按钮开始网络拓扑发现 </div> <button id="goto-devices-btn" class="btn btn-primary"> <i class="fas fa-server"></i> 管理设备 </button> </div> <!-- 拓扑SVG容器 --> <svg id="topology-svg" width="100%" height="100%" style="display: none;"> <!-- 定义箭头标记 --> <defs> <marker id="arrowhead" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto"> <polygon points="0 0, 10 3.5, 0 7" fill="#666" /> </marker> <marker id="arrowhead-selected" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto"> <polygon points="0 0, 10 3.5, 0 7" fill="#2563eb" /> </marker> </defs> <!-- 拓扑图层 --> <g id="topology-graph"> <!-- 链路组 --> <g id="links-group"></g> <!-- 节点组 --> <g id="nodes-group"></g> <!-- 标签组 --> <g id="labels-group"></g> </g> </svg> </div> <!-- 拓扑操作提示 --> <div class="topology-hints"> <div class="hint-item"> <i class="fas fa-mouse-pointer"></i> <span>拖拽节点移动</span> </div> <div class="hint-item"> <i class="fas fa-mouse"></i> <span>滚轮缩放</span> </div> <div class="hint-item"> <i class="fas fa-hand-point-up"></i> <span>双击左键菜单</span> </div> <div class="hint-item"> <i class="fas fa-search"></i> <span>双击适应</span> </div> </div> </div> <!-- 拓扑信息面板 --> <div class="topology-info-panel"> <!-- 拓扑统计 --> <div class="info-section"> <h3><i class="fas fa-chart-bar"></i> 拓扑统计</h3> <div class="stats-grid"> <div class="stat-item"> <div class="stat-value" id="stat-nodes">0</div> <div class="stat-label">设备节点</div> </div> <div class="stat-item"> <div class="stat-value" id="stat-links">0</div> <div class="stat-label">连接链路</div> </div> <div class="stat-item"> <div class="stat-value" id="stat-protocols">0</div> <div class="stat-label">发现协议</div> </div> <div class="stat-item"> <div class="stat-value" id="stat-vendors">0</div> <div class="stat-label">设备厂商</div> </div> </div> </div> <!-- 选中节点信息 --> <div class="info-section"> <h3><i class="fas fa-info-circle"></i> 节点详情</h3> <div id="node-details" class="node-details"> <div class="no-selection"> <i class="fas fa-mouse-pointer"></i> <span>点击节点查看详细信息</span> </div> </div> </div> <!-- 链路信息 --> <div class="info-section"> <h3><i class="fas fa-link"></i> 链路详情</h3> <div id="link-details" class="link-details"> <div class="no-selection"> <i class="fas fa-mouse-pointer"></i> <span>点击链路查看连接信息</span> </div> </div> </div> <!-- 操作面板 --> <div class="info-section"> <h3><i class="fas fa-tools"></i> 快速操作</h3> <div class="action-buttons"> <button id="connect-to-device-btn" class="btn btn-sm" disabled> <i class="fas fa-terminal"></i> 连接终端 </button> <button id="ping-device-btn" class="btn btn-sm" disabled> <i class="fas fa-satellite-dish"></i> Ping测试 </button> <button id="discover-neighbors-btn" class="btn btn-sm" disabled> <i class="fas fa-search"></i> 发现邻居 </button> <button id="device-properties-btn" class="btn btn-sm" disabled> <i class="fas fa-cog"></i> 设备属性 </button> </div> </div> </div> </div> <!-- 拓扑发现对话框 --> <div id="topology-discover-modal" class="modal"> <div class="modal-content"> <div class="modal-header"> <h2>网络拓扑发现</h2> <span class="close-modal">&times;</span> </div> <div class="modal-body"> <form id="topology-discover-form"> <div class="form-group"> <label>选择种子设备</label> <div class="device-selection"> <div id="seed-devices-list" class="checkbox-list"> <!-- 设备列表将通过JavaScript动态加载 --> <div class="no-devices"> <p>没有可用的设备。请先在设备管理页面添加设备。</p> </div> </div> </div> </div> <div class="form-group"> <label>发现选项</label> <div class="discovery-options"> <label class="checkbox-label"> <input type="checkbox" id="discover-cdp" checked> <span class="checkmark"></span> 启用CDP发现 </label> <label class="checkbox-label"> <input type="checkbox" id="discover-lldp" checked> <span class="checkmark"></span> 启用LLDP发现 </label> </div> </div> <div class="form-actions"> <button type="button" class="btn btn-outline close-btn"> <i class="fas fa-times"></i> 取消 </button> <button type="submit" class="btn btn-primary"> <i class="fas fa-search"></i> 开始发现 </button> </div> </form> <!-- 发现进度 --> <div id="discovery-progress" class="discovery-progress" style="display: none;"> <div class="progress-header"> <h3>正在发现网络拓扑...</h3> <div class="progress-spinner"> <i class="fas fa-spinner fa-spin"></i> </div> </div> <div class="progress-details"> <div id="discovery-status">正在连接种子设备...</div> <div class="progress-bar"> <div id="discovery-progress-bar" class="progress-fill"></div> </div> </div> </div> </div> </div> </div> </div> <!-- 设置页面 --> <div id="settings-content" class="page-content" style="display: none;"> <h1 class="page-title">系统设置</h1> <div class="card"> <p>系统设置功能将在后续版本中推出</p> </div> </div> </div> </div> <footer class="footer"> <div class="container"> © 2025 NetBrain MCP </div> </footer> </div> <!-- 设备连接对话框 --> <div id="device-connect-modal" class="modal"> <div class="modal-content"> <div class="modal-header"> <h2>连接设备</h2> <span class="close-modal">&times;</span> </div> <div class="modal-body"> <form id="connect-device-form"> <div class="form-group"> <label for="device-select">选择设备</label> <select id="device-select" class="form-control" required> <option value="">-- 请选择设备 --</option> <!-- 设备列表将通过API动态加载 --> </select> </div> <div class="form-group"> <label for="credential-select">选择凭据</label> <select id="credential-select" class="form-control" required> <option value="">-- 请选择凭据 --</option> <!-- 凭据列表将通过API动态加载 --> </select> </div> <div class="form-group"> <label for="terminal-name">终端名称</label> <input type="text" id="terminal-name" class="form-control" placeholder="设备名称-会话"> </div> <div class="form-actions"> <button type="button" class="btn btn-outline close-btn"><i class="fas fa-times"></i> 取消</button> <button type="submit" class="btn"><i class="fas fa-link"></i> 连接</button> </div> </form> </div> </div> </div> <!-- 保存会话对话框 --> <div id="save-session-modal" class="modal"> <div class="modal-content"> <div class="modal-header"> <h2>保存会话</h2> <span class="close-modal">&times;</span> </div> <div class="modal-body"> <form id="save-session-form"> <div class="form-group"> <label for="active-session-select">选择活跃会话</label> <select id="active-session-select" class="form-control" required> <option value="">-- 请选择会话 --</option> <!-- 活跃会话列表将通过JS动态加载 --> </select> </div> <div class="form-group"> <label for="session-name">会话名称</label> <input type="text" id="session-name" class="form-control" placeholder="我的设备会话" required> </div> <div class="form-group"> <label for="session-description">会话描述</label> <textarea id="session-description" class="form-control" placeholder="描述此会话的用途或内容"></textarea> </div> <div class="form-group"> <label for="session-group">所属分组(可选)</label> <select id="session-group" class="form-control"> <option value="">-- 无分组 --</option> <!-- 会话组列表将通过JS动态加载 --> </select> </div> <div class="form-actions"> <button type="button" class="btn btn-outline close-btn"><i class="fas fa-times"></i> 取消</button> <button type="submit" class="btn"><i class="fas fa-save"></i> 保存</button> </div> </form> </div> </div> </div> <!-- 管理会话组对话框 --> <div id="manage-groups-modal" class="modal"> <div class="modal-content"> <div class="modal-header"> <h2>管理会话组</h2> <span class="close-modal">&times;</span> </div> <div class="modal-body"> <div class="session-groups-manager"> <!-- 创建新组 --> <div class="form-group"> <label for="new-group-name">创建新组</label> <div class="group-input-container"> <input type="text" id="new-group-name" class="form-control" placeholder="输入新组名称"> <button id="create-group-btn" class="btn"><i class="fas fa-plus"></i> 创建</button> </div> </div> <!-- 会话组列表 --> <div class="session-groups-list"> <h3>现有会话组</h3> <div id="manage-groups-list"> <!-- 会话组列表将通过JS动态加载 --> <div class="group-empty">暂无会话组</div> </div> </div> </div> <div class="form-actions right"> <button type="button" class="btn close-btn"><i class="fas fa-check"></i> 完成</button> </div> </div> </div> </div> <!-- 添加设备对话框 --> <div id="add-device-modal" class="modal"> <div class="modal-content"> <div class="modal-header"> <h2>添加网络设备</h2> <span class="close-modal">&times;</span> </div> <div class="modal-body"> <form id="add-device-form"> <div class="form-group"> <label for="device-name">设备名称</label> <input type="text" id="device-name" class="form-control" placeholder="例如:Core-Router-01" required> </div> <div class="form-group"> <label for="device-ip">IP地址</label> <input type="text" id="device-ip" class="form-control" placeholder="例如:192.168.1.1" required> </div> <div class="form-group"> <label for="device-type">设备类型</label> <select id="device-type" class="form-control" required> <option value="router">路由器</option> <option value="switch">交换机</option> <option value="firewall">防火墙</option> <option value="load_balancer">负载均衡器</option> <option value="wireless_controller">无线控制器</option> <option value="access_point">接入点</option> <option value="other">其他</option> </select> </div> <div class="form-group"> <label for="device-vendor">设备厂商</label> <select id="device-vendor" class="form-control" required> <option value="cisco">思科(Cisco)</option> <option value="huawei">华为(Huawei)</option> <option value="h3c">新华三(H3C)</option> <option value="juniper">Juniper</option> <option value="arista">Arista</option> <option value="fortinet">Fortinet</option> <option value="checkpoint">CheckPoint</option> <option value="other">其他</option> </select> </div> <div class="form-group"> <label for="device-platform">平台</label> <input type="text" id="device-platform" class="form-control" placeholder="例如:ISR 4000系列"> </div> <div class="form-group"> <label for="device-model">设备型号</label> <input type="text" id="device-model" class="form-control" placeholder="例如:ISR 4431"> </div> <div class="form-group"> <label for="device-os">操作系统版本</label> <input type="text" id="device-os" class="form-control" placeholder="例如:IOS-XE 16.9.3"> </div> <div class="form-group"> <label for="device-location">位置</label> <input type="text" id="device-location" class="form-control" placeholder="例如:主数据中心1层"> </div> <div class="form-group"> <label for="device-description">描述</label> <textarea id="device-description" class="form-control" placeholder="设备用途和其他信息"></textarea> </div> <div class="form-group"> <label for="device-tags">标签</label> <input type="text" id="device-tags" class="form-control" placeholder="用逗号分隔多个标签,例如:核心,生产,关键"> </div> <div class="form-group"> <label for="device-credential">关联凭据 (可选)</label> <select id="device-credential" class="form-control"> <option value="">-- 无凭据 --</option> <!-- 凭据列表将通过JS动态加载 --> </select> <small class="form-text text-muted">拓扑发现和设备连接需要凭据</small> </div> <div class="form-actions"> <button type="button" class="btn btn-outline close-btn"><i class="fas fa-times"></i> 取消</button> <button type="submit" class="btn"><i class="fas fa-plus"></i> 添加设备</button> </div> </form> </div> </div> </div> <!-- 添加凭据对话框 --> <div id="add-credential-modal" class="modal"> <div class="modal-content"> <div class="modal-header"> <h2>添加设备凭据</h2> <span class="close-modal">&times;</span> </div> <div class="modal-body"> <form id="add-credential-form"> <div class="form-group"> <label for="credential-name">凭据名称</label> <input type="text" id="credential-name" class="form-control" placeholder="例如:Cisco设备SSH凭据" required> </div> <div class="form-group"> <label for="credential-username">用户名</label> <input type="text" id="credential-username" class="form-control" placeholder="例如:admin" required> </div> <div class="form-group"> <label for="credential-password">密码</label> <input type="password" id="credential-password" class="form-control" placeholder="输入密码" required> </div> <div class="form-group"> <label for="credential-protocol">连接协议</label> <select id="credential-protocol" class="form-control" required> <option value="ssh">SSH</option> <option value="telnet">Telnet</option> </select> </div> <div class="form-group"> <label for="credential-port">端口</label> <input type="number" id="credential-port" class="form-control" placeholder="22"> <small class="form-text text-muted">SSH默认为22,Telnet默认为23</small> </div> <div class="form-group"> <label for="credential-enable">Enable密码 (可选)</label> <input type="password" id="credential-enable" class="form-control" placeholder="思科设备的特权模式密码"> </div> <div class="form-actions"> <button type="button" class="btn btn-outline close-btn"><i class="fas fa-times"></i> 取消</button> <button type="submit" class="btn"><i class="fas fa-plus"></i> 添加凭据</button> </div> </form> </div> </div> </div> <!-- 修改设备对话框 --> <div id="edit-device-modal" class="modal"> <div class="modal-content"> <div class="modal-header"> <h2>修改网络设备</h2> <span class="close-modal">&times;</span> </div> <div class="modal-body"> <form id="edit-device-form"> <input type="hidden" id="edit-device-id"> <div class="form-group"> <label for="edit-device-name">设备名称</label> <input type="text" id="edit-device-name" class="form-control" placeholder="例如:Core-Router-01" required> </div> <div class="form-group"> <label for="edit-device-ip">IP地址</label> <input type="text" id="edit-device-ip" class="form-control" placeholder="例如:192.168.1.1" required> </div> <div class="form-group"> <label for="edit-device-type">设备类型</label> <select id="edit-device-type" class="form-control" required> <option value="router">路由器</option> <option value="switch">交换机</option> <option value="firewall">防火墙</option> <option value="load_balancer">负载均衡器</option> <option value="wireless_controller">无线控制器</option> <option value="access_point">接入点</option> <option value="other">其他</option> </select> </div> <div class="form-group"> <label for="edit-device-vendor">设备厂商</label> <select id="edit-device-vendor" class="form-control" required> <option value="cisco">思科(Cisco)</option> <option value="huawei">华为(Huawei)</option> <option value="h3c">新华三(H3C)</option> <option value="juniper">Juniper</option> <option value="arista">Arista</option> <option value="fortinet">Fortinet</option> <option value="checkpoint">CheckPoint</option> <option value="other">其他</option> </select> </div> <div class="form-group"> <label for="edit-device-platform">平台</label> <input type="text" id="edit-device-platform" class="form-control" placeholder="例如:ISR 4000系列"> </div> <div class="form-group"> <label for="edit-device-model">设备型号</label> <input type="text" id="edit-device-model" class="form-control" placeholder="例如:ISR 4431"> </div> <div class="form-group"> <label for="edit-device-os">操作系统版本</label> <input type="text" id="edit-device-os" class="form-control" placeholder="例如:IOS-XE 16.9.3"> </div> <div class="form-group"> <label for="edit-device-status">状态</label> <select id="edit-device-status" class="form-control"> <option value="online">在线</option> <option value="offline">离线</option> <option value="unreachable">不可达</option> <option value="maintenance">维护中</option> <option value="unknown">未知</option> </select> </div> <div class="form-group"> <label for="edit-device-location">位置</label> <input type="text" id="edit-device-location" class="form-control" placeholder="例如:主数据中心1层"> </div> <div class="form-group"> <label for="edit-device-description">描述</label> <textarea id="edit-device-description" class="form-control" placeholder="设备用途和其他信息"></textarea> </div> <div class="form-group"> <label for="edit-device-tags">标签</label> <input type="text" id="edit-device-tags" class="form-control" placeholder="用逗号分隔多个标签,例如:核心,生产,关键"> </div> <div class="form-group"> <label for="edit-device-credential">关联凭据 (可选)</label> <select id="edit-device-credential" class="form-control"> <option value="">-- 无凭据 --</option> <!-- 凭据列表将通过JS动态加载 --> </select> <small class="form-text text-muted">拓扑发现和设备连接需要凭据</small> </div> <div class="form-actions"> <button type="button" class="btn btn-outline close-btn"><i class="fas fa-times"></i> 取消</button> <button type="submit" class="btn"><i class="fas fa-save"></i> 保存修改</button> </div> </form> </div> </div> </div> <!-- 修改凭据对话框 --> <div id="edit-credential-modal" class="modal"> <div class="modal-content"> <div class="modal-header"> <h2>修改设备凭据</h2> <span class="close-modal">&times;</span> </div> <div class="modal-body"> <form id="edit-credential-form"> <input type="hidden" id="edit-credential-id"> <div class="form-group"> <label for="edit-credential-name">凭据名称</label> <input type="text" id="edit-credential-name" class="form-control" placeholder="例如:Cisco设备SSH凭据" required> </div> <div class="form-group"> <label for="edit-credential-username">用户名</label> <input type="text" id="edit-credential-username" class="form-control" placeholder="例如:admin" required> </div> <div class="form-group"> <label for="edit-credential-password">密码</label> <input type="password" id="edit-credential-password" class="form-control" placeholder="输入新密码(留空表示不修改)"> <small class="form-text text-muted">留空表示不修改密码</small> </div> <div class="form-group"> <label for="edit-credential-protocol">连接协议</label> <select id="edit-credential-protocol" class="form-control" required> <option value="ssh">SSH</option> <option value="telnet">Telnet</option> </select> </div> <div class="form-group"> <label for="edit-credential-port">端口</label> <input type="number" id="edit-credential-port" class="form-control" placeholder="22"> <small class="form-text text-muted">SSH默认为22,Telnet默认为23</small> </div> <div class="form-group"> <label for="edit-credential-enable">Enable密码 (可选)</label> <input type="password" id="edit-credential-enable" class="form-control" placeholder="思科设备的特权模式密码"> <small class="form-text text-muted">留空表示不修改Enable密码</small> </div> <div class="form-actions"> <button type="button" class="btn btn-outline close-btn"><i class="fas fa-times"></i> 取消</button> <button type="submit" class="btn"><i class="fas fa-save"></i> 保存修改</button> </div> </form> </div> </div> </div> </div> <style> /* 页面特定样式 */ .login-container { display: flex; flex-direction: column; align-items: center; justify-content: center; background-color: var(--light-bg); padding: 4rem 2.5rem; max-width: 400px; width: 90%; margin: 0 auto; text-align: center; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); border-radius: var(--border-radius); box-shadow: var(--box-shadow); } .login-logo { margin-bottom: 1.5rem; } .logo { max-width: 120px; } .login-title { font-size: 1.75rem; margin-bottom: 1rem; font-weight: 600; } .login-description { margin-bottom: 2rem; color: var(--light-text); font-size: 1rem; max-width: 300px; } .copyright { margin-top: 3rem; color: var(--light-text); font-size: 0.8rem; } .page { min-height: 100vh; width: 100%; } #login-page { background-color: var(--bg-color); position: relative; } .page-content { padding: 1rem 0 3rem; } .page-title { margin-bottom: 2rem; font-weight: 600; font-size: 1.75rem; } /* 增加整体容器最大宽度 */ .container { max-width: 1400px; width: 95%; margin: 0 auto; padding: 0 1rem; } /* 终端页面布局样式 */ .terminal-layout { display: flex; gap: 1.5rem; height: calc(100vh - 200px); min-height: 500px; } /* 会话管理面板左侧垂直布局 */ .sessions-card { width: 320px; flex-shrink: 0; background-color: var(--light-bg); border-radius: var(--border-radius); box-shadow: var(--box-shadow); padding: 1.5rem; display: flex; flex-direction: column; max-height: 100%; } /* 终端窗口右侧布局 */ .terminal-card { flex: 1; background-color: var(--light-bg); border-radius: var(--border-radius); box-shadow: var(--box-shadow); padding: 1.5rem; max-height: 100%; } /* 终端相关样式 */ .terminal-container { display: flex; flex-direction: column; height: 100%; background-color: #1a1a1a; border-radius: var(--border-radius); overflow: hidden; } .terminal-toolbar { display: flex; justify-content: space-between; background-color: #333; padding: 0.5rem; border-bottom: 1px solid #555; } .terminal-tabs { display: flex; flex-wrap: nowrap; overflow-x: auto; max-width: 80%; } .terminal-tab { padding: 0.5rem 1rem; background-color: #444; color: #ccc; cursor: pointer; margin-right: 0.5rem; border-radius: 4px 4px 0 0; user-select: none; white-space: nowrap; display: flex; align-items: center; } .terminal-tab.active { background-color: #666; color: white; } .terminal-tab-close { margin-left: 0.5rem; width: 16px; height: 16px; line-height: 16px; text-align: center; border-radius: 50%; background-color: #555; color: #ccc; } .terminal-tab-close:hover { background-color: #777; color: white; } .terminal-actions { display: flex; align-items: center; } .terminal-actions button { margin-left: 0.5rem; } .btn-sm { padding: 0.4rem 0.8rem; font-size: 0.85rem; } /* 按钮内图标样式 */ .btn i { margin-right: 5px; } .terminal-windows { flex: 1; position: relative; background-color: black; overflow: hidden; } .terminal-instance { position: absolute; top: 0; left: 0; width: 100%; height: 100%; padding: 0.5rem; display: none; } .terminal-instance.active { display: block; } /* 设备列表样式 */ .device-manager { width: 100%; } .device-list-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.5rem; } .device-list-empty { text-align: center; padding: 2rem; color: #777; } /* 模态框样式 */ .modal { display: none; position: fixed; z-index: 1000; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(0,0,0,0.6); backdrop-filter: blur(5px); } .modal-content { background-color: white; margin: 5% auto; /* 从10%改为5%,让对话框往上移 */ padding: 0; width: 90%; max-width: 500px; border-radius: var(--border-radius); box-shadow: var(--box-shadow); animation: modalFadeIn 0.3s; position: relative; top: -50px; /* 添加负的top值使对话框进一步上移 */ } .modal-header { padding: 1.5rem; border-bottom: 1px solid #eee; display: flex; justify-content: space-between; align-items: center; } .modal-header h2 { margin: 0; } .modal-body { padding: 1.5rem; } .close-modal { cursor: pointer; font-size: 1.5rem; font-weight: bold; color: #777; } .close-modal:hover { color: #333; } .form-actions { display: flex; justify-content: flex-end; gap: 1rem; margin-top: 1.5rem; } @keyframes modalFadeIn { from { opacity: 0; transform: translateY(-20px); } to { opacity: 1; transform: translateY(0); } } /* 导航栏图标样式 */ .nav-link i { margin-right: 8px; font-size: 16px; } /* 激活状态的导航链接图标样式 */ .nav-link.active i { color: var(--primary-color); } /* 会话管理面板样式 */ .sessions-header { display: flex; flex-direction: column; align-items: flex-start; margin-bottom: 1.2rem; border-bottom: 1px solid var(--border-color); padding-bottom: 1rem; } .sessions-header h2 { margin: 0 0 0.8rem 0; font-size: 1.2rem; display: flex; align-items: center; width: 100%; } .sessions-header h2 i { margin-right: 0.5rem; } .sessions-actions { display: flex; flex-wrap: wrap; gap: 8px; width: 100%; } .sessions-actions button { flex: 1; text-align: center; display: flex; align-items: center; justify-content: center; padding: 0.5rem 0.6rem; min-width: 90px; } .sessions-container { display: flex; flex-direction: column; gap: 1.2rem; overflow-y: auto; flex: 1; } .sessions-section { width: 100%; } .sessions-section h3 { font-size: 1rem; margin-bottom: 0.7rem; color: var(--secondary-color); display: flex; align-items: center; padding-left: 0.3rem; } .sessions-section h3 i { margin-right: 0.5rem; width: 16px; text-align: center; } .sessions-list { background-color: var(--bg-color); border-radius: 8px; padding: 0.8rem; min-height: 100px; max-height: 180px; overflow-y: auto; box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05); } .session-item { display: flex; justify-content: space-between; align-items: center; background-color: var(--light-bg); padding: 0.8rem 1rem; border-radius: 6px; margin-bottom: 0.7rem; cursor: pointer; transition: all 0.2s ease; border: 1px solid transparent; } .session-item:hover { transform: translateY(-2px); box-shadow: 0 4px 10px rgba(0, 0, 0, 0.05); border-color: var(--border-color); } .session-item-content { flex: 1; overflow: hidden; } .session-item-title { font-weight: 500; font-size: 0.95rem; margin-bottom: 0.25rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .session-item-details { font-size: 0.8rem; color: var(--light-text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .session-item-actions { display: flex; gap: 5px; margin-left: 0.5rem; } .session-item-action { width: 30px; height: 30px; border-radius: 50%; background-color: var(--bg-color); color: var(--text-color); border: none; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: all 0.2s ease; } .session-item-action:hover { background-color: var(--primary-color); color: white; } .session-empty { text-align: center; color: var(--light-text); font-size: 0.9rem; padding: 2rem 1rem; } .session-status { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 8px; } .session-status-active { background-color: #4CAF50; } .session-status-connecting { background-color: #FFC107; } .session-status-disconnected { background-color: #F44336; } .session-group-item { background-color: var(--light-bg); border-radius: 6px; margin-bottom: 0.7rem; border: 1px solid var(--border-color); } .session-group-header { display: flex; justify-content: space-between; align-items: center; padding: 0.8rem 1rem; cursor: pointer; } .session-group-content { padding: 0 0.8rem 0.8rem; display: none; } .session-group-content.active { display: block; } /* 对话框内的特殊样式 */ .group-input-container { display: flex; gap: 10px; } .group-input-container .form-control { flex: 1; } .form-actions.right { justify-content: flex-end; } .session-groups-list { margin-top: 1.5rem; } .session-groups-list h3 { font-size: 1.1rem; margin-bottom: 0.75rem; } .group-item { display: flex; justify-content: space-between; align-items: center; background-color: var(--bg-color); padding: 0.75rem; border-radius: 6px; margin-bottom: 0.5rem; } .group-item-name { font-weight: 500; } .group-item-actions { display: flex; gap: 5px; } .group-empty { text-align: center; color: var(--light-text); padding: 2rem 0; } /* 表单控件样式增强 */ textarea.form-control { min-height: 80px; resize: vertical; } /* 响应式布局调整 */ @media (max-width: 768px) { .terminal-layout { flex-direction: column; height: auto; } .sessions-card { width: 100%; height: auto; max-height: 400px; } .terminal-card { height: 500px; } } /* 设备管理页面样式 */ .device-management-container { display: flex; flex-wrap: wrap; gap: 1.5rem; } .device-card, .credential-card { flex: 1; min-width: 400px; } .device-manager, .credential-manager { width: 100%; } .device-list-header, .credential-list-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.5rem; padding-bottom: 1rem; border-bottom: 1px solid var(--border-color); } .device-list-header h2, .credential-list-header h2 { margin: 0; display: flex; align-items: center; font-size: 1.3rem; } .device-list-header h2 i, .credential-list-header h2 i { margin-right: 0.5rem; } .device-list-empty, .credential-list-empty { text-align: center; padding: 2rem; color: var(--light-text); background-color: var(--bg-color); border-radius: var(--border-radius); } .item-list { max-height: 500px; overflow-y: auto; } .device-item, .credential-item { display: flex; justify-content: space-between; align-items: center; background-color: var(--bg-color); padding: 1rem; border-radius: var(--border-radius); margin-bottom: 0.8rem; transition: all 0.2s ease; border: 1px solid transparent; } .device-item:hover, .credential-item:hover { border-color: var(--border-color); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); transform: translateY(-2px); } .device-item-content, .credential-item-content { flex: 1; } .device-item-title, .credential-item-title { font-weight: 500; font-size: 1.1rem; margin-bottom: 0.3rem; display: flex; align-items: center; } .device-item-title i, .credential-item-title i { margin-right: 0.5rem; width: 16px; text-align: center; } .device-item-subtitle, .credential-item-subtitle { font-size: 0.9rem; color: var(--light-text); } .device-item-details, .credential-item-details { font-size: 0.85rem; color: var(--light-text); margin-top: 0.5rem; } .device-item-actions, .credential-item-actions { display: flex; gap: 0.5rem; } .device-item-action, .credential-item-action { width: 32px; height: 32px; border-radius: 50%; background-color: var(--light-bg); color: var(--text-color); display: flex; align-items: center; justify-content: center; cursor: pointer; transition: all 0.2s ease; border: none; } .device-item-action:hover, .credential-item-action:hover { background-color: var(--primary-color); color: white; } .device-status { display: inline-block !important; width: 10px !important; height: 10px !important; border-radius: 50% !important; margin-right: 8px !important; flex-shrink: 0 !important; flex-grow: 0 !important; min-width: 10px !important; min-height: 10px !important; max-width: 10px !important; max-height: 10px !important; box-sizing: border-box !important; vertical-align: middle !important; position: relative !important; aspect-ratio: 1 / 1 !important; } .device-status-online { background-color: #4CAF50; } .device-status-offline { background-color: #F44336; } .device-status-unreachable { background-color: #FFC107; } .device-status-maintenance { background-color: #2196F3; } .device-status-unknown { background-color: #9E9E9E; } /* 设备详情悬浮卡片 */ .device-hover-card { position: absolute; background-color: white; border-radius: var(--border-radius); box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); padding: 1rem; z-index: 100; width: 300px; display: none; } .device-hover-card-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.5rem; padding-bottom: 0.5rem; border-bottom: 1px solid var(--border-color); } .device-hover-card-title { font-weight: 600; font-size: 1.1rem; } .device-hover-card-content { font-size: 0.9rem; } .device-hover-card-item { margin-bottom: 0.5rem; display: flex; } .device-hover-card-label { font-weight: 500; width: 80px; color: var(--light-text); } .device-hover-card-value { flex: 1; } /* 快速操作按钮 */ .quick-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; } .quick-action-btn { display: flex; flex-direction: column; align-items: center; gap: 0.5rem; padding: 1rem; background: var(--bg-color); border: 1px solid var(--border-color); border-radius: var(--border-radius); cursor: pointer; transition: var(--transition); text-decoration: none; color: var(--text-color); } .quick-action-btn:hover { background: var(--hover-bg); transform: translateY(-2px); box-shadow: var(--box-shadow); } .quick-action-btn i { font-size: 1.5rem; color: var(--primary-color); } .quick-action-btn span { font-size: 0.875rem; font-weight: 500; } .device-overview-status.unknown { background: var(--light-text); } </style> <script> // 设备管理UI类 class DeviceManagerUI { constructor() { // 获取DOM元素 this.deviceList = document.getElementById('device-list'); this.credentialList = document.getElementById('credential-list'); // 获取添加设备按钮和表单 this.addDeviceBtn = document.getElementById('add-device-btn'); this.addDeviceModal = document.getElementById('add-device-modal'); this.addDeviceForm = document.getElementById('add-device-form'); // 获取添加凭据按钮和表单 this.addCredentialBtn = document.getElementById('add-credential-btn'); this.addCredentialModal = document.getElementById('add-credential-modal'); this.addCredentialForm = document.getElementById('add-credential-form'); // 获取修改设备和凭据对话框 this.editDeviceModal = document.getElementById('edit-device-modal'); this.editDeviceForm = document.getElementById('edit-device-form'); this.editCredentialModal = document.getElementById('edit-credential-modal'); this.editCredentialForm = document.getElementById('edit-credential-form'); // 绑定添加设备按钮事件 this.addDeviceBtn.addEventListener('click', async () => { // 加载凭据列表到选择器 await this.loadCredentialsForSelect('device-credential'); this.addDeviceModal.style.display = 'block'; }); // 绑定添加凭据按钮事件 this.addCredentialBtn.addEventListener('click', () => { this.addCredentialModal.style.display = 'block'; }); // 绑定表单提交事件 this.addDeviceForm.addEventListener('submit', this.handleAddDevice.bind(this)); this.addCredentialForm.addEventListener('submit', this.handleAddCredential.bind(this)); // 绑定修改表单提交事件 this.editDeviceForm.addEventListener('submit', this.handleEditDevice.bind(this)); this.editCredentialForm.addEventListener('submit', this.handleEditCredential.bind(this)); // 绑定对话框关闭按钮事件 this.addDeviceModal.querySelector('.close-modal').addEventListener('click', () => { this.addDeviceModal.style.display = 'none'; }); this.addCredentialModal.querySelector('.close-modal').addEventListener('click', () => { this.addCredentialModal.style.display = 'none'; }); this.editDeviceModal.querySelector('.close-modal').addEventListener('click', () => { this.editDeviceModal.style.display = 'none'; }); this.editCredentialModal.querySelector('.close-modal').addEventListener('click', () => { this.editCredentialModal.style.display = 'none'; }); // 绑定取消按钮事件 this.addDeviceModal.querySelectorAll('.close-btn').forEach(btn => { btn.addEventListener('click', () => { this.addDeviceModal.style.display = 'none'; }); }); this.addCredentialModal.querySelectorAll('.close-btn').forEach(btn => { btn.addEventListener('click', () => { this.addCredentialModal.style.display = 'none'; }); }); this.editDeviceModal.querySelectorAll('.close-btn').forEach(btn => { btn.addEventListener('click', () => { this.editDeviceModal.style.display = 'none'; }); }); this.editCredentialModal.querySelectorAll('.close-btn').forEach(btn => { btn.addEventListener('click', () => { this.editCredentialModal.style.display = 'none'; }); }); // 加载设备和凭据列表 this.loadDevices(); this.loadCredentials(); } // 加载设备列表 async loadDevices() { try { const response = await fetch('/api/devices'); const devices = await response.json(); // 清空设备列表 this.deviceList.innerHTML = ''; if (devices.length === 0) { this.deviceList.innerHTML = ` <div class="device-list-empty"> <p>尚未添加任何设备</p> </div> `; return; } // 填充设备列表 devices.forEach(device => { this.addDeviceToUI(device); }); } catch (error) { console.error('加载设备列表失败:', error); this.deviceList.innerHTML = ` <div class="device-list-empty"> <p>加载设备列表失败,请刷新页面重试</p> </div> `; } } // 加载凭据列表 async loadCredentials() { try { const response = await fetch('/api/credentials'); const credentials = await response.json(); // 清空凭据列表 this.credentialList.innerHTML = ''; if (credentials.length === 0) { this.credentialList.innerHTML = ` <div class="credential-list-empty"> <p>尚未添加任何凭据</p> </div> `; return; } // 填充凭据列表 credentials.forEach(credential => { this.addCredentialToUI(credential); }); } catch (error) { console.error('加载凭据列表失败:', error); this.credentialList.innerHTML = ` <div class="credential-list-empty"> <p>加载凭据列表失败,请刷新页面重试</p> </div> `; } } // 处理添加设备 async handleAddDevice(e) { e.preventDefault(); const deviceName = document.getElementById('device-name').value; const deviceIp = document.getElementById('device-ip').value; const deviceType = document.getElementById('device-type').value; const deviceVendor = document.getElementById('device-vendor').value; const devicePlatform = document.getElementById('device-platform').value; const deviceModel = document.getElementById('device-model').value; const deviceOs = document.getElementById('device-os').value; const deviceLocation = document.getElementById('device-location').value; const deviceDescription = document.getElementById('device-description').value; const deviceTags = document.getElementById('device-tags').value.split(',').map(tag => tag.trim()).filter(tag => tag); const deviceCredential = document.getElementById('device-credential').value || null; // 验证必填字段 if (!deviceName || !deviceIp) { alert('请填写设备名称和IP地址'); return; } // 准备设备数据 const deviceData = { name: deviceName, ip_address: deviceIp, device_type: deviceType, vendor: deviceVendor, platform: devicePlatform, model: deviceModel, os_version: deviceOs, location: deviceLocation, description: deviceDescription, tags: deviceTags, credential_id: deviceCredential }; console.log('准备发送的设备数据:', deviceData); try { // 发送API请求添加设备 const response = await fetch('/api/devices', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(deviceData) }); console.log('收到响应状态:', response.status, response.statusText); // 读取响应文本 const responseText = await response.text(); console.log('响应内容:', responseText); // 处理错误情况 if (!response.ok) { let errorDetail = responseText; try { const errorJson = JSON.parse(responseText); if (errorJson.detail) { errorDetail = errorJson.detail; } } catch (e) { // 如果不是JSON格式,使用原始文本 } throw new Error(`添加设备失败 (${response.status}): ${errorDetail}`); } // 解析成功的响应 let data; try { data = JSON.parse(responseText); } catch (error) { console.error('解析响应数据失败:', error); throw new Error('解析响应数据失败'); } // 添加到UI this.addDeviceToUI(data); // 关闭对话框 this.addDeviceModal.style.display = 'none'; // 重新加载设备列表 this.loadDevices(); // 重置表单 this.addDeviceForm.reset(); } catch (error) { console.error('添加设备失败:', error); alert(`添加设备失败: ${error.message}`); } } // 处理添加凭据 async handleAddCredential(e) { e.preventDefault(); const credentialName = document.getElementById('credential-name').value; const credentialUsername = document.getElementById('credential-username').value; const credentialPassword = document.getElementById('credential-password').value; const credentialProtocol = document.getElementById('credential-protocol').value; const credentialPort = document.getElementById('credential-port').value || (credentialProtocol === 'ssh' ? '22' : '23'); const credentialEnable = document.getElementById('credential-enable').value; // 验证必填字段 if (!credentialName || !credentialUsername || !credentialPassword) { alert('请填写凭据名称、用户名和密码'); return; } // 准备凭据数据 const credentialData = { name: credentialName, username: credentialUsername, password: credentialPassword, protocol: credentialProtocol, port: parseInt(credentialPort), enable_password: credentialEnable || null }; console.log('准备发送的凭据数据:', credentialData); try { // 发送API请求添加凭据 const response = await fetch('/api/credentials', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(credentialData) }); console.log('收到响应状态:', response.status, response.statusText); // 读取响应文本 const responseText = await response.text(); console.log('响应内容:', responseText); // 处理错误情况 if (!response.ok) { let errorDetail = responseText; try { const errorJson = JSON.parse(responseText); if (errorJson.detail) { errorDetail = errorJson.detail; } } catch (e) { // 如果不是JSON格式,使用原始文本 } throw new Error(`添加凭据失败 (${response.status}): ${errorDetail}`); } // 解析成功的响应 let data; try { data = JSON.parse(responseText); } catch (error) { console.error('解析响应数据失败:', error); throw new Error('解析响应数据失败'); } // 添加到UI this.addCredentialToUI(data); // 关闭对话框 this.addCredentialModal.style.display = 'none'; // 重新加载凭据列表 this.loadCredentials(); // 重置表单 this.addCredentialForm.reset(); } catch (error) { console.error('添加凭据失败:', error); alert(`添加凭据失败: ${error.message}`); } } // 添加设备到UI addDeviceToUI(device) { const deviceEmpty = this.deviceList.querySelector('.device-list-empty'); if (deviceEmpty) { deviceEmpty.remove(); } // 检查是否已存在同ID设备条目 const existingDevice = document.getElementById(`device-${device.id}`); if (existingDevice) { existingDevice.remove(); } const deviceItem = document.createElement('div'); deviceItem.className = 'device-item'; deviceItem.id = `device-${device.id}`; // 设备状态类 const statusClasses = { 'online': 'device-status-online', 'offline': 'device-status-offline', 'unreachable': 'device-status-unreachable', 'maintenance': 'device-status-maintenance', 'unknown': 'device-status-unknown' }; // 设备类型图标 const typeIcons = { 'router': 'fas fa-route', 'switch': 'fas fa-network-wired', 'firewall': 'fas fa-shield-alt', 'load_balancer': 'fas fa-balance-scale', 'wireless_controller': 'fas fa-wifi', 'access_point': 'fas fa-broadcast-tower', 'other': 'fas fa-server' }; deviceItem.innerHTML = ` <div class="device-item-content"> <div class="device-item-title"> <span class="device-status ${statusClasses[device.status] || 'device-status-unknown'}"></span> <i class="${typeIcons[device.device_type] || 'fas fa-server'}"></i> ${device.name} </div> <div class="device-item-subtitle"> ${device.ip_address} | ${device.vendor.toUpperCase()} ${device.platform ? device.platform + ' ' : ''}${device.model} </div> <div class="device-item-details"> ${device.os_version ? `OS: ${device.os_version} | ` : ''} ${device.location ? `位置: ${device.location}` : ''} </div> </div> <div class="device-item-actions"> <button class="device-item-action connect-device" title="连接设备"> <i class="fas fa-terminal"></i> </button> <button class="device-item-action edit-device" title="编辑设备" data-id="${device.id}"> <i class="fas fa-edit"></i> </button> <button class="device-item-action delete-device" title="删除设备" data-id="${device.id}"> <i class="fas fa-trash"></i> </button> </div> `; // 连接设备按钮 deviceItem.querySelector('.connect-device').addEventListener('click', () => { // 显示连接对话框,预选此设备 const modal = document.getElementById('device-connect-modal'); modal.style.display = 'block'; // 调用终端管理器的showConnectDialog方法加载设备和凭据列表 window.terminalManager.showConnectDialog(); // 等待列表加载完成后选择当前设备 const checkDeviceSelect = setInterval(() => { const deviceSelect = document.getElementById('device-select'); if (deviceSelect.options.length > 1) { clearInterval(checkDeviceSelect); for (let i = 0; i < deviceSelect.options.length; i++) { if (deviceSelect.options[i].value === device.id) { deviceSelect.selectedIndex = i; // 触发change事件以更新终端名称 const event = new Event('change'); deviceSelect.dispatchEvent(event); break; } } } }, 100); }); // 添加设备条目到列表 this.deviceList.appendChild(deviceItem); // 绑定编辑和删除按钮事件 const editBtn = deviceItem.querySelector('.edit-device'); const deleteBtn = deviceItem.querySelector('.delete-device'); editBtn.addEventListener('click', () => { this.showEditDeviceModal(device.id); }); deleteBtn.addEventListener('click', () => { this.handleDeleteDevice(device.id); }); } // 添加凭据到UI addCredentialToUI(credential) { const credentialEmpty = this.credentialList.querySelector('.credential-list-empty'); if (credentialEmpty) { credentialEmpty.remove(); } // 检查是否已存在同ID凭据条目 const existingCredential = document.getElementById(`credential-${credential.id}`); if (existingCredential) { existingCredential.remove(); } // 协议图标 const protocolIcons = { 'ssh': 'fas fa-lock', 'telnet': 'fas fa-unlock', 'snmp': 'fas fa-chart-line', 'http': 'fas fa-globe', 'https': 'fas fa-shield-alt', 'netconf': 'fas fa-code' }; const credentialItem = document.createElement('div'); credentialItem.className = 'credential-item'; credentialItem.id = `credential-${credential.id}`; credentialItem.innerHTML = ` <div class="credential-item-content"> <div class="credential-item-title"> <i class="${protocolIcons[credential.protocol] || 'fas fa-key'}"></i> ${credential.name} </div> <div class="credential-item-subtitle"> ${credential.username}@${credential.protocol.toUpperCase()} | 端口: ${credential.port} </div> </div> <div class="credential-item-actions"> <button class="credential-item-action edit-credential" title="编辑凭据" data-id="${credential.id}"> <i class="fas fa-edit"></i> </button> <button class="credential-item-action delete-credential" title="删除凭据" data-id="${credential.id}"> <i class="fas fa-trash"></i> </button> </div> `; // 添加凭据条目到列表 this.credentialList.appendChild(credentialItem); // 绑定编辑和删除按钮事件 const editBtn = credentialItem.querySelector('.edit-credential'); const deleteBtn = credentialItem.querySelector('.delete-credential'); editBtn.addEventListener('click', () => { this.showEditCredentialModal(credential.id); }); deleteBtn.addEventListener('click', () => { this.handleDeleteCredential(credential.id); }); } // 显示修改设备对话框 async showEditDeviceModal(deviceId) { try { // 获取设备详情 const response = await fetch(`/api/devices/${deviceId}`); if (!response.ok) { throw new Error('获取设备详情失败'); } const device = await response.json(); // 设置表单值 document.getElementById('edit-device-id').value = device.id; document.getElementById('edit-device-name').value = device.name; document.getElementById('edit-device-ip').value = device.ip_address; document.getElementById('edit-device-type').value = device.device_type; document.getElementById('edit-device-vendor').value = device.vendor; document.getElementById('edit-device-platform').value = device.platform || ''; document.getElementById('edit-device-model').value = device.model || ''; document.getElementById('edit-device-os').value = device.os_version || ''; document.getElementById('edit-device-status').value = device.status; document.getElementById('edit-device-location').value = device.location || ''; document.getElementById('edit-device-description').value = device.description || ''; document.getElementById('edit-device-tags').value = (device.tags || []).join(','); // 加载并设置凭据选择器 await this.loadCredentialsForSelect('edit-device-credential', device.credential_id); // 显示对话框 this.editDeviceModal.style.display = 'block'; } catch (error) { console.error('加载设备详情失败:', error); alert('加载设备详情失败: ' + error.message); } } // 显示修改凭据对话框 async showEditCredentialModal(credentialId) { try { // 获取凭据详情 const response = await fetch(`/api/credentials/${credentialId}`); if (!response.ok) { throw new Error('获取凭据详情失败'); } const credential = await response.json(); // 设置表单值 document.getElementById('edit-credential-id').value = credential.id; document.getElementById('edit-credential-name').value = credential.name; document.getElementById('edit-credential-username').value = credential.username; document.getElementById('edit-credential-password').value = ''; // 不显示原密码 document.getElementById('edit-credential-protocol').value = credential.protocol; document.getElementById('edit-credential-port').value = credential.port; document.getElementById('edit-credential-enable').value = ''; // 不显示原Enable密码 // 显示对话框 this.editCredentialModal.style.display = 'block'; } catch (error) { console.error('加载凭据详情失败:', error); alert('加载凭据详情失败: ' + error.message); } } // 处理修改设备 async handleEditDevice(e) { e.preventDefault(); const deviceId = document.getElementById('edit-device-id').value; const deviceName = document.getElementById('edit-device-name').value; const deviceIp = document.getElementById('edit-device-ip').value; const deviceType = document.getElementById('edit-device-type').value; const deviceVendor = document.getElementById('edit-device-vendor').value; const devicePlatform = document.getElementById('edit-device-platform').value; const deviceModel = document.getElementById('edit-device-model').value; const deviceOs = document.getElementById('edit-device-os').value; const deviceStatus = document.getElementById('edit-device-status').value; const deviceLocation = document.getElementById('edit-device-location').value; const deviceDescription = document.getElementById('edit-device-description').value; const deviceTags = document.getElementById('edit-device-tags').value.split(',').map(tag => tag.trim()).filter(tag => tag); const deviceCredential = document.getElementById('edit-device-credential').value || null; // 验证必填字段 if (!deviceName || !deviceIp) { alert('请填写设备名称和IP地址'); return; } // 准备设备数据 const deviceData = { name: deviceName, ip_address: deviceIp, device_type: deviceType, vendor: deviceVendor, platform: devicePlatform, model: deviceModel, os_version: deviceOs, status: deviceStatus, location: deviceLocation, description: deviceDescription, tags: deviceTags, credential_id: deviceCredential }; try { // 发送API请求修改设备 const response = await fetch(`/api/devices/${deviceId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(deviceData) }); // 处理错误情况 if (!response.ok) { const responseText = await response.text(); let errorDetail = responseText; try { const errorJson = JSON.parse(responseText); if (errorJson.detail) { errorDetail = errorJson.detail; } } catch (e) {} throw new Error(`修改设备失败 (${response.status}): ${errorDetail}`); } // 解析成功的响应 const data = await response.json(); // 更新UI this.addDeviceToUI(data); // 关闭对话框 this.editDeviceModal.style.display = 'none'; // 提示成功 alert('设备修改成功'); } catch (error) { console.error('修改设备失败:', error); alert(`修改设备失败: ${error.message}`); } } // 处理修改凭据 async handleEditCredential(e) { e.preventDefault(); const credentialId = document.getElementById('edit-credential-id').value; const credentialName = document.getElementById('edit-credential-name').value; const credentialUsername = document.getElementById('edit-credential-username').value; const credentialPassword = document.getElementById('edit-credential-password').value; const credentialProtocol = document.getElementById('edit-credential-protocol').value; const credentialPortInput = document.getElementById('edit-credential-port'); const credentialPort = credentialPortInput.value === '' ? null : parseInt(credentialPortInput.value); const credentialEnable = document.getElementById('edit-credential-enable').value; // 验证必填字段 if (!credentialName || !credentialUsername) { alert('请填写凭据名称和用户名'); return; } // 准备凭据数据 const credentialData = { name: credentialName, username: credentialUsername, protocol: credentialProtocol, port: credentialPort }; // 只有在用户输入了新密码时才包含密码字段 if (credentialPassword) { credentialData.password = credentialPassword; } // 只有在用户输入了新Enable密码时才包含Enable密码字段 if (credentialEnable) { credentialData.enable_password = credentialEnable; } try { // 发送API请求修改凭据 const response = await fetch(`/api/credentials/${credentialId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(credentialData) }); // 处理错误情况 if (!response.ok) { const responseText = await response.text(); let errorDetail = responseText; try { const errorJson = JSON.parse(responseText); if (errorJson.detail) { errorDetail = errorJson.detail; } } catch (e) {} throw new Error(`修改凭据失败 (${response.status}): ${errorDetail}`); } // 解析成功的响应 const data = await response.json(); // 更新UI this.addCredentialToUI(data); // 关闭对话框 this.editCredentialModal.style.display = 'none'; // 提示成功 alert('凭据修改成功'); } catch (error) { console.error('修改凭据失败:', error); alert(`修改凭据失败: ${error.message}`); } } // 处理删除设备 async handleDeleteDevice(deviceId) { if (!confirm('确定要删除此设备吗?')) { return; } try { // 发送API请求删除设备 const response = await fetch(`/api/devices/${deviceId}`, { method: 'DELETE' }); if (!response.ok) { throw new Error('删除设备失败'); } // 删除成功,从UI中移除 const deviceElement = document.getElementById(`device-${deviceId}`); if (deviceElement) { deviceElement.remove(); } // 如果列表为空,显示提示 if (this.deviceList.children.length === 0) { this.deviceList.innerHTML = ` <div class="device-list-empty"> <p>尚未添加任何设备</p> </div> `; } } catch (error) { console.error('删除设备失败:', error); alert('删除设备失败: ' + error.message); } } // 处理删除凭据 async handleDeleteCredential(credentialId) { if (!confirm('确定要删除此凭据吗?')) { return; } try { // 发送API请求删除凭据 const response = await fetch(`/api/credentials/${credentialId}`, { method: 'DELETE' }); if (!response.ok) { throw new Error('删除凭据失败'); } // 删除成功,从UI中移除 const credentialElement = document.getElementById(`credential-${credentialId}`); if (credentialElement) { credentialElement.remove(); } // 如果列表为空,显示提示 if (this.credentialList.children.length === 0) { this.credentialList.innerHTML = ` <div class="credential-list-empty"> <p>尚未添加任何凭据</p> </div> `; } } catch (error) { console.error('删除凭据失败:', error); alert('删除凭据失败: ' + error.message); } } // 为选择器加载凭据列表 async loadCredentialsForSelect(selectId, selectedCredentialId = null) { try { const response = await fetch('/api/credentials'); const credentials = await response.json(); const selectElement = document.getElementById(selectId); if (!selectElement) { console.error(`未找到选择器元素: ${selectId}`); return; } // 清空选择器 selectElement.innerHTML = '<option value="">-- 无凭据 --</option>'; // 填充凭据选项 credentials.forEach(credential => { const option = document.createElement('option'); option.value = credential.id; option.textContent = `${credential.name} (${credential.username}@${credential.protocol.toUpperCase()})`; // 设置选中状态 if (selectedCredentialId && credential.id === selectedCredentialId) { option.selected = true; } selectElement.appendChild(option); }); } catch (error) { console.error('加载凭据选择器失败:', error); } } } // ================================ // 全局主题管理系统 // ================================ // 初始化全局主题系统 function initializeGlobalTheme() { console.log('初始化全局主题系统...'); // 获取主题切换器 const loginThemeToggle = document.getElementById('login-theme-toggle'); const dashboardThemeToggle = document.getElementById('theme-toggle'); // 获取当前主题 const currentTheme = localStorage.getItem('theme') || 'light'; // 应用主题 applyTheme(currentTheme); // 设置切换器状态 if (loginThemeToggle) { loginThemeToggle.checked = currentTheme === 'dark'; // 绑定登录页面主题切换事件 loginThemeToggle.addEventListener('change', function() { const newTheme = this.checked ? 'dark' : 'light'; changeTheme(newTheme); }); } if (dashboardThemeToggle) { dashboardThemeToggle.checked = currentTheme === 'dark'; // 绑定仪表盘主题切换事件 dashboardThemeToggle.addEventListener('change', function() { const newTheme = this.checked ? 'dark' : 'light'; changeTheme(newTheme); }); } console.log(`主题系统初始化完成,当前主题:${currentTheme}`); } // 应用主题 function applyTheme(theme) { document.documentElement.setAttribute('data-theme', theme); // 根据主题切换logo图片 const loginLogo = document.getElementById('login-logo'); const headerLogo = document.getElementById('header-logo'); if (theme === 'dark') { // 暗色主题使用logod.png if (loginLogo) { loginLogo.src = loginLogo.src.replace('logo.png', 'logod.png'); } if (headerLogo) { headerLogo.src = headerLogo.src.replace('logo.png', 'logod.png'); } } else { // 明亮主题使用logo.png if (loginLogo) { loginLogo.src = loginLogo.src.replace('logod.png', 'logo.png'); } if (headerLogo) { headerLogo.src = headerLogo.src.replace('logod.png', 'logo.png'); } } // 同步所有主题切换器状态 const loginThemeToggle = document.getElementById('login-theme-toggle'); const dashboardThemeToggle = document.getElementById('theme-toggle'); if (loginThemeToggle) { loginThemeToggle.checked = theme === 'dark'; } if (dashboardThemeToggle) { dashboardThemeToggle.checked = theme === 'dark'; } } // 切换主题 function changeTheme(newTheme) { // 应用新主题 applyTheme(newTheme); // 保存到本地存储 localStorage.setItem('theme', newTheme); // 添加活动记录(如果仪表盘已初始化) if (typeof addActivity === 'function') { addActivity(`切换到${newTheme === 'dark' ? '暗色' : '明亮'}主题`, 'info'); } console.log(`主题已切换到:${newTheme}`); } // 页面路由管理 document.addEventListener('DOMContentLoaded', function() { // 初始化主题系统 initializeGlobalTheme(); // 页面加载完成后初始化 console.log('初始化会话管理器...'); if (!window.sessionManager) { window.sessionManager = new SessionManager(); console.log('会话管理器初始化完成'); } console.log('初始化会话管理UI...'); if (!window.sessionManagerUI) { window.sessionManagerUI = new SessionManagerUI(); console.log('会话管理UI初始化完成'); } console.log('初始化终端管理器...'); if (!window.terminalManager) { window.terminalManager = new TerminalManager(); console.log('终端管理器初始化完成'); } console.log('初始化设备管理UI...'); if (!window.deviceManagerUI) { window.deviceManagerUI = new DeviceManagerUI(); console.log('设备管理UI初始化完成'); } // 预加载设备和凭据列表到缓存 function preloadDevicesAndCredentials() { console.log('预加载设备和凭据列表...'); // 加载设备列表 fetch('/api/devices') .then(response => response.ok ? response.json() : []) .then(devices => { console.log('预加载设备列表成功:', devices); window.cachedDevices = devices; }) .catch(error => { console.error('预加载设备列表失败:', error); }); // 加载凭据列表 fetch('/api/credentials') .then(response => response.ok ? response.json() : []) .then(credentials => { console.log('预加载凭据列表成功:', credentials); window.cachedCredentials = credentials; }) .catch(error => { console.error('预加载凭据列表失败:', error); }); } // 页面加载时预加载数据 preloadDevicesAndCredentials(); // 添加备用的拓扑发现按钮事件监听器 function ensureTopologyInitialized() { if (!window.topologyVisualization && window.TopologyVisualization) { console.log('备用初始化:创建拓扑可视化实例'); try { window.topologyVisualization = new window.TopologyVisualization(); console.log('备用拓扑可视化模块初始化成功'); } catch (error) { console.error('备用拓扑可视化模块初始化失败:', error); } } } // 为拓扑发现按钮添加备用事件监听器 const discoverTopologyBtn = document.getElementById('discover-topology-btn'); if (discoverTopologyBtn) { discoverTopologyBtn.addEventListener('click', function(e) { console.log('发现拓扑按钮被点击'); // 确保拓扑模块已初始化 ensureTopologyInitialized(); // 如果拓扑模块仍然没有初始化,显示错误提示 if (!window.topologyVisualization) { console.error('拓扑可视化模块未能初始化'); alert('拓扑可视化模块加载失败,请刷新页面重试'); e.preventDefault(); e.stopPropagation(); return false; } console.log('拓扑模块已准备就绪'); }); } // 按钮点击事件 - 从登录页面到主页(默认显示仪表盘) document.getElementById('start-btn').addEventListener('click', function() { document.getElementById('login-page').style.display = 'none'; document.getElementById('home-page').style.display = 'block'; // 默认显示仪表盘页面 document.querySelectorAll('.page-content').forEach(content => { content.style.display = 'none'; }); document.getElementById('dashboard-content').style.display = 'block'; // 初始化仪表盘 if (typeof initializeDashboard === 'function') { initializeDashboard(); } window.history.pushState({page: 'dashboard'}, 'Dashboard', '/dashboard'); }); // 导航链接点击处理 const navLinks = document.querySelectorAll('.nav-link'); const pageContents = document.querySelectorAll('.page-content'); navLinks.forEach(link => { link.addEventListener('click', function(e) { e.preventDefault(); // 移除所有活动状态 navLinks.forEach(l => l.classList.remove('active')); // 添加当前活动状态 this.classList.add('active'); // 隐藏所有内容 pageContents.forEach(content => { content.style.display = 'none'; }); // 显示选中的内容 const pageId = this.getAttribute('data-page'); document.getElementById(`${pageId}-content`).style.display = 'block'; // 更新URL window.history.pushState({page: pageId}, pageId, `#${pageId}`); // 特殊页面处理 if (pageId === 'terminal') { // 如果是终端页面,重新适应终端大小 const activeTerminal = window.terminalManager.getTerminalById(window.terminalManager.activeTerminalId); if (activeTerminal && activeTerminal.fitAddon) { setTimeout(() => activeTerminal.fitAddon.fit(), 10); } } else if (pageId === 'topology') { // 如果是拓扑页面,初始化拓扑可视化 console.log('切换到拓扑页面,初始化拓扑可视化模块...'); if (!window.topologyVisualization) { try { window.topologyVisualization = new TopologyVisualization(); console.log('拓扑可视化模块初始化成功'); } catch (error) { console.error('拓扑可视化模块初始化失败:', error); } } else { console.log('拓扑可视化模块已存在,跳过初始化'); // 如果已经初始化,可以触发窗口大小调整 if (window.topologyVisualization.handleResize) { setTimeout(() => window.topologyVisualization.handleResize(), 10); } } } }); }); // 处理浏览器的后退/前进按钮 window.addEventListener('popstate', function(event) { if (event.state && event.state.page) { if (event.state.page === 'home') { document.getElementById('login-page').style.display = 'none'; document.getElementById('home-page').style.display = 'block'; } else if (event.state.page === 'login') { document.getElementById('login-page').style.display = 'block'; document.getElementById('home-page').style.display = 'none'; } else { // 处理内部页面导航 navLinks.forEach(link => { if (link.getAttribute('data-page') === event.state.page) { link.click(); } }); } } }); // 初始化页面状态 const initialPath = window.location.pathname; if (initialPath === '/home') { document.getElementById('login-page').style.display = 'none'; document.getElementById('home-page').style.display = 'block'; window.history.replaceState({page: 'home'}, 'Home', '/home'); } else { window.history.replaceState({page: 'login'}, 'Login', '/'); } // 处理锚点导航 const hash = window.location.hash.substring(1); if (hash) { const targetLink = document.querySelector(`.nav-link[data-page="${hash}"]`); if (targetLink) { targetLink.click(); } } // 设备连接表单处理 document.getElementById('connect-device-form').addEventListener('submit', function(e) { e.preventDefault(); const deviceId = document.getElementById('device-select').value; const credentialId = document.getElementById('credential-select').value; let terminalName = document.getElementById('terminal-name').value; if (!deviceId || !credentialId) { alert('请选择设备和凭据'); return; } // 如果没有提供名称,使用默认格式 if (!terminalName) { const deviceOption = document.getElementById('device-select').selectedOptions[0]; terminalName = deviceOption.textContent.split(' ')[0] + '-会话'; } // 创建新的终端并连接设备 const termInfo = window.terminalManager.createTerminal(terminalName, deviceId, credentialId); // 创建会话记录 window.sessionManager.connectToDevice(deviceId, credentialId, terminalName); // 关闭对话框 document.getElementById('device-connect-modal').style.display = 'none'; // 清空表单 this.reset(); // 自动跳转到终端页面 const terminalLink = document.querySelector('.nav-link[data-page="terminal"]'); if (terminalLink) { terminalLink.click(); } }); // 通用模态框关闭处理 document.querySelectorAll('.close-modal, .close-btn').forEach(btn => { btn.addEventListener('click', function() { const modal = this.closest('.modal'); if (modal) { modal.style.display = 'none'; } }); }); // 点击对话框外部关闭 window.addEventListener('click', function(e) { document.querySelectorAll('.modal').forEach(modal => { if (e.target === modal) { modal.style.display = 'none'; } }); }); }); // ================================ // 仪表盘相关功能 // ================================ // 初始化仪表盘 function initializeDashboard() { console.log('初始化仪表盘...'); // 初始化主题切换器 initializeThemeSwitcher(); // 更新当前时间 updateCurrentTime(); setInterval(updateCurrentTime, 1000); // 加载仪表盘数据 loadDashboardData(); // 绑定仪表盘事件 bindDashboardEvents(); // 添加活动记录 addActivity('系统启动成功', 'success'); } // 主题切换器初始化 function initializeThemeSwitcher() { // 主题切换器现在由全局主题系统管理 // 这个函数保留用于兼容性,实际功能在initializeGlobalTheme中实现 console.log('仪表盘主题切换器由全局主题系统管理'); } // 更新当前时间 function updateCurrentTime() { const now = new Date(); const timeString = now.toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' }); const currentTimeElement = document.getElementById('current-time'); if (currentTimeElement) { currentTimeElement.textContent = timeString; } } // 加载仪表盘数据 async function loadDashboardData() { try { // 并行加载各种数据 const [devices, topology, sessions] = await Promise.all([ fetch('/api/devices').then(r => r.ok ? r.json() : []).catch(() => []), fetch('/api/topology').then(r => r.ok ? r.json() : {}).catch(() => ({})), // 假设有会话API,暂时返回空数组 Promise.resolve([]) ]); // 更新统计数据 updateDashboardStats(devices, topology, sessions); // 更新设备概览 updateDevicesOverview(devices); // 检查网络状态 checkNetworkStatus(); } catch (error) { console.error('加载仪表盘数据失败:', error); addActivity('加载仪表盘数据失败', 'error'); } } // 更新仪表盘统计数据 function updateDashboardStats(devices, topology, sessions) { // 设备数量 const devicesCount = devices.length; document.getElementById('dashboard-devices-count').textContent = devicesCount; document.getElementById('dashboard-devices-change').textContent = `+${devicesCount}`; // 活跃连接数(暂时设为0) document.getElementById('dashboard-connections-count').textContent = '0'; document.getElementById('dashboard-connections-change').textContent = '0'; // 拓扑节点数 const topologyNodes = topology.nodes ? Object.keys(topology.nodes).length : 0; document.getElementById('dashboard-topology-nodes').textContent = topologyNodes; document.getElementById('dashboard-topology-change').textContent = `+${topologyNodes}`; // 终端会话数 const sessionsCount = sessions.length; document.getElementById('dashboard-sessions-count').textContent = sessionsCount; document.getElementById('dashboard-sessions-change').textContent = `+${sessionsCount}`; } // 更新设备概览 function updateDevicesOverview(devices) { const devicesOverview = document.getElementById('devices-overview'); if (!devices || devices.length === 0) { devicesOverview.innerHTML = ` <div class="no-devices"> <i class="fas fa-server"></i> <p>暂无设备</p> <button class="btn btn-sm" onclick="goToDevices()"> <i class="fas fa-plus"></i> 添加设备 </button> </div> `; return; } // 显示前5个设备 const displayDevices = devices.slice(0, 5); const deviceItems = displayDevices.map(device => ` <div class="device-overview-item"> <div class="device-overview-icon"> <i class="fas fa-server"></i> </div> <div class="device-overview-info"> <div class="device-overview-name">${device.name}</div> <div class="device-overview-details">${device.ip_address} • ${device.vendor}</div> </div> <div class="device-overview-status ${device.status || 'unknown'}"></div> </div> `).join(''); devicesOverview.innerHTML = deviceItems; if (devices.length > 5) { devicesOverview.innerHTML += ` <div class="device-overview-item" onclick="goToDevices()" style="cursor: pointer;"> <div class="device-overview-icon"> <i class="fas fa-ellipsis-h"></i> </div> <div class="device-overview-info"> <div class="device-overview-name">查看更多</div> <div class="device-overview-details">还有 ${devices.length - 5} 个设备</div> </div> <div class="device-overview-status more-indicator"></div> </div> `; } } // 检查网络状态 async function checkNetworkStatus() { try { const response = await fetch('/api/devices', { method: 'HEAD' }); const statusIcon = document.getElementById('network-status'); const statusText = document.getElementById('network-status-text'); if (response.ok) { statusIcon.className = 'status-icon online'; statusIcon.innerHTML = '<i class="fas fa-wifi"></i>'; statusText.textContent = '连接正常'; } else { statusIcon.className = 'status-icon warning'; statusIcon.innerHTML = '<i class="fas fa-exclamation-triangle"></i>'; statusText.textContent = '连接异常'; } } catch (error) { const statusIcon = document.getElementById('network-status'); const statusText = document.getElementById('network-status-text'); statusIcon.className = 'status-icon offline'; statusIcon.innerHTML = '<i class="fas fa-times-circle"></i>'; statusText.textContent = '连接失败'; } } // 绑定仪表盘事件 function bindDashboardEvents() { // 刷新仪表盘按钮 const refreshBtn = document.getElementById('refresh-dashboard'); if (refreshBtn) { refreshBtn.addEventListener('click', function() { loadDashboardData(); addActivity('刷新仪表盘数据', 'info'); }); } } // 添加活动记录 function addActivity(text, type = 'info') { const activitiesList = document.getElementById('recent-activities'); if (!activitiesList) return; const activityItem = document.createElement('div'); activityItem.className = 'activity-item'; activityItem.innerHTML = ` <div class="activity-icon ${type}"> <i class="fas fa-${getActivityIcon(type)}"></i> </div> <div class="activity-content"> <div class="activity-text">${text}</div> <div class="activity-time">刚刚</div> </div> `; // 插入到列表顶部 activitiesList.insertBefore(activityItem, activitiesList.firstChild); // 限制活动数量(最多保留10条) const activities = activitiesList.querySelectorAll('.activity-item'); if (activities.length > 10) { activitiesList.removeChild(activities[activities.length - 1]); } } // 获取活动图标 function getActivityIcon(type) { const icons = { info: 'info-circle', success: 'check-circle', warning: 'exclamation-triangle', error: 'times-circle' }; return icons[type] || 'info-circle'; } // 仪表盘快速操作功能 function goToDevices() { const devicesLink = document.querySelector('.nav-link[data-page="devices"]'); if (devicesLink) { devicesLink.click(); addActivity('跳转到设备管理页面', 'info'); } } function openTerminal() { const terminalLink = document.querySelector('.nav-link[data-page="terminal"]'); if (terminalLink) { terminalLink.click(); addActivity('打开终端页面', 'info'); } } function discoverTopology() { const topologyLink = document.querySelector('.nav-link[data-page="topology"]'); if (topologyLink) { topologyLink.click(); // 延迟触发拓扑发现 setTimeout(() => { const discoverBtn = document.getElementById('discover-topology-btn'); if (discoverBtn) { discoverBtn.click(); addActivity('启动拓扑发现', 'info'); } }, 500); } } function scanNetwork() { // 暂时跳转到设备页面 goToDevices(); addActivity('网络扫描功能开发中', 'warning'); } function refreshSystemStatus() { checkNetworkStatus(); addActivity('刷新系统状态', 'info'); } function refreshDevicesOverview() { loadDashboardData(); addActivity('刷新设备概览', 'info'); } function clearActivities() { const activitiesList = document.getElementById('recent-activities'); if (activitiesList) { activitiesList.innerHTML = ` <div class="activity-item"> <div class="activity-icon info"> <i class="fas fa-info-circle"></i> </div> <div class="activity-content"> <div class="activity-text">活动记录已清空</div> <div class="activity-time">刚刚</div> </div> </div> `; } } // 暴露initializeDashboard函数到全局作用域 window.initializeDashboard = initializeDashboard; </script> </body> </html>

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/NorthLaneMS/NetBrain_MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server