index.html•155 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">×</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">×</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">×</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">×</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">×</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">×</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">×</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">×</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>