A:重新调整虚拟列表组件
This commit is contained in:
1 parent
7716850e16
commit
e737713ef5
23 files changed
+3660
-1325
No files matched your search
@@ -0,0 +1,331 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>动态折线图 - 跳动小球轨迹</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background: linear-gradient(135deg, #1e3c72, #2a5298);
|
||||
font-family: 'Arial', sans-serif;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 20px;
|
||||
padding: 30px;
|
||||
backdrop-filter: blur(10px);
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
font-size: 2.5em;
|
||||
background: linear-gradient(45deg, #ff6b6b, #4ecdc4);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
#chartContainer {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 500px;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border-radius: 15px;
|
||||
overflow: hidden;
|
||||
border: 2px solid rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
#chart {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.ball {
|
||||
position: absolute;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
background: radial-gradient(circle at 30% 30%, #ff6b6b, #ff4757);
|
||||
box-shadow: 0 0 20px rgba(255, 107, 107, 0.8), 0 0 40px rgba(255, 107, 107, 0.4);
|
||||
z-index: 10;
|
||||
transform: translate(-50%, -50%);
|
||||
animation: bounce 0.6s ease-in-out infinite alternate;
|
||||
}
|
||||
|
||||
@keyframes bounce {
|
||||
0% { transform: translate(-50%, -50%) scale(1); }
|
||||
100% { transform: translate(-50%, -50%) scale(1.2); }
|
||||
}
|
||||
|
||||
.value-display {
|
||||
position: absolute;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
color: white;
|
||||
padding: 8px 12px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
z-index: 11;
|
||||
transform: translate(-50%, -150%);
|
||||
animation: pulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 0.8; }
|
||||
50% { opacity: 1; }
|
||||
}
|
||||
|
||||
.controls {
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
button {
|
||||
background: linear-gradient(45deg, #ff6b6b, #4ecdc4);
|
||||
border: none;
|
||||
color: white;
|
||||
padding: 12px 24px;
|
||||
margin: 0 10px;
|
||||
border-radius: 25px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
button:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
button:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.info {
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
font-size: 18px;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>🎯 动态数据轨迹图</h1>
|
||||
|
||||
<div id="chartContainer">
|
||||
<svg id="chart" viewBox="0 0 1000 400">
|
||||
<!-- 网格线 -->
|
||||
<defs>
|
||||
<pattern id="grid" width="50" height="40" patternUnits="userSpaceOnUse">
|
||||
<path d="M 50 0 L 0 0 0 40" fill="none" stroke="rgba(255,255,255,0.1)" stroke-width="1"/>
|
||||
</pattern>
|
||||
<linearGradient id="lineGradient" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" style="stop-color:#4ecdc4;stop-opacity:0.3" />
|
||||
<stop offset="100%" style="stop-color:#ff6b6b;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<rect width="100%" height="100%" fill="url(#grid)" />
|
||||
|
||||
<!-- 主要轨迹线 -->
|
||||
<path id="mainPath" fill="none" stroke="url(#lineGradient)" stroke-width="3" opacity="0.8"/>
|
||||
|
||||
<!-- 辅助点 -->
|
||||
<g id="points"></g>
|
||||
</svg>
|
||||
|
||||
<!-- 跳动的小球 -->
|
||||
<div class="ball" id="ball"></div>
|
||||
<div class="value-display" id="valueDisplay">0</div>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<button onclick="startAnimation()">🚀 开始动画</button>
|
||||
<button onclick="pauseAnimation()">⏸️ 暂停</button>
|
||||
<button onclick="resetAnimation()">🔄 重置</button>
|
||||
<button onclick="changeSpeed()">⚡ 切换速度</button>
|
||||
</div>
|
||||
|
||||
<div class="info">
|
||||
<p>💡 小球会沿着数据轨迹移动,实时显示当前数值</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let animationRunning = false;
|
||||
let animationSpeed = 50; // 毫秒
|
||||
let currentIndex = 0;
|
||||
let dataPoints = [];
|
||||
let pathData = '';
|
||||
let animationId;
|
||||
|
||||
// 生成模拟数据
|
||||
function generateData() {
|
||||
dataPoints = [];
|
||||
const baseValue = 200;
|
||||
let currentValue = baseValue;
|
||||
|
||||
for (let i = 0; i < 200; i++) {
|
||||
// 生成随机波动数据
|
||||
const change = (Math.random() - 0.5) * 40;
|
||||
currentValue += change;
|
||||
|
||||
// 限制范围
|
||||
currentValue = Math.max(50, Math.min(350, currentValue));
|
||||
|
||||
dataPoints.push({
|
||||
x: i * 5, // x轴间距
|
||||
y: 400 - currentValue, // y轴翻转
|
||||
value: Math.round(currentValue)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 更新SVG路径
|
||||
function updatePath() {
|
||||
if (currentIndex < 2) return;
|
||||
|
||||
pathData = `M ${dataPoints[0].x} ${dataPoints[0].y}`;
|
||||
for (let i = 1; i <= currentIndex; i++) {
|
||||
pathData += ` L ${dataPoints[i].x} ${dataPoints[i].y}`;
|
||||
}
|
||||
|
||||
document.getElementById('mainPath').setAttribute('d', pathData);
|
||||
|
||||
// 添加轨迹点
|
||||
const pointsGroup = document.getElementById('points');
|
||||
if (currentIndex < dataPoints.length) {
|
||||
const point = dataPoints[currentIndex];
|
||||
const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
||||
circle.setAttribute('cx', point.x);
|
||||
circle.setAttribute('cy', point.y);
|
||||
circle.setAttribute('r', '2');
|
||||
circle.setAttribute('fill', 'rgba(255, 255, 255, 0.6)');
|
||||
pointsGroup.appendChild(circle);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新小球位置
|
||||
function updateBall() {
|
||||
if (currentIndex >= dataPoints.length) {
|
||||
pauseAnimation();
|
||||
return;
|
||||
}
|
||||
|
||||
const point = dataPoints[currentIndex];
|
||||
const ball = document.getElementById('ball');
|
||||
const valueDisplay = document.getElementById('valueDisplay');
|
||||
|
||||
// 计算在容器中的实际位置
|
||||
const container = document.getElementById('chartContainer');
|
||||
const rect = container.getBoundingClientRect();
|
||||
const svgWidth = 1000;
|
||||
const svgHeight = 400;
|
||||
|
||||
const actualX = (point.x / svgWidth) * rect.width;
|
||||
const actualY = (point.y / svgHeight) * rect.height;
|
||||
|
||||
ball.style.left = actualX + 'px';
|
||||
ball.style.top = actualY + 'px';
|
||||
|
||||
valueDisplay.style.left = actualX + 'px';
|
||||
valueDisplay.style.top = actualY + 'px';
|
||||
valueDisplay.textContent = point.value;
|
||||
|
||||
// 自动滚动视图保持小球在视线内
|
||||
const scrollThreshold = rect.width * 0.8;
|
||||
if (actualX > scrollThreshold) {
|
||||
const svg = document.getElementById('chart');
|
||||
const newViewBox = `${point.x - 800} 0 1000 400`;
|
||||
svg.setAttribute('viewBox', newViewBox);
|
||||
}
|
||||
}
|
||||
|
||||
// 动画循环
|
||||
function animate() {
|
||||
if (!animationRunning) return;
|
||||
|
||||
updatePath();
|
||||
updateBall();
|
||||
currentIndex++;
|
||||
|
||||
if (currentIndex < dataPoints.length) {
|
||||
animationId = setTimeout(animate, animationSpeed);
|
||||
} else {
|
||||
pauseAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
// 开始动画
|
||||
function startAnimation() {
|
||||
if (dataPoints.length === 0) {
|
||||
generateData();
|
||||
}
|
||||
|
||||
animationRunning = true;
|
||||
animate();
|
||||
}
|
||||
|
||||
// 暂停动画
|
||||
function pauseAnimation() {
|
||||
animationRunning = false;
|
||||
if (animationId) {
|
||||
clearTimeout(animationId);
|
||||
}
|
||||
}
|
||||
|
||||
// 重置动画
|
||||
function resetAnimation() {
|
||||
pauseAnimation();
|
||||
currentIndex = 0;
|
||||
document.getElementById('mainPath').setAttribute('d', '');
|
||||
document.getElementById('points').innerHTML = '';
|
||||
document.getElementById('chart').setAttribute('viewBox', '0 0 1000 400');
|
||||
|
||||
const ball = document.getElementById('ball');
|
||||
const valueDisplay = document.getElementById('valueDisplay');
|
||||
ball.style.left = '0px';
|
||||
ball.style.top = '200px';
|
||||
valueDisplay.style.left = '0px';
|
||||
valueDisplay.style.top = '200px';
|
||||
valueDisplay.textContent = '0';
|
||||
|
||||
generateData();
|
||||
}
|
||||
|
||||
// 切换速度
|
||||
function changeSpeed() {
|
||||
const speeds = [100, 50, 25, 10];
|
||||
const currentSpeedIndex = speeds.indexOf(animationSpeed);
|
||||
const nextSpeedIndex = (currentSpeedIndex + 1) % speeds.length;
|
||||
animationSpeed = speeds[nextSpeedIndex];
|
||||
|
||||
const speedNames = ['慢速', '正常', '快速', '极速'];
|
||||
alert(`切换到 ${speedNames[nextSpeedIndex]} 模式`);
|
||||
}
|
||||
|
||||
// 初始化
|
||||
window.addEventListener('load', function() {
|
||||
generateData();
|
||||
|
||||
// 处理窗口大小变化
|
||||
window.addEventListener('resize', function() {
|
||||
if (currentIndex > 0) {
|
||||
updateBall();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,372 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>动态折线图 - 跳动小球轨迹</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
font-family: 'Arial', sans-serif;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.container {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border-radius: 20px;
|
||||
padding: 30px;
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
|
||||
backdrop-filter: blur(10px);
|
||||
max-width: 1000px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
color: #333;
|
||||
margin-bottom: 30px;
|
||||
font-size: 2.5em;
|
||||
background: linear-gradient(45deg, #667eea, #764ba2);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 400px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 15px;
|
||||
overflow: hidden;
|
||||
box-shadow: inset 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
canvas {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 15px;
|
||||
margin-top: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 12px 24px;
|
||||
border: none;
|
||||
border-radius: 25px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.start-btn {
|
||||
background: linear-gradient(45deg, #4CAF50, #45a049);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.start-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(76, 175, 80, 0.3);
|
||||
}
|
||||
|
||||
.stop-btn {
|
||||
background: linear-gradient(45deg, #f44336, #da190b);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.stop-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(244, 67, 54, 0.3);
|
||||
}
|
||||
|
||||
.reset-btn {
|
||||
background: linear-gradient(45deg, #2196F3, #0b7dda);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.reset-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(33, 150, 243, 0.3);
|
||||
}
|
||||
|
||||
.info {
|
||||
margin-top: 20px;
|
||||
padding: 15px;
|
||||
background: rgba(103, 126, 234, 0.1);
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.current-value {
|
||||
font-size: 1.2em;
|
||||
font-weight: bold;
|
||||
color: #667eea;
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>🎯 动态折线图</h1>
|
||||
|
||||
<div class="chart-container">
|
||||
<canvas id="chartCanvas"></canvas>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<button class="start-btn" onclick="startAnimation()">开始动画</button>
|
||||
<button class="stop-btn" onclick="stopAnimation()">停止动画</button>
|
||||
<button class="reset-btn" onclick="resetChart()">重置图表</button>
|
||||
</div>
|
||||
|
||||
<div class="info">
|
||||
<div>小球位置固定在画布右侧,数据值实时更新</div>
|
||||
<div class="current-value" id="currentValue">当前值: 0</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const canvas = document.getElementById('chartCanvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
const currentValueEl = document.getElementById('currentValue');
|
||||
|
||||
// 设置canvas实际尺寸
|
||||
function resizeCanvas() {
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
canvas.width = rect.width * window.devicePixelRatio;
|
||||
canvas.height = rect.height * window.devicePixelRatio;
|
||||
ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
|
||||
}
|
||||
|
||||
// 初始化
|
||||
resizeCanvas();
|
||||
window.addEventListener('resize', resizeCanvas);
|
||||
|
||||
// 图表配置
|
||||
const config = {
|
||||
ballRadius: 8,
|
||||
ballPosition: 0.85, // 小球在画布右侧85%的位置
|
||||
trailWidth: 3,
|
||||
gridColor: '#e0e0e0',
|
||||
ballColor: '#ff4757',
|
||||
trailColor: '#667eea',
|
||||
ballGlowColor: 'rgba(255, 71, 87, 0.3)',
|
||||
animationSpeed: 50, // ms
|
||||
dataRange: { min: 10, max: 90 }, // 数据范围百分比
|
||||
scrollSpeed: 2 // 画布滚动速度
|
||||
};
|
||||
|
||||
// 数据存储
|
||||
let dataPoints = [];
|
||||
let animationId = null;
|
||||
let isRunning = false;
|
||||
let time = 0;
|
||||
let currentValue = 50;
|
||||
|
||||
// 生成模拟数据
|
||||
function generateNextValue() {
|
||||
// 使用正弦波加随机波动生成更自然的数据
|
||||
const baseValue = 50 + Math.sin(time * 0.02) * 20;
|
||||
const randomFluctuation = (Math.random() - 0.5) * 10;
|
||||
currentValue = Math.max(config.dataRange.min,
|
||||
Math.min(config.dataRange.max, baseValue + randomFluctuation));
|
||||
time++;
|
||||
return currentValue;
|
||||
}
|
||||
|
||||
// 绘制网格
|
||||
function drawGrid() {
|
||||
const width = canvas.width / window.devicePixelRatio;
|
||||
const height = canvas.height / window.devicePixelRatio;
|
||||
|
||||
ctx.strokeStyle = config.gridColor;
|
||||
ctx.lineWidth = 1;
|
||||
ctx.setLineDash([5, 5]);
|
||||
|
||||
// 水平网格线
|
||||
for (let i = 0; i <= 10; i++) {
|
||||
const y = (height / 10) * i;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, y);
|
||||
ctx.lineTo(width, y);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// 垂直网格线
|
||||
for (let i = 0; i <= 20; i++) {
|
||||
const x = (width / 20) * i;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x, 0);
|
||||
ctx.lineTo(x, height);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
ctx.setLineDash([]);
|
||||
}
|
||||
|
||||
// 绘制轨迹线
|
||||
function drawTrail() {
|
||||
if (dataPoints.length < 2) return;
|
||||
|
||||
const width = canvas.width / window.devicePixelRatio;
|
||||
const height = canvas.height / window.devicePixelRatio;
|
||||
|
||||
ctx.strokeStyle = config.trailColor;
|
||||
ctx.lineWidth = config.trailWidth;
|
||||
ctx.lineCap = 'round';
|
||||
ctx.lineJoin = 'round';
|
||||
|
||||
// 添加阴影效果
|
||||
ctx.shadowColor = 'rgba(102, 126, 234, 0.3)';
|
||||
ctx.shadowBlur = 8;
|
||||
ctx.shadowOffsetY = 2;
|
||||
|
||||
ctx.beginPath();
|
||||
for (let i = 0; i < dataPoints.length; i++) {
|
||||
const point = dataPoints[i];
|
||||
const x = width * config.ballPosition - (dataPoints.length - 1 - i) * config.scrollSpeed;
|
||||
const y = height - (point / 100) * height;
|
||||
|
||||
if (x < 0) continue; // 不绘制超出左边界的点
|
||||
|
||||
if (i === 0) {
|
||||
ctx.moveTo(x, y);
|
||||
} else {
|
||||
ctx.lineTo(x, y);
|
||||
}
|
||||
}
|
||||
ctx.stroke();
|
||||
|
||||
// 重置阴影
|
||||
ctx.shadowColor = 'transparent';
|
||||
ctx.shadowBlur = 0;
|
||||
ctx.shadowOffsetY = 0;
|
||||
}
|
||||
|
||||
// 绘制跳动的小球
|
||||
function drawBall() {
|
||||
const width = canvas.width / window.devicePixelRatio;
|
||||
const height = canvas.height / window.devicePixelRatio;
|
||||
|
||||
const ballX = width * config.ballPosition;
|
||||
const ballY = height - (currentValue / 100) * height;
|
||||
|
||||
// 小球跳动效果
|
||||
const bounce = Math.sin(time * 0.3) * 2;
|
||||
const finalBallY = ballY + bounce;
|
||||
|
||||
// 绘制小球光晕
|
||||
const gradient = ctx.createRadialGradient(ballX, finalBallY, 0, ballX, finalBallY, config.ballRadius * 3);
|
||||
gradient.addColorStop(0, config.ballGlowColor);
|
||||
gradient.addColorStop(1, 'transparent');
|
||||
|
||||
ctx.fillStyle = gradient;
|
||||
ctx.beginPath();
|
||||
ctx.arc(ballX, finalBallY, config.ballRadius * 3, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
// 绘制小球主体
|
||||
ctx.fillStyle = config.ballColor;
|
||||
ctx.beginPath();
|
||||
ctx.arc(ballX, finalBallY, config.ballRadius, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
// 小球高光
|
||||
ctx.fillStyle = 'rgba(255, 255, 255, 0.6)';
|
||||
ctx.beginPath();
|
||||
ctx.arc(ballX - 2, finalBallY - 2, config.ballRadius * 0.4, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
// 主渲染函数
|
||||
function render() {
|
||||
const width = canvas.width / window.devicePixelRatio;
|
||||
const height = canvas.height / window.devicePixelRatio;
|
||||
|
||||
// 清空画布
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
|
||||
// 绘制背景渐变
|
||||
const bgGradient = ctx.createLinearGradient(0, 0, 0, height);
|
||||
bgGradient.addColorStop(0, '#f8f9fa');
|
||||
bgGradient.addColorStop(1, '#e9ecef');
|
||||
ctx.fillStyle = bgGradient;
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
|
||||
// 绘制网格
|
||||
drawGrid();
|
||||
|
||||
// 绘制轨迹
|
||||
drawTrail();
|
||||
|
||||
// 绘制小球
|
||||
drawBall();
|
||||
|
||||
// 更新当前值显示
|
||||
currentValueEl.textContent = `当前值: ${currentValue.toFixed(1)}`;
|
||||
}
|
||||
|
||||
// 动画循环
|
||||
function animate() {
|
||||
if (!isRunning) return;
|
||||
|
||||
// 生成新数据点
|
||||
const newValue = generateNextValue();
|
||||
dataPoints.push(newValue);
|
||||
|
||||
// 限制数据点数量,移除画布外的点
|
||||
const maxPoints = Math.ceil(canvas.width / window.devicePixelRatio / config.scrollSpeed) + 10;
|
||||
if (dataPoints.length > maxPoints) {
|
||||
dataPoints.shift();
|
||||
}
|
||||
|
||||
render();
|
||||
|
||||
animationId = setTimeout(animate, config.animationSpeed);
|
||||
}
|
||||
|
||||
// 控制函数
|
||||
function startAnimation() {
|
||||
if (isRunning) return;
|
||||
isRunning = true;
|
||||
animate();
|
||||
}
|
||||
|
||||
function stopAnimation() {
|
||||
isRunning = false;
|
||||
if (animationId) {
|
||||
clearTimeout(animationId);
|
||||
animationId = null;
|
||||
}
|
||||
}
|
||||
|
||||
function resetChart() {
|
||||
stopAnimation();
|
||||
dataPoints = [];
|
||||
time = 0;
|
||||
currentValue = 50;
|
||||
render();
|
||||
}
|
||||
|
||||
// 初始渲染
|
||||
render();
|
||||
|
||||
// 自动开始演示
|
||||
setTimeout(startAnimation, 1000);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,626 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>网速记录仪</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background: #0a0a0a;
|
||||
color: #fff;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 90%;
|
||||
max-width: 1200px;
|
||||
margin: 20px auto;
|
||||
}
|
||||
|
||||
.title {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
font-size: 24px;
|
||||
color: #00ff88;
|
||||
text-shadow: 0 0 20px rgba(0, 255, 136, 0.5);
|
||||
}
|
||||
|
||||
.controls {
|
||||
background: rgba(0, 255, 136, 0.05);
|
||||
border: 1px solid rgba(0, 255, 136, 0.2);
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
gap: 30px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.control-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.control-label {
|
||||
font-size: 14px;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.slider {
|
||||
width: 150px;
|
||||
height: 6px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 3px;
|
||||
outline: none;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
.slider::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: #00ff88;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 0 10px rgba(0, 255, 136, 0.5);
|
||||
}
|
||||
|
||||
.slider::-moz-range-thumb {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: #00ff88;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 0 10px rgba(0, 255, 136, 0.5);
|
||||
}
|
||||
|
||||
.slider-value {
|
||||
font-size: 14px;
|
||||
color: #00ff88;
|
||||
min-width: 40px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.button {
|
||||
background: rgba(0, 255, 136, 0.2);
|
||||
border: 1px solid rgba(0, 255, 136, 0.5);
|
||||
color: #00ff88;
|
||||
padding: 8px 20px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
background: rgba(0, 255, 136, 0.3);
|
||||
box-shadow: 0 0 15px rgba(0, 255, 136, 0.5);
|
||||
}
|
||||
|
||||
.button:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.recorder-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 400px;
|
||||
background: linear-gradient(180deg,
|
||||
rgba(0, 255, 136, 0.03) 0%,
|
||||
rgba(0, 255, 136, 0.01) 50%,
|
||||
rgba(0, 255, 136, 0.03) 100%);
|
||||
border: 1px solid rgba(0, 255, 136, 0.2);
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 0 30px rgba(0, 255, 136, 0.1);
|
||||
}
|
||||
|
||||
.grid {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image:
|
||||
repeating-linear-gradient(0deg,
|
||||
rgba(0, 255, 136, 0.05) 0px,
|
||||
transparent 1px,
|
||||
transparent 39px,
|
||||
rgba(0, 255, 136, 0.05) 40px),
|
||||
repeating-linear-gradient(90deg,
|
||||
rgba(0, 255, 136, 0.05) 0px,
|
||||
transparent 1px,
|
||||
transparent 39px,
|
||||
rgba(0, 255, 136, 0.05) 40px);
|
||||
}
|
||||
|
||||
.canvas-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
canvas {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.indicator {
|
||||
position: absolute;
|
||||
right: 30px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
box-shadow: inset 0 0 10px rgba(255, 255, 255, 0.5);
|
||||
z-index: 10;
|
||||
transition: top 0.1s ease-out;
|
||||
}
|
||||
|
||||
.indicator.download {
|
||||
background: #00ff88;
|
||||
box-shadow:
|
||||
0 0 20px #00ff88,
|
||||
0 0 40px #00ff88,
|
||||
inset 0 0 10px rgba(255, 255, 255, 0.5);
|
||||
animation: pulse-green 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.indicator.upload {
|
||||
background: #00d4ff;
|
||||
box-shadow:
|
||||
0 0 20px #00d4ff,
|
||||
0 0 40px #00d4ff,
|
||||
inset 0 0 10px rgba(255, 255, 255, 0.5);
|
||||
animation: pulse-blue 1.5s ease-in-out infinite;
|
||||
right: 60px;
|
||||
}
|
||||
|
||||
@keyframes pulse-green {
|
||||
0%, 100% {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.2);
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse-blue {
|
||||
0%, 100% {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.2);
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
.stats {
|
||||
margin-top: 30px;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.stat-box {
|
||||
background: rgba(0, 255, 136, 0.1);
|
||||
border: 1px solid rgba(0, 255, 136, 0.3);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.stat-box.upload {
|
||||
background: rgba(0, 212, 255, 0.1);
|
||||
border-color: rgba(0, 212, 255, 0.3);
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 14px;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
text-shadow: 0 0 10px currentColor;
|
||||
}
|
||||
|
||||
.stat-value.download {
|
||||
color: #00ff88;
|
||||
}
|
||||
|
||||
.stat-value.upload {
|
||||
color: #00d4ff;
|
||||
}
|
||||
|
||||
.unit {
|
||||
font-size: 16px;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.legend {
|
||||
display: flex;
|
||||
gap: 30px;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.legend-dot {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.legend-dot.download {
|
||||
background: #00ff88;
|
||||
box-shadow: 0 0 10px #00ff88;
|
||||
}
|
||||
|
||||
.legend-dot.upload {
|
||||
background: #00d4ff;
|
||||
box-shadow: 0 0 10px #00d4ff;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1 class="title">网络速度监测仪</h1>
|
||||
|
||||
<div class="controls">
|
||||
<div class="control-group">
|
||||
<label class="control-label">动画速度:</label>
|
||||
<input type="range" class="slider" id="animSpeed" min="0.5" max="3" step="0.1" value="1">
|
||||
<span class="slider-value" id="animSpeedValue">1.0</span>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label class="control-label">波动频率:</label>
|
||||
<input type="range" class="slider" id="fluctuation" min="0.1" max="2" step="0.1" value="1">
|
||||
<span class="slider-value" id="fluctuationValue">1.0</span>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<button class="button" id="pauseBtn">暂停</button>
|
||||
<button class="button" id="clearBtn">清除轨迹</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="recorder-container">
|
||||
<div class="grid"></div>
|
||||
<div class="canvas-container">
|
||||
<canvas id="speedCanvas"></canvas>
|
||||
<div class="indicator download" id="downloadIndicator"></div>
|
||||
<div class="indicator upload" id="uploadIndicator"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="legend">
|
||||
<div class="legend-item">
|
||||
<div class="legend-dot download"></div>
|
||||
<span>下载速度</span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<div class="legend-dot upload"></div>
|
||||
<span>上传速度</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stats">
|
||||
<div class="stat-box">
|
||||
<div class="stat-label">当前下载速度</div>
|
||||
<div class="stat-value download">
|
||||
<span id="currentDownload">0</span>
|
||||
<span class="unit">Mbps</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-box upload">
|
||||
<div class="stat-label">当前上传速度</div>
|
||||
<div class="stat-value upload">
|
||||
<span id="currentUpload">0</span>
|
||||
<span class="unit">Mbps</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-box">
|
||||
<div class="stat-label">平均下载速度</div>
|
||||
<div class="stat-value download">
|
||||
<span id="avgDownload">0</span>
|
||||
<span class="unit">Mbps</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-box upload">
|
||||
<div class="stat-label">平均上传速度</div>
|
||||
<div class="stat-value upload">
|
||||
<span id="avgUpload">0</span>
|
||||
<span class="unit">Mbps</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const canvas = document.getElementById('speedCanvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
const downloadIndicator = document.getElementById('downloadIndicator');
|
||||
const uploadIndicator = document.getElementById('uploadIndicator');
|
||||
|
||||
// 控制元素
|
||||
const animSpeedSlider = document.getElementById('animSpeed');
|
||||
const animSpeedValue = document.getElementById('animSpeedValue');
|
||||
const fluctuationSlider = document.getElementById('fluctuation');
|
||||
const fluctuationValue = document.getElementById('fluctuationValue');
|
||||
const pauseBtn = document.getElementById('pauseBtn');
|
||||
const clearBtn = document.getElementById('clearBtn');
|
||||
|
||||
// 设置画布大小
|
||||
function resizeCanvas() {
|
||||
const container = canvas.parentElement;
|
||||
canvas.width = container.offsetWidth;
|
||||
canvas.height = container.offsetHeight;
|
||||
}
|
||||
resizeCanvas();
|
||||
window.addEventListener('resize', resizeCanvas);
|
||||
|
||||
// 数据存储
|
||||
const maxDataPoints = 200;
|
||||
const downloadData = [];
|
||||
const uploadData = [];
|
||||
let maxSpeed = 100;
|
||||
|
||||
// 当前速度和目标速度
|
||||
let currentDownload = 50;
|
||||
let targetDownload = 50;
|
||||
let currentUpload = 30;
|
||||
let targetUpload = 30;
|
||||
|
||||
// 动画参数
|
||||
let animationTime = 0;
|
||||
let dataUpdateCounter = 0;
|
||||
let speedUpdateCounter = 0;
|
||||
let animationSpeed = 1;
|
||||
let fluctuationRate = 1;
|
||||
let isPaused = false;
|
||||
let animationId = null;
|
||||
|
||||
// 更新控制值
|
||||
animSpeedSlider.addEventListener('input', (e) => {
|
||||
animationSpeed = parseFloat(e.target.value);
|
||||
animSpeedValue.textContent = animationSpeed.toFixed(1);
|
||||
});
|
||||
|
||||
fluctuationSlider.addEventListener('input', (e) => {
|
||||
fluctuationRate = parseFloat(e.target.value);
|
||||
fluctuationValue.textContent = fluctuationRate.toFixed(1);
|
||||
});
|
||||
|
||||
// 暂停/继续按钮
|
||||
pauseBtn.addEventListener('click', () => {
|
||||
isPaused = !isPaused;
|
||||
pauseBtn.textContent = isPaused ? '继续' : '暂停';
|
||||
if (!isPaused && !animationId) {
|
||||
animate();
|
||||
}
|
||||
});
|
||||
|
||||
// 清除轨迹按钮
|
||||
clearBtn.addEventListener('click', () => {
|
||||
downloadData.length = 0;
|
||||
uploadData.length = 0;
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
});
|
||||
|
||||
// 生成随机速度数据
|
||||
function generateSpeed() {
|
||||
// 下载速度波动(通常较高)
|
||||
const downloadChange = (Math.random() - 0.5) * 20 * fluctuationRate;
|
||||
targetDownload = Math.max(20, Math.min(95, targetDownload + downloadChange));
|
||||
|
||||
// 上传速度波动(通常较低)
|
||||
const uploadChange = (Math.random() - 0.5) * 15 * fluctuationRate;
|
||||
targetUpload = Math.max(10, Math.min(60, targetUpload + uploadChange));
|
||||
|
||||
// 偶尔出现下载峰值
|
||||
if (Math.random() < 0.05 * fluctuationRate) {
|
||||
targetDownload = Math.random() * 20 + 75;
|
||||
}
|
||||
|
||||
// 偶尔出现上传峰值
|
||||
if (Math.random() < 0.05 * fluctuationRate) {
|
||||
targetUpload = Math.random() * 15 + 45;
|
||||
}
|
||||
|
||||
// 偶尔同时降低
|
||||
if (Math.random() < 0.03 * fluctuationRate) {
|
||||
targetDownload = Math.random() * 20 + 20;
|
||||
targetUpload = Math.random() * 10 + 10;
|
||||
}
|
||||
}
|
||||
|
||||
// 平滑插值
|
||||
function lerp(start, end, t) {
|
||||
return start + (end - start) * t;
|
||||
}
|
||||
|
||||
// 更新统计数据
|
||||
function updateStats() {
|
||||
document.getElementById('currentDownload').textContent = Math.round(currentDownload);
|
||||
document.getElementById('currentUpload').textContent = Math.round(currentUpload);
|
||||
|
||||
if (downloadData.length > 0) {
|
||||
const avgDown = downloadData.reduce((a, b) => a + b, 0) / downloadData.length;
|
||||
document.getElementById('avgDownload').textContent = Math.round(avgDown);
|
||||
}
|
||||
|
||||
if (uploadData.length > 0) {
|
||||
const avgUp = uploadData.reduce((a, b) => a + b, 0) / uploadData.length;
|
||||
document.getElementById('avgUpload').textContent = Math.round(avgUp);
|
||||
}
|
||||
}
|
||||
|
||||
// 绘制单条曲线
|
||||
function drawCurve(data, color, shadowColor, indicatorOffset) {
|
||||
if (data.length < 2) return;
|
||||
|
||||
// 设置渐变
|
||||
const gradient = ctx.createLinearGradient(0, 0, canvas.width, 0);
|
||||
gradient.addColorStop(0, `${color}22`);
|
||||
gradient.addColorStop(0.5, `${color}99`);
|
||||
gradient.addColorStop(1, color);
|
||||
|
||||
ctx.strokeStyle = gradient;
|
||||
ctx.lineWidth = 3;
|
||||
ctx.lineCap = 'round';
|
||||
ctx.lineJoin = 'round';
|
||||
|
||||
// 添加光晕效果
|
||||
ctx.shadowColor = shadowColor;
|
||||
ctx.shadowBlur = 10;
|
||||
|
||||
ctx.beginPath();
|
||||
|
||||
// 计算每个数据点的X位置
|
||||
const pointSpacing = 2 * animationSpeed;
|
||||
const endX = canvas.width - 30 - indicatorOffset; // 终点位置与指示器对齐
|
||||
const startX = endX - (data.length - 1) * pointSpacing;
|
||||
|
||||
// 绘制平滑曲线
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const x = startX + i * pointSpacing;
|
||||
const y = canvas.height - (data[i] / maxSpeed) * (canvas.height - 40) - 20;
|
||||
|
||||
if (i === 0) {
|
||||
ctx.moveTo(x, y);
|
||||
} else {
|
||||
// 使用贝塞尔曲线创建平滑效果
|
||||
const prevX = startX + (i - 1) * pointSpacing;
|
||||
const prevY = canvas.height - (data[i - 1] / maxSpeed) * (canvas.height - 40) - 20;
|
||||
const midX = (prevX + x) / 2;
|
||||
const midY = (prevY + y) / 2;
|
||||
|
||||
ctx.quadraticCurveTo(prevX, prevY, midX, midY);
|
||||
}
|
||||
}
|
||||
|
||||
// 连接到最后一个点
|
||||
if (data.length > 0) {
|
||||
const lastX = endX;
|
||||
const lastY = canvas.height - (data[data.length - 1] / maxSpeed) * (canvas.height - 40) - 20;
|
||||
ctx.lineTo(lastX, lastY);
|
||||
}
|
||||
|
||||
ctx.stroke();
|
||||
|
||||
// 绘制发光的尾迹
|
||||
ctx.strokeStyle = `${color}55`;
|
||||
ctx.lineWidth = 8;
|
||||
ctx.shadowBlur = 20;
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// 绘制所有曲线
|
||||
function drawCurves() {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
// 先绘制上传曲线(在后面)
|
||||
ctx.save();
|
||||
drawCurve(uploadData, '#00d4ff', '#00d4ff', 30); // 偏移30像素,对应upload indicator位置
|
||||
ctx.restore();
|
||||
|
||||
// 再绘制下载曲线(在前面)
|
||||
ctx.save();
|
||||
drawCurve(downloadData, '#00ff88', '#00ff88', 0); // 偏移0像素,对应download indicator位置
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
// 更新指示器位置
|
||||
function updateIndicators() {
|
||||
const downloadY = canvas.height - (currentDownload / maxSpeed) * (canvas.height - 40) - 20;
|
||||
downloadIndicator.style.top = (downloadY - 10) + 'px';
|
||||
|
||||
const uploadY = canvas.height - (currentUpload / maxSpeed) * (canvas.height - 40) - 20;
|
||||
uploadIndicator.style.top = (uploadY - 10) + 'px';
|
||||
}
|
||||
|
||||
// 动画循环
|
||||
function animate() {
|
||||
if (isPaused) {
|
||||
animationId = null;
|
||||
return;
|
||||
}
|
||||
|
||||
animationTime++;
|
||||
|
||||
// 平滑过渡到目标速度 - 使用固定的插值速度
|
||||
currentDownload = lerp(currentDownload, targetDownload, 0.1);
|
||||
currentUpload = lerp(currentUpload, targetUpload, 0.1);
|
||||
|
||||
// 数据更新计数器 - 基于动画速度
|
||||
dataUpdateCounter += animationSpeed;
|
||||
|
||||
// 当计数器达到10时,添加新数据点
|
||||
if (dataUpdateCounter >= 10) {
|
||||
dataUpdateCounter = 0;
|
||||
downloadData.push(currentDownload);
|
||||
uploadData.push(currentUpload);
|
||||
|
||||
// 限制数据点数量
|
||||
if (downloadData.length > maxDataPoints) {
|
||||
downloadData.shift();
|
||||
}
|
||||
if (uploadData.length > maxDataPoints) {
|
||||
uploadData.shift();
|
||||
}
|
||||
}
|
||||
|
||||
// 速度变化计数器 - 基于波动频率
|
||||
speedUpdateCounter += fluctuationRate;
|
||||
|
||||
// 根据波动频率调整生成新目标的频率
|
||||
if (speedUpdateCounter >= 60) {
|
||||
speedUpdateCounter = 0;
|
||||
generateSpeed();
|
||||
}
|
||||
|
||||
// 更新显示
|
||||
drawCurves();
|
||||
updateIndicators();
|
||||
updateStats();
|
||||
|
||||
animationId = requestAnimationFrame(animate);
|
||||
}
|
||||
|
||||
// 初始化数据
|
||||
for (let i = 0; i < 30; i++) {
|
||||
downloadData.push(50 + Math.random() * 20 - 10);
|
||||
uploadData.push(30 + Math.random() * 15 - 7.5);
|
||||
}
|
||||
|
||||
// 开始动画
|
||||
animate();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,345 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>网速记录仪</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background: #0a0a0a;
|
||||
color: #fff;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 90%;
|
||||
max-width: 1000px;
|
||||
margin: 20px auto;
|
||||
}
|
||||
|
||||
.title {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
font-size: 24px;
|
||||
color: #00ff88;
|
||||
text-shadow: 0 0 20px rgba(0, 255, 136, 0.5);
|
||||
}
|
||||
|
||||
.recorder-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 400px;
|
||||
background: linear-gradient(180deg,
|
||||
rgba(0, 255, 136, 0.03) 0%,
|
||||
rgba(0, 255, 136, 0.01) 50%,
|
||||
rgba(0, 255, 136, 0.03) 100%);
|
||||
border: 1px solid rgba(0, 255, 136, 0.2);
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 0 30px rgba(0, 255, 136, 0.1);
|
||||
}
|
||||
|
||||
.grid {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image:
|
||||
repeating-linear-gradient(0deg,
|
||||
rgba(0, 255, 136, 0.05) 0px,
|
||||
transparent 1px,
|
||||
transparent 39px,
|
||||
rgba(0, 255, 136, 0.05) 40px),
|
||||
repeating-linear-gradient(90deg,
|
||||
rgba(0, 255, 136, 0.05) 0px,
|
||||
transparent 1px,
|
||||
transparent 39px,
|
||||
rgba(0, 255, 136, 0.05) 40px);
|
||||
}
|
||||
|
||||
.canvas-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
canvas {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.indicator {
|
||||
position: absolute;
|
||||
right: 30px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: #00ff88;
|
||||
border-radius: 50%;
|
||||
box-shadow:
|
||||
0 0 20px #00ff88,
|
||||
0 0 40px #00ff88,
|
||||
inset 0 0 10px rgba(255, 255, 255, 0.5);
|
||||
animation: pulse 1.5s ease-in-out infinite;
|
||||
z-index: 10;
|
||||
transition: top 0.1s ease-out;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.2);
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
.stats {
|
||||
margin-top: 30px;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.stat-box {
|
||||
background: rgba(0, 255, 136, 0.1);
|
||||
border: 1px solid rgba(0, 255, 136, 0.3);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 14px;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: #00ff88;
|
||||
text-shadow: 0 0 10px rgba(0, 255, 136, 0.5);
|
||||
}
|
||||
|
||||
.unit {
|
||||
font-size: 16px;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
margin-left: 5px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1 class="title">网络速度监测仪</h1>
|
||||
|
||||
<div class="recorder-container">
|
||||
<div class="grid"></div>
|
||||
<div class="canvas-container">
|
||||
<canvas id="speedCanvas"></canvas>
|
||||
<div class="indicator" id="indicator"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stats">
|
||||
<div class="stat-box">
|
||||
<div class="stat-label">当前速度</div>
|
||||
<div class="stat-value">
|
||||
<span id="currentSpeed">0</span>
|
||||
<span class="unit">Mbps</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-box">
|
||||
<div class="stat-label">平均速度</div>
|
||||
<div class="stat-value">
|
||||
<span id="avgSpeed">0</span>
|
||||
<span class="unit">Mbps</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-box">
|
||||
<div class="stat-label">峰值速度</div>
|
||||
<div class="stat-value">
|
||||
<span id="maxSpeed">0</span>
|
||||
<span class="unit">Mbps</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const canvas = document.getElementById('speedCanvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
const indicator = document.getElementById('indicator');
|
||||
|
||||
// 设置画布大小
|
||||
function resizeCanvas() {
|
||||
const container = canvas.parentElement;
|
||||
canvas.width = container.offsetWidth;
|
||||
canvas.height = container.offsetHeight;
|
||||
}
|
||||
resizeCanvas();
|
||||
window.addEventListener('resize', resizeCanvas);
|
||||
|
||||
// 数据存储
|
||||
const maxDataPoints = 200;
|
||||
const speedData = [];
|
||||
let maxSpeed = 100; // 最大速度范围
|
||||
let currentSpeed = 50;
|
||||
let targetSpeed = 50;
|
||||
|
||||
// 动画参数
|
||||
let animationTime = 0;
|
||||
const scrollSpeed = 2; // 每帧移动的像素数
|
||||
|
||||
// 生成随机网速数据
|
||||
function generateSpeed() {
|
||||
// 模拟网速的自然波动
|
||||
const change = (Math.random() - 0.5) * 20;
|
||||
targetSpeed = Math.max(5, Math.min(95, targetSpeed + change));
|
||||
|
||||
// 偶尔出现速度峰值
|
||||
if (Math.random() < 0.05) {
|
||||
targetSpeed = Math.random() * 30 + 70;
|
||||
}
|
||||
|
||||
// 偶尔出现速度低谷
|
||||
if (Math.random() < 0.05) {
|
||||
targetSpeed = Math.random() * 20 + 10;
|
||||
}
|
||||
}
|
||||
|
||||
// 平滑插值
|
||||
function lerp(start, end, t) {
|
||||
return start + (end - start) * t;
|
||||
}
|
||||
|
||||
// 更新统计数据
|
||||
function updateStats() {
|
||||
document.getElementById('currentSpeed').textContent = Math.round(currentSpeed);
|
||||
|
||||
if (speedData.length > 0) {
|
||||
const avg = speedData.reduce((a, b) => a + b, 0) / speedData.length;
|
||||
document.getElementById('avgSpeed').textContent = Math.round(avg);
|
||||
|
||||
const max = Math.max(...speedData);
|
||||
document.getElementById('maxSpeed').textContent = Math.round(max);
|
||||
}
|
||||
}
|
||||
|
||||
// 绘制曲线
|
||||
function drawCurve() {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
if (speedData.length < 2) return;
|
||||
|
||||
// 设置渐变
|
||||
const gradient = ctx.createLinearGradient(0, 0, canvas.width, 0);
|
||||
gradient.addColorStop(0, 'rgba(0, 255, 136, 0.1)');
|
||||
gradient.addColorStop(0.5, 'rgba(0, 255, 136, 0.6)');
|
||||
gradient.addColorStop(1, 'rgba(0, 255, 136, 1)');
|
||||
|
||||
ctx.strokeStyle = gradient;
|
||||
ctx.lineWidth = 3;
|
||||
ctx.lineCap = 'round';
|
||||
ctx.lineJoin = 'round';
|
||||
|
||||
// 添加光晕效果
|
||||
ctx.shadowColor = '#00ff88';
|
||||
ctx.shadowBlur = 10;
|
||||
|
||||
ctx.beginPath();
|
||||
|
||||
// 计算每个数据点的X位置
|
||||
const pointSpacing = scrollSpeed;
|
||||
const startX = canvas.width - 50 - (speedData.length - 1) * pointSpacing;
|
||||
|
||||
// 绘制平滑曲线
|
||||
for (let i = 0; i < speedData.length; i++) {
|
||||
const x = startX + i * pointSpacing;
|
||||
const y = canvas.height - (speedData[i] / maxSpeed) * (canvas.height - 40) - 20;
|
||||
|
||||
if (i === 0) {
|
||||
ctx.moveTo(x, y);
|
||||
} else {
|
||||
// 使用贝塞尔曲线创建平滑效果
|
||||
const prevX = startX + (i - 1) * pointSpacing;
|
||||
const prevY = canvas.height - (speedData[i - 1] / maxSpeed) * (canvas.height - 40) - 20;
|
||||
const midX = (prevX + x) / 2;
|
||||
const midY = (prevY + y) / 2;
|
||||
|
||||
ctx.quadraticCurveTo(prevX, prevY, midX, midY);
|
||||
}
|
||||
}
|
||||
|
||||
// 连接到最后一个点
|
||||
if (speedData.length > 0) {
|
||||
const lastX = canvas.width - 50;
|
||||
const lastY = canvas.height - (speedData[speedData.length - 1] / maxSpeed) * (canvas.height - 40) - 20;
|
||||
ctx.lineTo(lastX, lastY);
|
||||
}
|
||||
|
||||
ctx.stroke();
|
||||
|
||||
// 绘制发光的尾迹
|
||||
ctx.strokeStyle = 'rgba(0, 255, 136, 0.3)';
|
||||
ctx.lineWidth = 8;
|
||||
ctx.shadowBlur = 20;
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// 更新指示器位置
|
||||
function updateIndicator() {
|
||||
const y = canvas.height - (currentSpeed / maxSpeed) * (canvas.height - 40) - 20;
|
||||
indicator.style.top = (y - 10) + 'px';
|
||||
}
|
||||
|
||||
// 动画循环
|
||||
function animate() {
|
||||
animationTime++;
|
||||
|
||||
// 平滑过渡到目标速度
|
||||
currentSpeed = lerp(currentSpeed, targetSpeed, 0.1);
|
||||
|
||||
// 每10帧添加一个新数据点
|
||||
if (animationTime % 10 === 0) {
|
||||
speedData.push(currentSpeed);
|
||||
|
||||
// 限制数据点数量
|
||||
if (speedData.length > maxDataPoints) {
|
||||
speedData.shift();
|
||||
}
|
||||
}
|
||||
|
||||
// 每60帧生成新的目标速度
|
||||
if (animationTime % 60 === 0) {
|
||||
generateSpeed();
|
||||
}
|
||||
|
||||
// 更新显示
|
||||
drawCurve();
|
||||
updateIndicator();
|
||||
updateStats();
|
||||
|
||||
requestAnimationFrame(animate);
|
||||
}
|
||||
|
||||
// 初始化数据
|
||||
for (let i = 0; i < 50; i++) {
|
||||
speedData.push(50 + Math.random() * 20 - 10);
|
||||
}
|
||||
|
||||
// 开始动画
|
||||
animate();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,677 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>网速监测仪 - 上传下载双线版</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background: #0a0e1b;
|
||||
color: #fff;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 90%;
|
||||
max-width: 1200px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 2.5em;
|
||||
background: linear-gradient(135deg, #00ff88 0%, #00a8ff 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.monitor-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 500px;
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 15px;
|
||||
overflow: hidden;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.canvas-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
canvas {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#gridCanvas {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
#chartCanvas {
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.ball {
|
||||
position: absolute;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 10;
|
||||
transition: none; /* 移除过渡效果,使用实时位置 */
|
||||
animation: pulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
transform: translate(-50%, -50%) scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: translate(-50%, -50%) scale(1.2);
|
||||
}
|
||||
}
|
||||
|
||||
.ball-download {
|
||||
background: #00ff88;
|
||||
box-shadow:
|
||||
0 0 20px #00ff88,
|
||||
0 0 40px #00ff88,
|
||||
inset 0 0 8px rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
.ball-upload {
|
||||
background: #00a8ff;
|
||||
box-shadow:
|
||||
0 0 20px #00a8ff,
|
||||
0 0 40px #00a8ff,
|
||||
inset 0 0 8px rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
.ball::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 20%;
|
||||
left: 20%;
|
||||
width: 40%;
|
||||
height: 40%;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
border-radius: 50%;
|
||||
filter: blur(2px);
|
||||
}
|
||||
|
||||
.stats-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 20px;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.stat-group {
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.stat-group h3 {
|
||||
grid-column: 1 / -1;
|
||||
margin-bottom: 10px;
|
||||
font-size: 1.2em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.stat-group.download h3 {
|
||||
color: #00ff88;
|
||||
}
|
||||
|
||||
.stat-group.upload h3 {
|
||||
color: #00a8ff;
|
||||
}
|
||||
|
||||
.stat-box {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 0.8em;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 1.5em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.download .stat-value {
|
||||
color: #00ff88;
|
||||
text-shadow: 0 0 10px rgba(0, 255, 136, 0.5);
|
||||
}
|
||||
|
||||
.upload .stat-value {
|
||||
color: #00a8ff;
|
||||
text-shadow: 0 0 10px rgba(0, 168, 255, 0.5);
|
||||
}
|
||||
|
||||
.controls {
|
||||
margin-top: 30px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 15px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.control-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
padding: 10px 20px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.control-group label {
|
||||
font-size: 0.9em;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
.control-group input[type="range"] {
|
||||
width: 120px;
|
||||
height: 4px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
outline: none;
|
||||
-webkit-appearance: none;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.control-group input[type="range"]::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: #fff;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.control-value {
|
||||
min-width: 40px;
|
||||
text-align: right;
|
||||
font-weight: bold;
|
||||
color: #00ff88;
|
||||
}
|
||||
|
||||
.legend {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.legend-color {
|
||||
width: 20px;
|
||||
height: 3px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.legend-color.download {
|
||||
background: #00ff88;
|
||||
box-shadow: 0 0 10px #00ff88;
|
||||
}
|
||||
|
||||
.legend-color.upload {
|
||||
background: #00a8ff;
|
||||
box-shadow: 0 0 10px #00a8ff;
|
||||
}
|
||||
|
||||
button {
|
||||
background: linear-gradient(135deg, #00ff88 0%, #00a8ff 100%);
|
||||
color: #0a0e1b;
|
||||
border: none;
|
||||
padding: 12px 24px;
|
||||
border-radius: 8px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 20px rgba(0, 255, 136, 0.3);
|
||||
}
|
||||
|
||||
button:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>网络速度监测仪</h1>
|
||||
<p style="color: rgba(255, 255, 255, 0.6);">实时监测上传下载速度</p>
|
||||
</div>
|
||||
|
||||
<div class="monitor-container">
|
||||
<div class="canvas-wrapper">
|
||||
<canvas id="gridCanvas"></canvas>
|
||||
<canvas id="chartCanvas"></canvas>
|
||||
<div class="ball ball-download" id="ballDownload"></div>
|
||||
<div class="ball ball-upload" id="ballUpload"></div>
|
||||
</div>
|
||||
|
||||
<div class="legend">
|
||||
<div class="legend-item">
|
||||
<div class="legend-color download"></div>
|
||||
<span>下载速度</span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<div class="legend-color upload"></div>
|
||||
<span>上传速度</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stats-container">
|
||||
<div class="stat-group download">
|
||||
<h3>📥 下载速度</h3>
|
||||
<div class="stat-box">
|
||||
<div class="stat-label">当前</div>
|
||||
<div class="stat-value"><span id="downloadCurrent">0</span> Mbps</div>
|
||||
</div>
|
||||
<div class="stat-box">
|
||||
<div class="stat-label">平均</div>
|
||||
<div class="stat-value"><span id="downloadAvg">0</span> Mbps</div>
|
||||
</div>
|
||||
<div class="stat-box">
|
||||
<div class="stat-label">峰值</div>
|
||||
<div class="stat-value"><span id="downloadMax">0</span> Mbps</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-group upload">
|
||||
<h3>📤 上传速度</h3>
|
||||
<div class="stat-box">
|
||||
<div class="stat-label">当前</div>
|
||||
<div class="stat-value"><span id="uploadCurrent">0</span> Mbps</div>
|
||||
</div>
|
||||
<div class="stat-box">
|
||||
<div class="stat-label">平均</div>
|
||||
<div class="stat-value"><span id="uploadAvg">0</span> Mbps</div>
|
||||
</div>
|
||||
<div class="stat-box">
|
||||
<div class="stat-label">峰值</div>
|
||||
<div class="stat-value"><span id="uploadMax">0</span> Mbps</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<div class="control-group">
|
||||
<label>动画速度:</label>
|
||||
<input type="range" id="speedControl" min="10" max="100" value="50">
|
||||
<span class="control-value" id="speedValue">50</span>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label>数据平滑度:</label>
|
||||
<input type="range" id="smoothControl" min="1" max="20" value="10">
|
||||
<span class="control-value" id="smoothValue">10</span>
|
||||
</div>
|
||||
<button onclick="toggleAnimation()">暂停/继续</button>
|
||||
<button onclick="resetData()">重置数据</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 获取画布和上下文
|
||||
const gridCanvas = document.getElementById('gridCanvas');
|
||||
const chartCanvas = document.getElementById('chartCanvas');
|
||||
const gridCtx = gridCanvas.getContext('2d');
|
||||
const chartCtx = chartCanvas.getContext('2d');
|
||||
|
||||
// 获取小球元素
|
||||
const ballDownload = document.getElementById('ballDownload');
|
||||
const ballUpload = document.getElementById('ballUpload');
|
||||
|
||||
// 配置参数 - 必须在使用前定义
|
||||
const config = {
|
||||
maxDataPoints: 300,
|
||||
animationSpeed: 50,
|
||||
smoothness: 10,
|
||||
maxSpeed: 100,
|
||||
pixelsPerFrame: 2,
|
||||
ballFixedX: 0.9, // 小球固定在90%的位置
|
||||
colors: {
|
||||
download: '#00ff88',
|
||||
upload: '#00a8ff',
|
||||
grid: 'rgba(255, 255, 255, 0.05)'
|
||||
}
|
||||
};
|
||||
|
||||
// 数据存储
|
||||
const data = {
|
||||
download: [],
|
||||
upload: []
|
||||
};
|
||||
|
||||
// 当前值和目标值
|
||||
const current = {
|
||||
download: 50,
|
||||
upload: 30
|
||||
};
|
||||
|
||||
const target = {
|
||||
download: 50,
|
||||
upload: 30
|
||||
};
|
||||
|
||||
// 动画控制
|
||||
let animationRunning = true;
|
||||
let frameCount = 0;
|
||||
|
||||
// 设置画布大小
|
||||
function resizeCanvas() {
|
||||
const container = gridCanvas.parentElement;
|
||||
const rect = container.getBoundingClientRect();
|
||||
|
||||
gridCanvas.width = rect.width;
|
||||
gridCanvas.height = rect.height;
|
||||
chartCanvas.width = rect.width;
|
||||
chartCanvas.height = rect.height;
|
||||
|
||||
drawGrid(); // 重绘网格
|
||||
}
|
||||
|
||||
// 绘制网格
|
||||
function drawGrid() {
|
||||
gridCtx.clearRect(0, 0, gridCanvas.width, gridCanvas.height);
|
||||
gridCtx.strokeStyle = config.colors.grid;
|
||||
gridCtx.lineWidth = 1;
|
||||
|
||||
// 水平线
|
||||
const hLines = 10;
|
||||
for (let i = 0; i <= hLines; i++) {
|
||||
const y = (gridCanvas.height / hLines) * i;
|
||||
gridCtx.beginPath();
|
||||
gridCtx.moveTo(0, y);
|
||||
gridCtx.lineTo(gridCanvas.width, y);
|
||||
gridCtx.stroke();
|
||||
}
|
||||
|
||||
// 垂直线
|
||||
const vLines = 20;
|
||||
for (let i = 0; i <= vLines; i++) {
|
||||
const x = (gridCanvas.width / vLines) * i;
|
||||
gridCtx.beginPath();
|
||||
gridCtx.moveTo(x, 0);
|
||||
gridCtx.lineTo(x, gridCanvas.height);
|
||||
gridCtx.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
// 生成随机速度
|
||||
function generateRandomSpeed(current, type) {
|
||||
const baseChange = (Math.random() - 0.5) * 15;
|
||||
let newSpeed = current + baseChange;
|
||||
|
||||
// 添加一些特征
|
||||
if (type === 'download') {
|
||||
// 下载速度通常更高且更稳定
|
||||
if (Math.random() < 0.02) newSpeed = Math.random() * 30 + 60;
|
||||
if (Math.random() < 0.01) newSpeed = Math.random() * 10 + 10; // 偶尔低速
|
||||
} else {
|
||||
// 上传速度通常较低
|
||||
if (Math.random() < 0.02) newSpeed = Math.random() * 20 + 40;
|
||||
if (Math.random() < 0.01) newSpeed = Math.random() * 10 + 5; // 偶尔低速
|
||||
}
|
||||
|
||||
return Math.max(5, Math.min(95, newSpeed));
|
||||
}
|
||||
|
||||
// 平滑插值
|
||||
function lerp(start, end, t) {
|
||||
return start + (end - start) * t;
|
||||
}
|
||||
|
||||
// 绘制曲线
|
||||
function drawCurve(ctx, data, color, yOffset = 0) {
|
||||
if (data.length < 2) return null;
|
||||
|
||||
ctx.save(); // 保存上下文状态
|
||||
|
||||
// 创建渐变色
|
||||
const gradient = ctx.createLinearGradient(0, 0, chartCanvas.width, 0);
|
||||
gradient.addColorStop(0, color + '33'); // 33是20%的透明度
|
||||
gradient.addColorStop(0.5, color + '88'); // 88是53%的透明度
|
||||
gradient.addColorStop(1, color);
|
||||
|
||||
ctx.strokeStyle = gradient;
|
||||
ctx.lineWidth = 3;
|
||||
ctx.lineCap = 'round';
|
||||
ctx.lineJoin = 'round';
|
||||
ctx.shadowColor = color;
|
||||
ctx.shadowBlur = 15;
|
||||
|
||||
ctx.beginPath();
|
||||
|
||||
const spacing = config.pixelsPerFrame;
|
||||
const fixedX = chartCanvas.width * config.ballFixedX;
|
||||
|
||||
// 计算起始X位置,使最后一个点在固定位置
|
||||
const startX = fixedX - (data.length - 1) * spacing;
|
||||
|
||||
let lastPoint = null;
|
||||
|
||||
// 绘制平滑曲线
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const x = startX + i * spacing;
|
||||
const y = chartCanvas.height - (data[i] / config.maxSpeed) * (chartCanvas.height - 60) - 30 + yOffset;
|
||||
|
||||
if (x < -50) continue; // 跳过画布外的点
|
||||
|
||||
if (i === 0 || (i > 0 && startX + (i - 1) * spacing < -50)) {
|
||||
ctx.moveTo(x, y);
|
||||
} else {
|
||||
// 使用贝塞尔曲线创建平滑效果
|
||||
const prevX = startX + (i - 1) * spacing;
|
||||
const prevY = chartCanvas.height - (data[i - 1] / config.maxSpeed) * (chartCanvas.height - 60) - 30 + yOffset;
|
||||
const cpX = (prevX + x) / 2;
|
||||
const cpY = (prevY + y) / 2;
|
||||
|
||||
ctx.quadraticCurveTo(prevX, prevY, cpX, cpY);
|
||||
}
|
||||
|
||||
if (i === data.length - 1) {
|
||||
lastPoint = { x, y };
|
||||
ctx.lineTo(x, y); // 确保最后一个点被绘制
|
||||
}
|
||||
}
|
||||
|
||||
ctx.stroke();
|
||||
|
||||
// 绘制发光效果
|
||||
ctx.strokeStyle = color + '33'; // 20%透明度
|
||||
ctx.lineWidth = 8;
|
||||
ctx.shadowBlur = 25;
|
||||
ctx.stroke();
|
||||
|
||||
ctx.restore(); // 恢复上下文状态
|
||||
|
||||
return lastPoint;
|
||||
}
|
||||
|
||||
// 更新小球位置
|
||||
function updateBallPosition(ball, position) {
|
||||
if (position && ball) {
|
||||
ball.style.left = position.x + 'px';
|
||||
ball.style.top = position.y + 'px';
|
||||
ball.style.display = 'block'; // 确保小球可见
|
||||
}
|
||||
}
|
||||
|
||||
// 更新统计数据
|
||||
function updateStats(type, data) {
|
||||
if (data.length === 0) return;
|
||||
|
||||
const currentValue = Math.round(data[data.length - 1]);
|
||||
const avgValue = Math.round(data.reduce((a, b) => a + b, 0) / data.length);
|
||||
const maxValue = Math.round(Math.max(...data));
|
||||
|
||||
document.getElementById(`${type}Current`).textContent = currentValue;
|
||||
document.getElementById(`${type}Avg`).textContent = avgValue;
|
||||
document.getElementById(`${type}Max`).textContent = maxValue;
|
||||
}
|
||||
|
||||
// 主动画循环
|
||||
function animate() {
|
||||
if (animationRunning) {
|
||||
frameCount++;
|
||||
|
||||
// 平滑过渡到目标值
|
||||
const smoothFactor = 1 / config.smoothness;
|
||||
current.download = lerp(current.download, target.download, smoothFactor);
|
||||
current.upload = lerp(current.upload, target.upload, smoothFactor);
|
||||
|
||||
// 每几帧添加一个数据点
|
||||
if (frameCount % 2 === 0) {
|
||||
data.download.push(current.download);
|
||||
data.upload.push(current.upload);
|
||||
|
||||
// 限制数据点数量
|
||||
if (data.download.length > config.maxDataPoints) {
|
||||
data.download.shift();
|
||||
}
|
||||
if (data.upload.length > config.maxDataPoints) {
|
||||
data.upload.shift();
|
||||
}
|
||||
}
|
||||
|
||||
// 定期生成新目标
|
||||
if (frameCount % config.animationSpeed === 0) {
|
||||
target.download = generateRandomSpeed(target.download, 'download');
|
||||
target.upload = generateRandomSpeed(target.upload, 'upload');
|
||||
}
|
||||
|
||||
// 清空画布
|
||||
chartCtx.clearRect(0, 0, chartCanvas.width, chartCanvas.height);
|
||||
|
||||
// 绘制曲线并获取末端位置
|
||||
const downloadPos = drawCurve(chartCtx, data.download, config.colors.download, 0);
|
||||
const uploadPos = drawCurve(chartCtx, data.upload, config.colors.upload, 20);
|
||||
|
||||
// 更新小球位置(确保与曲线末端对齐)
|
||||
updateBallPosition(ballDownload, downloadPos);
|
||||
updateBallPosition(ballUpload, uploadPos);
|
||||
|
||||
// 更新统计
|
||||
updateStats('download', data.download);
|
||||
updateStats('upload', data.upload);
|
||||
}
|
||||
|
||||
requestAnimationFrame(animate);
|
||||
}
|
||||
|
||||
// 控制函数
|
||||
window.toggleAnimation = function() {
|
||||
animationRunning = !animationRunning;
|
||||
}
|
||||
|
||||
window.resetData = function() {
|
||||
data.download = [];
|
||||
data.upload = [];
|
||||
current.download = 50;
|
||||
current.upload = 30;
|
||||
target.download = 50;
|
||||
target.upload = 30;
|
||||
frameCount = 0;
|
||||
|
||||
// 添加一些初始数据
|
||||
for (let i = 0; i < 50; i++) {
|
||||
data.download.push(50 + Math.random() * 20 - 10);
|
||||
data.upload.push(30 + Math.random() * 15 - 7.5);
|
||||
}
|
||||
}
|
||||
|
||||
// 控件事件
|
||||
document.getElementById('speedControl').addEventListener('input', (e) => {
|
||||
config.animationSpeed = parseInt(e.target.value);
|
||||
document.getElementById('speedValue').textContent = e.target.value;
|
||||
});
|
||||
|
||||
document.getElementById('smoothControl').addEventListener('input', (e) => {
|
||||
config.smoothness = parseInt(e.target.value);
|
||||
document.getElementById('smoothValue').textContent = e.target.value;
|
||||
});
|
||||
|
||||
// 初始化
|
||||
console.log('开始初始化...');
|
||||
resizeCanvas();
|
||||
window.addEventListener('resize', resizeCanvas);
|
||||
|
||||
// 测试canvas是否正常工作
|
||||
if (chartCtx) {
|
||||
chartCtx.fillStyle = 'rgba(255, 255, 255, 0.1)';
|
||||
chartCtx.fillRect(0, 0, 100, 100);
|
||||
console.log('Canvas 测试成功');
|
||||
}
|
||||
|
||||
// 添加一些初始数据
|
||||
for (let i = 0; i < 50; i++) {
|
||||
data.download.push(50 + Math.random() * 20 - 10);
|
||||
data.upload.push(30 + Math.random() * 15 - 7.5);
|
||||
}
|
||||
|
||||
// 设置初始值为最后一个数据点
|
||||
current.download = data.download[data.download.length - 1];
|
||||
current.upload = data.upload[data.upload.length - 1];
|
||||
target.download = current.download;
|
||||
target.upload = current.upload;
|
||||
|
||||
drawGrid();
|
||||
console.log('初始化完成,开始动画...');
|
||||
console.log('数据点数量:', data.download.length, data.upload.length);
|
||||
animate();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,103 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>NetGuardian - Modern Network Monitor</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
|
||||
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
|
||||
<style>
|
||||
body { font-family: 'Segoe UI', sans-serif; }
|
||||
.card { @apply rounded-2xl shadow-md bg-white dark:bg-gray-800 p-4; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-gray-100 dark:bg-gray-900 text-gray-900 dark:text-white">
|
||||
<div class="flex h-screen overflow-hidden">
|
||||
<!-- Sidebar -->
|
||||
<aside class="w-72 bg-white dark:bg-gray-800 border-r border-gray-200 dark:border-gray-700 p-4 flex flex-col">
|
||||
<h2 class="text-xl font-semibold mb-4">NetGuardian</h2>
|
||||
<nav class="space-y-2">
|
||||
<button class="w-full text-left hover:bg-gray-200 dark:hover:bg-gray-700 p-2 rounded">总览 Dashboard</button>
|
||||
<button class="w-full text-left hover:bg-gray-200 dark:hover:bg-gray-700 p-2 rounded">连接详情 Connections</button>
|
||||
<button class="w-full text-left hover:bg-gray-200 dark:hover:bg-gray-700 p-2 rounded">进程监控 Processes</button>
|
||||
<button class="w-full text-left hover:bg-gray-200 dark:hover:bg-gray-700 p-2 rounded">流量统计 Traffic</button>
|
||||
<button class="w-full text-left hover:bg-gray-200 dark:hover:bg-gray-700 p-2 rounded">规则管理 Rules</button>
|
||||
<button class="w-full text-left hover:bg-gray-200 dark:hover:bg-gray-700 p-2 rounded">设置 Settings</button>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="flex-1 overflow-y-auto p-6 space-y-6">
|
||||
<!-- Summary Cards -->
|
||||
<section class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-4">
|
||||
<div class="card">
|
||||
<h3 class="text-sm font-medium">当前连接</h3>
|
||||
<p class="text-2xl font-bold">128</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3 class="text-sm font-medium">上传总速率</h3>
|
||||
<p class="text-2xl font-bold">2.3 MB/s</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3 class="text-sm font-medium">下载总速率</h3>
|
||||
<p class="text-2xl font-bold">5.1 MB/s</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3 class="text-sm font-medium">阻止请求</h3>
|
||||
<p class="text-2xl font-bold">27</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Real-time Chart -->
|
||||
<section class="card">
|
||||
<h3 class="text-lg font-semibold mb-2">实时流量图</h3>
|
||||
<div id="traffic-chart" style="height: 300px;"></div>
|
||||
</section>
|
||||
|
||||
<!-- Connection Table -->
|
||||
<section class="card">
|
||||
<h3 class="text-lg font-semibold mb-2">当前连接详情</h3>
|
||||
<div class="overflow-auto">
|
||||
<table class="min-w-full text-sm">
|
||||
<thead>
|
||||
<tr class="bg-gray-200 dark:bg-gray-700">
|
||||
<th class="text-left p-2">进程</th>
|
||||
<th class="text-left p-2">本地端口</th>
|
||||
<th class="text-left p-2">远程地址</th>
|
||||
<th class="text-left p-2">协议</th>
|
||||
<th class="text-left p-2">上传速率</th>
|
||||
<th class="text-left p-2">下载速率</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="p-2">chrome.exe</td>
|
||||
<td class="p-2">49152</td>
|
||||
<td class="p-2">93.184.216.34 (example.com)</td>
|
||||
<td class="p-2">TCP</td>
|
||||
<td class="p-2">123 KB/s</td>
|
||||
<td class="p-2">987 KB/s</td>
|
||||
</tr>
|
||||
<!-- 更多行通过 JS 动态生成 -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const chart = echarts.init(document.getElementById('traffic-chart'));
|
||||
const option = {
|
||||
tooltip: { trigger: 'axis' },
|
||||
xAxis: { type: 'category', data: Array.from({length: 20}, (_, i) => `${i}s`) },
|
||||
yAxis: { type: 'value', name: 'KB/s' },
|
||||
series: [
|
||||
{ name: '上传', type: 'line', data: Array.from({length: 20}, () => Math.random() * 100) },
|
||||
{ name: '下载', type: 'line', data: Array.from({length: 20}, () => Math.random() * 200) }
|
||||
]
|
||||
};
|
||||
chart.setOption(option);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,732 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>NetGuard - 现代化网络管理工具</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:root {
|
||||
--bg-primary: #0a0e1a;
|
||||
--bg-secondary: #151923;
|
||||
--bg-card: #1a1f2e;
|
||||
--bg-hover: #252b3b;
|
||||
--text-primary: #ffffff;
|
||||
--text-secondary: #8892b0;
|
||||
--text-dim: #64748b;
|
||||
--accent-primary: #00d4ff;
|
||||
--accent-secondary: #7c3aed;
|
||||
--accent-success: #10b981;
|
||||
--accent-danger: #ef4444;
|
||||
--accent-warning: #f59e0b;
|
||||
--border-color: #2a3142;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
overflow: hidden;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
/* 主布局 */
|
||||
.app-container {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
/* 侧边栏 */
|
||||
.sidebar {
|
||||
width: 240px;
|
||||
background: var(--bg-secondary);
|
||||
border-right: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.sidebar.collapsed {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
padding: 20px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.logo-icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.nav-menu {
|
||||
flex: 1;
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
padding: 12px 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.nav-item:hover {
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
.nav-item.active {
|
||||
background: var(--bg-hover);
|
||||
color: var(--accent-primary);
|
||||
}
|
||||
|
||||
.nav-item.active::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 3px;
|
||||
background: var(--accent-primary);
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* 主内容区 */
|
||||
.main-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 顶部栏 */
|
||||
.top-bar {
|
||||
height: 60px;
|
||||
background: var(--bg-secondary);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: var(--bg-card);
|
||||
border-radius: 8px;
|
||||
padding: 8px 16px;
|
||||
width: 300px;
|
||||
border: 1px solid var(--border-color);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.search-box:focus-within {
|
||||
border-color: var(--accent-primary);
|
||||
box-shadow: 0 0 0 3px rgba(0, 212, 255, 0.1);
|
||||
}
|
||||
|
||||
.search-input {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text-primary);
|
||||
outline: none;
|
||||
width: 100%;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.top-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
padding: 8px 16px;
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.action-btn:hover {
|
||||
border-color: var(--accent-primary);
|
||||
color: var(--accent-primary);
|
||||
}
|
||||
|
||||
.action-btn.primary {
|
||||
background: var(--accent-primary);
|
||||
color: var(--bg-primary);
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* 内容区域 */
|
||||
.content-area {
|
||||
flex: 1;
|
||||
padding: 24px;
|
||||
overflow-y: auto;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-rows: auto auto 1fr;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
/* 统计卡片 */
|
||||
.stats-container {
|
||||
grid-column: 1 / -1;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.stat-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 3px;
|
||||
background: linear-gradient(90deg, var(--accent-primary), var(--accent-secondary));
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.stat-card:hover::before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
color: var(--text-secondary);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.stat-trend {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.trend-up {
|
||||
color: var(--accent-success);
|
||||
}
|
||||
|
||||
.trend-down {
|
||||
color: var(--accent-danger);
|
||||
}
|
||||
|
||||
/* 实时监控区 */
|
||||
.monitor-section {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
height: 300px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--text-dim);
|
||||
}
|
||||
|
||||
/* 连接列表 */
|
||||
.connections-section {
|
||||
grid-column: 1 / -1;
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.connection-filters {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.filter-chip {
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 20px;
|
||||
padding: 6px 16px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.filter-chip.active {
|
||||
background: var(--accent-primary);
|
||||
color: var(--bg-primary);
|
||||
border-color: var(--accent-primary);
|
||||
}
|
||||
|
||||
.connections-table {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.table th {
|
||||
text-align: left;
|
||||
padding: 12px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
color: var(--text-secondary);
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.table td {
|
||||
padding: 12px;
|
||||
border-bottom: 1px solid rgba(42, 49, 66, 0.5);
|
||||
}
|
||||
|
||||
.table tr:hover {
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
.process-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.process-icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.process-name {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.process-path {
|
||||
font-size: 12px;
|
||||
color: var(--text-dim);
|
||||
}
|
||||
|
||||
.speed-badge {
|
||||
background: var(--bg-secondary);
|
||||
padding: 4px 12px;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.status-active {
|
||||
background: var(--accent-success);
|
||||
box-shadow: 0 0 8px var(--accent-success);
|
||||
}
|
||||
|
||||
.status-blocked {
|
||||
background: var(--accent-danger);
|
||||
}
|
||||
|
||||
/* 悬浮详情面板 */
|
||||
.detail-panel {
|
||||
position: fixed;
|
||||
right: -400px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 400px;
|
||||
background: var(--bg-secondary);
|
||||
border-left: 1px solid var(--border-color);
|
||||
transition: right 0.3s ease;
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.detail-panel.open {
|
||||
right: 0;
|
||||
box-shadow: -10px 0 30px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
padding: 20px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.close-btn:hover {
|
||||
border-color: var(--accent-danger);
|
||||
color: var(--accent-danger);
|
||||
}
|
||||
|
||||
/* 动画效果 */
|
||||
@keyframes pulse {
|
||||
0% { opacity: 1; }
|
||||
50% { opacity: 0.5; }
|
||||
100% { opacity: 1; }
|
||||
}
|
||||
|
||||
.live-indicator {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: var(--accent-success);
|
||||
border-radius: 50%;
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
/* 响应式调整 */
|
||||
@media (max-width: 1200px) {
|
||||
.stats-container {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.content-area {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-container">
|
||||
<!-- 侧边栏 -->
|
||||
<aside class="sidebar">
|
||||
<div class="logo">
|
||||
<div class="logo-icon">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
|
||||
<path d="M12 2L2 7V12C2 16.5 4.5 20.5 8 21.5C8.5 20.5 9 19.5 9.5 18.5C7.5 17.5 6 15.5 6 13V9L12 6L18 9V13C18 13.5 17.9 14 17.8 14.5C18.8 14.5 19.8 14.8 20.5 15.3C20.8 14.2 21 13.1 21 12V7L12 2Z" fill="white"/>
|
||||
<circle cx="18" cy="18" r="4" fill="#00d4ff"/>
|
||||
</svg>
|
||||
</div>
|
||||
<span class="logo-text">NetGuard</span>
|
||||
</div>
|
||||
|
||||
<nav class="nav-menu">
|
||||
<div class="nav-item active">
|
||||
<svg class="nav-icon" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M3 12v3c0 1.657 3.134 3 7 3s7-1.343 7-3v-3c0 1.657-3.134 3-7 3s-7-1.343-7-3z"/>
|
||||
<path d="M3 7v3c0 1.657 3.134 3 7 3s7-1.343 7-3V7c0 1.657-3.134 3-7 3S3 8.657 3 7z"/>
|
||||
<path d="M17 5c0 1.657-3.134 3-7 3S3 6.657 3 5s3.134-3 7-3 7 1.343 7 3z"/>
|
||||
</svg>
|
||||
<span>实时监控</span>
|
||||
</div>
|
||||
<div class="nav-item">
|
||||
<svg class="nav-icon" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M9 2a1 1 0 000 2h2a1 1 0 100-2H9z"/>
|
||||
<path fill-rule="evenodd" d="M4 5a2 2 0 012-2 1 1 0 000 2H6a2 2 0 00-2 2v6a2 2 0 002 2h2a1 1 0 100-2H6V7h8v6h-2a1 1 0 100 2h2a2 2 0 002-2V7a2 2 0 00-2-2h-2a1 1 0 100-2h2a2 2 0 012 2z"/>
|
||||
</svg>
|
||||
<span>防火墙规则</span>
|
||||
</div>
|
||||
<div class="nav-item">
|
||||
<svg class="nav-icon" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M9 4.804A7.968 7.968 0 005.5 4c-1.255 0-2.443.29-3.5.804v10A7.969 7.969 0 015.5 14c1.669 0 3.218.51 4.5 1.385A7.962 7.962 0 0114.5 14c1.255 0 2.443.29 3.5.804v-10A7.968 7.968 0 0014.5 4c-1.255 0-2.443.29-3.5.804V12a1 1 0 11-2 0V4.804z"/>
|
||||
</svg>
|
||||
<span>应用管理</span>
|
||||
</div>
|
||||
<div class="nav-item">
|
||||
<svg class="nav-icon" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M2 11a1 1 0 011-1h2a1 1 0 011 1v5a1 1 0 01-1 1H3a1 1 0 01-1-1v-5zM8 7a1 1 0 011-1h2a1 1 0 011 1v9a1 1 0 01-1 1H9a1 1 0 01-1-1V7zM14 4a1 1 0 011-1h2a1 1 0 011 1v12a1 1 0 01-1 1h-2a1 1 0 01-1-1V4z"/>
|
||||
</svg>
|
||||
<span>流量分析</span>
|
||||
</div>
|
||||
<div class="nav-item">
|
||||
<svg class="nav-icon" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M11.49 3.17c-.38-1.56-2.6-1.56-2.98 0a1.532 1.532 0 01-2.286.948c-1.372-.836-2.942.734-2.106 2.106.54.886.061 2.042-.947 2.287-1.561.379-1.561 2.6 0 2.978a1.532 1.532 0 01.947 2.287c-.836 1.372.734 2.942 2.106 2.106a1.532 1.532 0 012.287.947c.379 1.561 2.6 1.561 2.978 0a1.533 1.533 0 012.287-.947c1.372.836 2.942-.734 2.106-2.106a1.533 1.533 0 01.947-2.287c1.561-.379 1.561-2.6 0-2.978a1.532 1.532 0 01-.947-2.287c.836-1.372-.734-2.942-2.106-2.106a1.532 1.532 0 01-2.287-.947zM10 13a3 3 0 100-6 3 3 0 000 6z"/>
|
||||
</svg>
|
||||
<span>设置</span>
|
||||
</div>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<!-- 主内容区 -->
|
||||
<main class="main-content">
|
||||
<!-- 顶部栏 -->
|
||||
<header class="top-bar">
|
||||
<div class="search-box">
|
||||
<svg width="20" height="20" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="9" cy="9" r="7"/>
|
||||
<path d="M14 14l4 4"/>
|
||||
</svg>
|
||||
<input type="text" class="search-input" placeholder="搜索进程、IP地址或域名...">
|
||||
</div>
|
||||
|
||||
<div class="top-actions">
|
||||
<div class="live-indicator"></div>
|
||||
<span style="color: var(--text-secondary); font-size: 14px; margin-right: 16px;">实时监控中</span>
|
||||
<button class="action-btn">
|
||||
<svg width="16" height="16" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z"/>
|
||||
</svg>
|
||||
阻止所有
|
||||
</button>
|
||||
<button class="action-btn primary">
|
||||
<svg width="16" height="16" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M10 12a2 2 0 100-4 2 2 0 000 4z"/>
|
||||
<path fill-rule="evenodd" d="M.458 10C1.732 5.943 5.522 3 10 3s8.268 2.943 9.542 7c-1.274 4.057-5.064 7-9.542 7S1.732 14.057.458 10zM14 10a4 4 0 11-8 0 4 4 0 018 0z"/>
|
||||
</svg>
|
||||
学习模式
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<div class="content-area">
|
||||
<!-- 统计卡片 -->
|
||||
<div class="stats-container">
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">活跃连接</div>
|
||||
<div class="stat-value">247</div>
|
||||
<div class="stat-trend trend-up">↑ 12%</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">总下载</div>
|
||||
<div class="stat-value">3.2GB</div>
|
||||
<div class="stat-trend trend-up">↑ 5.3MB/s</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">总上传</div>
|
||||
<div class="stat-value">1.1GB</div>
|
||||
<div class="stat-trend trend-up">↑ 2.1MB/s</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">已阻止</div>
|
||||
<div class="stat-value">89</div>
|
||||
<div class="stat-trend" style="color: var(--accent-warning);">15 今日</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 实时监控图表 -->
|
||||
<div class="monitor-section">
|
||||
<div class="section-header">
|
||||
<h3 class="section-title">网络流量实时监控</h3>
|
||||
<select style="background: var(--bg-secondary); border: 1px solid var(--border-color); padding: 4px 8px; border-radius: 4px; color: var(--text-primary);">
|
||||
<option>最近5分钟</option>
|
||||
<option>最近1小时</option>
|
||||
<option>最近24小时</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="chart-container">
|
||||
<!-- 这里会放置 Echarts 实时流量图 -->
|
||||
<span>实时流量图表区域</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="monitor-section">
|
||||
<div class="section-header">
|
||||
<h3 class="section-title">应用流量排行</h3>
|
||||
<button class="action-btn" style="padding: 4px 12px; font-size: 12px;">查看全部</button>
|
||||
</div>
|
||||
<div class="chart-container">
|
||||
<!-- 这里会放置应用流量饼图 -->
|
||||
<span>应用流量分布图</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 连接列表 -->
|
||||
<div class="connections-section">
|
||||
<div class="section-header">
|
||||
<h3 class="section-title">活跃连接</h3>
|
||||
<div class="connection-filters">
|
||||
<div class="filter-chip active">全部</div>
|
||||
<div class="filter-chip">TCP</div>
|
||||
<div class="filter-chip">UDP</div>
|
||||
<div class="filter-chip">已阻止</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="connections-table">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>进程</th>
|
||||
<th>协议</th>
|
||||
<th>本地地址</th>
|
||||
<th>远程地址</th>
|
||||
<th>速度</th>
|
||||
<th>流量</th>
|
||||
<th>状态</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="process-info">
|
||||
<div class="process-icon">
|
||||
<svg width="20" height="20" fill="#00d4ff" viewBox="0 0 24 24">
|
||||
<path d="M12,3C7.58,3 4,4.79 4,7C4,9.21 7.58,11 12,11C16.42,11 20,9.21 20,7C20,4.79 16.42,3 12,3M4,9V12C4,14.21 7.58,16 12,16C16.42,16 20,14.21 20,12V9C20,11.21 16.42,13 12,13C7.58,13 4,11.21 4,9M4,14V17C4,19.21 7.58,21 12,21C16.42,21 20,19.21 20,17V14C20,16.21 16.42,18 12,18C7.58,18 4,16.21 4,14Z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<div class="process-name">Chrome.exe</div>
|
||||
<div class="process-path">C:\Program Files\Google\Chrome</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>TCP</td>
|
||||
<td>192.168.1.100:54321</td>
|
||||
<td>
|
||||
<div>142.250.185.78:443</div>
|
||||
<div class="process-path">youtube.com</div>
|
||||
</td>
|
||||
<td><span class="speed-badge">2.3 MB/s</span></td>
|
||||
<td>156 MB</td>
|
||||
<td><span class="status-dot status-active"></span>活跃</td>
|
||||
<td>
|
||||
<button class="action-btn" style="padding: 4px 12px; font-size: 12px;">详情</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="process-info">
|
||||
<div class="process-icon" style="background: #ef4444;">
|
||||
<svg width="20" height="20" fill="white" viewBox="0 0 24 24">
|
||||
<path d="M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M12,4A8,8 0 0,1 20,12A8,8 0 0,1 12,20A8,8 0 0,1 4,12A8,8 0 0,1 12,4M12,6A6,6 0 0,0 6,12A6,6 0 0,0 12,18A6,6 0 0,0 18,12A6,6 0 0,0 12,6M12,8A4,4 0 0,1 16,12A4,4 0 0,1 12,16A4,4 0 0,1 8,12A4,4 0 0,1 12,8M12,10A2,2 0 0,0 10,12A2,2 0 0,0 12,14A2,2 0 0,0 14,12A2,2 0 0,0 12,10Z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<div class="process-name">Unknown.exe</div>
|
||||
<div class="process-path">C:\Users\AppData\Temp</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>TCP</td>
|
||||
<td>192.168.1.100:55789</td>
|
||||
<td>
|
||||
<div>185.45.67.89:8080</div>
|
||||
<div class="process-path" style="color: var(--accent-danger);">未知</div>
|
||||
</td>
|
||||
<td><span class="speed-badge">0 KB/s</span></td>
|
||||
<td>12 KB</td>
|
||||
<td><span class="status-dot status-blocked"></span>已阻止</td>
|
||||
<td>
|
||||
<button class="action-btn" style="padding: 4px 12px; font-size: 12px;">详情</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- 详情面板 -->
|
||||
<div class="detail-panel" id="detailPanel">
|
||||
<div class="panel-header">
|
||||
<h3>连接详情</h3>
|
||||
<button class="close-btn" onclick="document.getElementById('detailPanel').classList.remove('open')">
|
||||
<svg width="20" height="20" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<!-- 详情内容 -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 模拟点击详情按钮打开面板
|
||||
document.querySelectorAll('.action-btn').forEach(btn => {
|
||||
if (btn.textContent === '详情') {
|
||||
btn.addEventListener('click', () => {
|
||||
document.getElementById('detailPanel').classList.add('open');
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,1063 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>NetGuard - 现代网络管理工具</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:root {
|
||||
--bg-primary: #0a0a0a;
|
||||
--bg-secondary: #141414;
|
||||
--bg-tertiary: #1a1a1a;
|
||||
--bg-hover: #242424;
|
||||
--text-primary: #ffffff;
|
||||
--text-secondary: #a0a0a0;
|
||||
--text-tertiary: #666666;
|
||||
--accent: #3b82f6;
|
||||
--accent-hover: #2563eb;
|
||||
--success: #10b981;
|
||||
--warning: #f59e0b;
|
||||
--danger: #ef4444;
|
||||
--border: #2a2a2a;
|
||||
--shadow: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* 主布局 */
|
||||
.app-container {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
/* 侧边栏 */
|
||||
.sidebar {
|
||||
width: 240px;
|
||||
background: var(--bg-secondary);
|
||||
border-right: 1px solid var(--border);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.sidebar.collapsed {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
padding: 20px;
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: var(--accent);
|
||||
border-bottom: 1px solid var(--border);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.logo-icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background: linear-gradient(135deg, var(--accent), #8b5cf6);
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.nav-menu {
|
||||
flex: 1;
|
||||
padding: 12px 0;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 20px;
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.nav-item:hover {
|
||||
background: var(--bg-hover);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.nav-item.active {
|
||||
color: var(--accent);
|
||||
background: rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
.nav-item.active::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 3px;
|
||||
background: var(--accent);
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 主内容区 */
|
||||
.main-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 顶部栏 */
|
||||
.topbar {
|
||||
height: 60px;
|
||||
background: var(--bg-secondary);
|
||||
border-bottom: 1px solid var(--border);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
position: relative;
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 100%;
|
||||
padding: 8px 16px 8px 40px;
|
||||
background: var(--bg-tertiary);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
color: var(--text-primary);
|
||||
font-size: 14px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.search-input:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
position: absolute;
|
||||
left: 12px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.topbar-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.icon-button {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 8px;
|
||||
background: var(--bg-tertiary);
|
||||
border: 1px solid var(--border);
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.icon-button:hover {
|
||||
background: var(--bg-hover);
|
||||
color: var(--text-primary);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
/* 内容区域 */
|
||||
.content-area {
|
||||
flex: 1;
|
||||
padding: 24px;
|
||||
overflow-y: auto;
|
||||
background: var(--bg-primary);
|
||||
}
|
||||
|
||||
/* 统计卡片 */
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 24px var(--shadow);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.stat-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 3px;
|
||||
background: linear-gradient(90deg, var(--accent), #8b5cf6);
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.stat-card:hover::before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.stat-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: start;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.stat-title {
|
||||
color: var(--text-secondary);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 8px;
|
||||
background: linear-gradient(135deg, var(--text-primary), var(--text-secondary));
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
.stat-change {
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.stat-change.positive {
|
||||
color: var(--success);
|
||||
}
|
||||
|
||||
.stat-change.negative {
|
||||
color: var(--danger);
|
||||
}
|
||||
|
||||
/* 实时连接表格 */
|
||||
.connections-section {
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
padding: 20px 24px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.filter-tabs {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
background: var(--bg-tertiary);
|
||||
padding: 4px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.filter-tab {
|
||||
padding: 6px 16px;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.filter-tab:hover {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.filter-tab.active {
|
||||
background: var(--accent);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.connections-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.table-header {
|
||||
background: var(--bg-tertiary);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.table-header th {
|
||||
padding: 12px 16px;
|
||||
text-align: left;
|
||||
font-weight: 500;
|
||||
color: var(--text-secondary);
|
||||
font-size: 13px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.table-row {
|
||||
border-bottom: 1px solid var(--border);
|
||||
transition: all 0.2s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.table-row:hover {
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
.table-row td {
|
||||
padding: 16px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.process-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.process-icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 8px;
|
||||
background: var(--bg-tertiary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.process-name {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.process-path {
|
||||
font-size: 12px;
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.connection-status {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 4px 12px;
|
||||
border-radius: 16px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-active {
|
||||
background: rgba(16, 185, 129, 0.1);
|
||||
color: var(--success);
|
||||
}
|
||||
|
||||
.status-blocked {
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
color: var(--danger);
|
||||
}
|
||||
|
||||
.bandwidth-meter {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.bandwidth-bar {
|
||||
width: 80px;
|
||||
height: 4px;
|
||||
background: var(--bg-tertiary);
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.bandwidth-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, var(--accent), #8b5cf6);
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
/* 浮动操作面板 */
|
||||
.floating-panel {
|
||||
position: fixed;
|
||||
bottom: 24px;
|
||||
right: 24px;
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
box-shadow: 0 8px 32px var(--shadow);
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
transform: translateY(100px);
|
||||
opacity: 0;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.floating-panel.visible {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
padding: 8px 16px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.action-button.primary {
|
||||
background: var(--accent);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.action-button.primary:hover {
|
||||
background: var(--accent-hover);
|
||||
}
|
||||
|
||||
.action-button.secondary {
|
||||
background: var(--bg-tertiary);
|
||||
color: var(--text-primary);
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.action-button.secondary:hover {
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 1200px) {
|
||||
.sidebar {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.logo span,
|
||||
.nav-item span {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
width: 300px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.stats-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.topbar {
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.content-area {
|
||||
padding: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 动画 */
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.live-indicator {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: var(--success);
|
||||
border-radius: 50%;
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
/* 自定义滚动条 */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: var(--bg-tertiary);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--border);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--text-tertiary);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-container">
|
||||
<!-- 侧边栏 -->
|
||||
<aside class="sidebar">
|
||||
<div class="logo">
|
||||
<div class="logo-icon">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M12 2L2 7v10c0 5.5 3.8 10 10 10s10-4.5 10-10V7l-10-5z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<span>NetGuard</span>
|
||||
</div>
|
||||
|
||||
<nav class="nav-menu">
|
||||
<div class="nav-item active">
|
||||
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<rect x="3" y="3" width="7" height="7" rx="1"/>
|
||||
<rect x="14" y="3" width="7" height="7" rx="1"/>
|
||||
<rect x="3" y="14" width="7" height="7" rx="1"/>
|
||||
<rect x="14" y="14" width="7" height="7" rx="1"/>
|
||||
</svg>
|
||||
<span>实时监控</span>
|
||||
</div>
|
||||
|
||||
<div class="nav-item">
|
||||
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M22 12h-4l-3 9L9 3l-3 9H2"/>
|
||||
</svg>
|
||||
<span>流量分析</span>
|
||||
</div>
|
||||
|
||||
<div class="nav-item">
|
||||
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M12 2L2 7v10c0 5.5 3.8 10 10 10s10-4.5 10-10V7l-10-5z"/>
|
||||
<path d="M12 22V12"/>
|
||||
<path d="M12 12L2 7"/>
|
||||
<path d="M12 12l10-5"/>
|
||||
</svg>
|
||||
<span>防火墙规则</span>
|
||||
</div>
|
||||
|
||||
<div class="nav-item">
|
||||
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="12" cy="12" r="3"/>
|
||||
<path d="M12 1v6m0 6v6m11-7h-6m-6 0H1m20.5-5.5L17 11l-4.5-4.5M7.5 7.5L3 3m18.5 18.5L17 17l-4.5 4.5m-5 0L3 21"/>
|
||||
</svg>
|
||||
<span>网络拓扑</span>
|
||||
</div>
|
||||
|
||||
<div class="nav-item">
|
||||
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/>
|
||||
<polyline points="3.27 6.96 12 12.01 20.73 6.96"/>
|
||||
<line x1="12" y1="22.01" x2="12" y2="12.01"/>
|
||||
</svg>
|
||||
<span>进程管理</span>
|
||||
</div>
|
||||
|
||||
<div class="nav-item">
|
||||
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
|
||||
<path d="M12 8v4m0 4h.01"/>
|
||||
</svg>
|
||||
<span>威胁检测</span>
|
||||
</div>
|
||||
|
||||
<div class="nav-item">
|
||||
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<rect x="3" y="11" width="18" height="10" rx="2" ry="2"/>
|
||||
<path d="M7 11V7a5 5 0 0 1 10 0v4"/>
|
||||
</svg>
|
||||
<span>日志审计</span>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="nav-item" style="border-top: 1px solid var(--border); margin-top: auto;">
|
||||
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="12" cy="12" r="3"/>
|
||||
<path d="M12 1v6m0 6v6m11-7h-6m-6 0H1"/>
|
||||
</svg>
|
||||
<span>设置</span>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- 主内容区 -->
|
||||
<main class="main-content">
|
||||
<!-- 顶部栏 -->
|
||||
<header class="topbar">
|
||||
<div class="search-container">
|
||||
<svg class="search-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="11" cy="11" r="8"/>
|
||||
<path d="m21 21-4.35-4.35"/>
|
||||
</svg>
|
||||
<input type="text" class="search-input" placeholder="搜索进程、IP地址或域名...">
|
||||
</div>
|
||||
|
||||
<div class="topbar-actions">
|
||||
<button class="icon-button">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="icon-button">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/>
|
||||
<path d="M13.73 21a2 2 0 0 1-3.46 0"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="icon-button">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/>
|
||||
<circle cx="12" cy="7" r="4"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<div class="content-area">
|
||||
<!-- 统计卡片 -->
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<div class="stat-header">
|
||||
<div>
|
||||
<div class="stat-title">活跃连接</div>
|
||||
<div class="stat-value">247</div>
|
||||
<div class="stat-change positive">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<polyline points="23 6 13.5 15.5 8.5 10.5 1 18"/>
|
||||
<polyline points="17 6 23 6 23 12"/>
|
||||
</svg>
|
||||
<span>+12.5%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-icon" style="background: rgba(59, 130, 246, 0.1); color: var(--accent);">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M5 12.55a11 11 0 0 1 14.08 0"/>
|
||||
<path d="M1.42 9a16 16 0 0 1 21.16 0"/>
|
||||
<path d="M8.53 16.11a6 6 0 0 1 6.95 0"/>
|
||||
<line x1="12" y1="20" x2="12.01" y2="20"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card">
|
||||
<div class="stat-header">
|
||||
<div>
|
||||
<div class="stat-title">总流量</div>
|
||||
<div class="stat-value">12.4 GB</div>
|
||||
<div class="stat-change positive">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<polyline points="23 6 13.5 15.5 8.5 10.5 1 18"/>
|
||||
<polyline points="17 6 23 6 23 12"/>
|
||||
</svg>
|
||||
<span>+8.2%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-icon" style="background: rgba(139, 92, 246, 0.1); color: #8b5cf6;">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>
|
||||
<polyline points="9 22 9 12 15 12 15 22"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card">
|
||||
<div class="stat-header">
|
||||
<div>
|
||||
<div class="stat-title">阻止威胁</div>
|
||||
<div class="stat-value">89</div>
|
||||
<div class="stat-change negative">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<polyline points="23 18 13.5 8.5 8.5 13.5 1 6"/>
|
||||
<polyline points="17 18 23 18 23 12"/>
|
||||
</svg>
|
||||
<span>-5.3%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-icon" style="background: rgba(239, 68, 68, 0.1); color: var(--danger);">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
|
||||
<line x1="12" y1="8" x2="12" y2="12"/>
|
||||
<line x1="12" y1="16" x2="12.01" y2="16"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card">
|
||||
<div class="stat-header">
|
||||
<div>
|
||||
<div class="stat-title">活跃进程</div>
|
||||
<div class="stat-value">42</div>
|
||||
<div class="stat-change positive">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<polyline points="23 6 13.5 15.5 8.5 10.5 1 18"/>
|
||||
<polyline points="17 6 23 6 23 12"/>
|
||||
</svg>
|
||||
<span>+3.1%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-icon" style="background: rgba(16, 185, 129, 0.1); color: var(--success);">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<rect x="4" y="4" width="16" height="16" rx="2" ry="2"/>
|
||||
<rect x="9" y="9" width="6" height="6"/>
|
||||
<line x1="9" y1="1" x2="9" y2="4"/>
|
||||
<line x1="15" y1="1" x2="15" y2="4"/>
|
||||
<line x1="9" y1="20" x2="9" y2="23"/>
|
||||
<line x1="15" y1="20" x2="15" y2="23"/>
|
||||
<line x1="20" y1="9" x2="23" y2="9"/>
|
||||
<line x1="20" y1="14" x2="23" y2="14"/>
|
||||
<line x1="1" y1="9" x2="4" y2="9"/>
|
||||
<line x1="1" y1="14" x2="4" y2="14"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 实时连接表格 -->
|
||||
<div class="connections-section">
|
||||
<div class="section-header">
|
||||
<h2 class="section-title">
|
||||
实时连接
|
||||
<span class="live-indicator" style="display: inline-block; margin-left: 8px;"></span>
|
||||
</h2>
|
||||
<div class="filter-tabs">
|
||||
<div class="filter-tab active">全部</div>
|
||||
<div class="filter-tab">活跃</div>
|
||||
<div class="filter-tab">阻止</div>
|
||||
<div class="filter-tab">监控</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="connections-table">
|
||||
<thead class="table-header">
|
||||
<tr>
|
||||
<th>进程</th>
|
||||
<th>远程地址</th>
|
||||
<th>端口</th>
|
||||
<th>协议</th>
|
||||
<th>状态</th>
|
||||
<th>带宽</th>
|
||||
<th>总流量</th>
|
||||
<th>持续时间</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="table-row">
|
||||
<td>
|
||||
<div class="process-info">
|
||||
<div class="process-icon">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<div class="process-name">Chrome.exe</div>
|
||||
<div class="process-path">C:\Program Files\Google\Chrome</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>142.250.185.46 (google.com)</td>
|
||||
<td>443</td>
|
||||
<td>HTTPS</td>
|
||||
<td><span class="connection-status status-active">活跃</span></td>
|
||||
<td>
|
||||
<div class="bandwidth-meter">
|
||||
<div class="bandwidth-bar">
|
||||
<div class="bandwidth-fill" style="width: 65%"></div>
|
||||
</div>
|
||||
<span>2.4 MB/s</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>156 MB</td>
|
||||
<td>12m 34s</td>
|
||||
</tr>
|
||||
|
||||
<tr class="table-row">
|
||||
<td>
|
||||
<div class="process-info">
|
||||
<div class="process-icon" style="background: rgba(139, 92, 246, 0.1); color: #8b5cf6;">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M9 11H7v2h2v-2zm4 0h-2v2h2v-2zm4 0h-2v2h2v-2zm2-7h-1V2h-2v2H8V2H6v2H5c-1.11 0-1.99.9-1.99 2L3 20c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 16H5V9h14v11z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<div class="process-name">Discord.exe</div>
|
||||
<div class="process-path">C:\Users\AppData\Local\Discord</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>162.159.135.234 (discord.com)</td>
|
||||
<td>443</td>
|
||||
<td>WSS</td>
|
||||
<td><span class="connection-status status-active">活跃</span></td>
|
||||
<td>
|
||||
<div class="bandwidth-meter">
|
||||
<div class="bandwidth-bar">
|
||||
<div class="bandwidth-fill" style="width: 20%"></div>
|
||||
</div>
|
||||
<span>128 KB/s</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>42 MB</td>
|
||||
<td>1h 23m</td>
|
||||
</tr>
|
||||
|
||||
<tr class="table-row">
|
||||
<td>
|
||||
<div class="process-info">
|
||||
<div class="process-icon" style="background: rgba(239, 68, 68, 0.1); color: var(--danger);">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<div class="process-name">Unknown.exe</div>
|
||||
<div class="process-path">C:\Temp\suspicious</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>185.220.101.34 (未知)</td>
|
||||
<td>8080</td>
|
||||
<td>TCP</td>
|
||||
<td><span class="connection-status status-blocked">已阻止</span></td>
|
||||
<td>
|
||||
<div class="bandwidth-meter">
|
||||
<div class="bandwidth-bar">
|
||||
<div class="bandwidth-fill" style="width: 0%"></div>
|
||||
</div>
|
||||
<span>0 KB/s</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>0 B</td>
|
||||
<td>-</td>
|
||||
</tr>
|
||||
|
||||
<tr class="table-row">
|
||||
<td>
|
||||
<div class="process-info">
|
||||
<div class="process-icon" style="background: rgba(16, 185, 129, 0.1); color: var(--success);">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<div class="process-name">OneDrive.exe</div>
|
||||
<div class="process-path">C:\Program Files\Microsoft\OneDrive</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>52.109.12.18 (microsoft.com)</td>
|
||||
<td>443</td>
|
||||
<td>HTTPS</td>
|
||||
<td><span class="connection-status status-active">活跃</span></td>
|
||||
<td>
|
||||
<div class="bandwidth-meter">
|
||||
<div class="bandwidth-bar">
|
||||
<div class="bandwidth-fill" style="width: 45%"></div>
|
||||
</div>
|
||||
<span>1.2 MB/s</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>89 MB</td>
|
||||
<td>5m 12s</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 浮动操作面板 -->
|
||||
<div class="floating-panel visible">
|
||||
<span style="color: var(--text-secondary); font-size: 14px;">已选择 2 个连接</span>
|
||||
<button class="action-button secondary">添加规则</button>
|
||||
<button class="action-button primary">阻止连接</button>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 模拟实时数据更新
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 统计数据动画
|
||||
const statValues = document.querySelectorAll('.stat-value');
|
||||
statValues.forEach(stat => {
|
||||
const finalValue = stat.textContent;
|
||||
const isNumeric = /^\d+$/.test(finalValue);
|
||||
|
||||
if (isNumeric) {
|
||||
let currentValue = 0;
|
||||
const targetValue = parseInt(finalValue);
|
||||
const increment = targetValue / 50;
|
||||
|
||||
const counter = setInterval(() => {
|
||||
currentValue += increment;
|
||||
if (currentValue >= targetValue) {
|
||||
currentValue = targetValue;
|
||||
clearInterval(counter);
|
||||
}
|
||||
stat.textContent = Math.floor(currentValue);
|
||||
}, 20);
|
||||
}
|
||||
});
|
||||
|
||||
// 带宽动画
|
||||
const bandwidthBars = document.querySelectorAll('.bandwidth-fill');
|
||||
setInterval(() => {
|
||||
bandwidthBars.forEach(bar => {
|
||||
if (bar.parentElement.parentElement.parentElement.parentElement.querySelector('.status-active')) {
|
||||
const currentWidth = parseInt(bar.style.width);
|
||||
const variation = (Math.random() - 0.5) * 20;
|
||||
const newWidth = Math.max(0, Math.min(100, currentWidth + variation));
|
||||
bar.style.width = newWidth + '%';
|
||||
}
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
// 筛选标签切换
|
||||
const filterTabs = document.querySelectorAll('.filter-tab');
|
||||
filterTabs.forEach(tab => {
|
||||
tab.addEventListener('click', function() {
|
||||
filterTabs.forEach(t => t.classList.remove('active'));
|
||||
this.classList.add('active');
|
||||
});
|
||||
});
|
||||
|
||||
// 表格行选择
|
||||
const tableRows = document.querySelectorAll('.table-row');
|
||||
let selectedCount = 0;
|
||||
const floatingPanel = document.querySelector('.floating-panel');
|
||||
|
||||
tableRows.forEach(row => {
|
||||
row.addEventListener('click', function() {
|
||||
this.classList.toggle('selected');
|
||||
selectedCount = document.querySelectorAll('.table-row.selected').length;
|
||||
|
||||
if (selectedCount > 0) {
|
||||
floatingPanel.classList.add('visible');
|
||||
floatingPanel.querySelector('span').textContent = `已选择 ${selectedCount} 个连接`;
|
||||
} else {
|
||||
floatingPanel.classList.remove('visible');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 侧边栏折叠
|
||||
let sidebarCollapsed = false;
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.ctrlKey && e.key === 'b') {
|
||||
e.preventDefault();
|
||||
const sidebar = document.querySelector('.sidebar');
|
||||
sidebar.classList.toggle('collapsed');
|
||||
}
|
||||
});
|
||||
|
||||
// 搜索功能
|
||||
const searchInput = document.querySelector('.search-input');
|
||||
searchInput.addEventListener('input', function(e) {
|
||||
const searchTerm = e.target.value.toLowerCase();
|
||||
tableRows.forEach(row => {
|
||||
const text = row.textContent.toLowerCase();
|
||||
row.style.display = text.includes(searchTerm) ? '' : 'none';
|
||||
});
|
||||
});
|
||||
|
||||
// 模拟新连接通知
|
||||
setInterval(() => {
|
||||
const statValue = document.querySelector('.stat-value');
|
||||
const currentValue = parseInt(statValue.textContent);
|
||||
if (Math.random() > 0.7) {
|
||||
statValue.textContent = currentValue + 1;
|
||||
|
||||
// 添加脉冲效果
|
||||
statValue.style.animation = 'pulse 0.5s';
|
||||
setTimeout(() => {
|
||||
statValue.style.animation = '';
|
||||
}, 500);
|
||||
}
|
||||
}, 5000);
|
||||
});
|
||||
|
||||
// 添加选中行的样式
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
.table-row.selected {
|
||||
background: rgba(59, 130, 246, 0.1);
|
||||
border-left: 3px solid var(--accent);
|
||||
}
|
||||
|
||||
.table-row.selected td:first-child {
|
||||
padding-left: 13px;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,1284 @@
|
||||
</svg>
|
||||
</div>
|
||||
<div class="overview-value">156</div>
|
||||
<div class="mini-chart">
|
||||
<div class="chart-line" style="background: linear-gradient(to top, rgba(255, 51, 102, 0.2), transparent); border-color: var(--danger); clip-path: polygon(0 100%, 10% 95%, 20% 98%, 30% 90%, 40% 92%, 50% 85%, 60% 88%, 70% 82%, 80% 85%, 90% 80%, 100% 75%, 100% 100%);"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 实时流量图表 -->
|
||||
<div class="chart-container">
|
||||
<div class="chart-header">
|
||||
<h3 class="chart-title">网络流量实时监控</h3>
|
||||
<div class="chart-controls">
|
||||
<button class="chart-btn active">1分钟</button>
|
||||
<button class="chart-btn">5分钟</button>
|
||||
<button class="chart-btn">30分钟</button>
|
||||
</div>
|
||||
</div>
|
||||
<div style="position: relative; height: 220px; background: var(--bg-tertiary); border-radius: 8px; overflow: hidden;">
|
||||
<!-- 模拟的实时图表 -->
|
||||
<svg width="100%" height="100%" style="position: absolute;">
|
||||
<defs>
|
||||
<linearGradient id="downloadGradient" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:rgba(0,212,255,0.4);stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:rgba(0,212,255,0);stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient id="uploadGradient" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:rgba(0,255,136,0.4);stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:rgba(0,255,136,0);stop-opacity:1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path d="M 0 150 Q 50 140, 100 145 T 200 135 T 300 140 T 400 130 T 500 135 T 600 125 T 700 130 T 800 120 L 800 220 L 0 220 Z" fill="url(#downloadGradient)" />
|
||||
<path d="M 0 150 Q 50 140, 100 145 T 200 135 T 300 140 T 400 130 T 500 135 T 600 125 T 700 130 T 800 120" fill="none" stroke="#00d4ff" stroke-width="2" />
|
||||
|
||||
<path d="M 0 180 Q 50 175, 100 178 T 200 172 T 300 175 T 400 170 T 500 172 T 600 168 T 700 170 T 800 165 L 800 220 L 0 220 Z" fill="url(#uploadGradient)" />
|
||||
<path d="M 0 180 Q 50 175, 100 178 T 200 172 T 300 175 T 400 170 T 500 172 T 600 168 T 700 170 T 800 165" fill="none" stroke="#00ff88" stroke-width="2" />
|
||||
</svg>
|
||||
<div style="position: absolute; bottom: 10px; right: 20px; display: flex; gap: 20px; font-size: 12px;">
|
||||
<span style="display: flex; align-items: center; gap: 8px;">
|
||||
<span style="width: 12px; height: 2px; background: #00d4ff;"></span>
|
||||
下载
|
||||
</span>
|
||||
<span style="display: flex; align-items: center; gap: 8px;">
|
||||
<span style="width: 12px; height: 2px; background: #00ff88;"></span>
|
||||
上传
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 搜索栏 -->
|
||||
<div class="search-bar">
|
||||
<svg class="search-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="11" cy="11" r="8"></circle>
|
||||
<path d="m21 21-4.35-4.35"></path>
|
||||
</svg>
|
||||
<input type="text" class="search-input" placeholder="搜索进程、IP地址或域名...">
|
||||
</div>
|
||||
|
||||
<!-- 实时连接列表 -->
|
||||
<div class="connections-grid">
|
||||
<div class="connection-card">
|
||||
<div class="process-info">
|
||||
<div class="process-icon">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="12" cy="12" r="10"></circle>
|
||||
<path d="M8 12h8"></path>
|
||||
<path d="M12 8v8"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="process-details">
|
||||
<h4>Chrome.exe <span class="tag system">系统进程</span></h4>
|
||||
<p>C:\Program Files\Google\Chrome\Application</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="connection-details">
|
||||
<div class="connection-path">
|
||||
<span>本地:51234</span>
|
||||
<span class="connection-arrow">→</span>
|
||||
<span class="ip-address">142.250.185.46:443</span>
|
||||
</div>
|
||||
<div class="domain-name">googleapis.com</div>
|
||||
</div>
|
||||
<div class="traffic-info">
|
||||
<div class="speed-value">↓ 458 KB/s</div>
|
||||
<div class="total-traffic">总计: 234 MB</div>
|
||||
</div>
|
||||
<div class="action-buttons">
|
||||
<button class="action-btn" title="查看详情">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
|
||||
<circle cx="12" cy="12" r="3"></circle>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="action-btn block" title="阻止连接">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="12" cy="12" r="10"></circle>
|
||||
<path d="m4.93 4.93 14.14 14.14"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="connection-card">
|
||||
<div class="process-info">
|
||||
<div class="process-icon" style="background: var(--accent-dim);">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z"></path>
|
||||
<line x1="4" y1="22" x2="4" y2="15"></line>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="process-details">
|
||||
<h4>MicrosoftEdge.exe <span class="tag trusted">已信任</span></h4>
|
||||
<p>C:\Program Files (x86)\Microsoft\Edge\Application</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="connection-details">
|
||||
<div class="connection-path">
|
||||
<span>本地:52810</span>
|
||||
<span class="connection-arrow">→</span>
|
||||
<span class="ip-address">13.107.42.14:443</span>
|
||||
</div>
|
||||
<div class="domain-name">outlook.live.com</div>
|
||||
</div>
|
||||
<div class="traffic-info">
|
||||
<div class="speed-value">↓ 24 KB/s</div>
|
||||
<div class="total-traffic">总计: 12.4 MB</div>
|
||||
</div>
|
||||
<div class="action-buttons">
|
||||
<button class="action-btn" title="查看详情">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
|
||||
<circle cx="12" cy="12" r="3"></circle>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="action-btn block" title="阻止连接">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="12" cy="12" r="10"></circle>
|
||||
<path d="m4.93 4.93 14.14 14.14"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="connection-card">
|
||||
<div class="process-info">
|
||||
<div class="process-icon" style="background: rgba(255, 170, 0, 0.2); color: var(--warning);">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path>
|
||||
<line x1="12" y1="9" x2="12" y2="13"></line>
|
||||
<line x1="12" y1="17" x2="12.01" y2="17"></line>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="process-details">
|
||||
<h4>Unknown.exe <span class="tag warning">未知进程</span></h4>
|
||||
<p>C:\Users\Admin\AppData\Local\Temp</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="connection-details">
|
||||
<div class="connection-path">
|
||||
<span>本地:49823</span>
|
||||
<span class="connection-arrow">→</span>
|
||||
<span class="ip-address">185.234.218.50:8080</span>
|
||||
</div>
|
||||
<div class="domain-name">未解析</div>
|
||||
</div>
|
||||
<div class="traffic-info">
|
||||
<div class="speed-value" style="color: var(--warning);">↑ 1.2 MB/s</div>
|
||||
<div class="total-traffic">总计: 458 MB</div>
|
||||
</div>
|
||||
<div class="action-buttons">
|
||||
<button class="action-btn" title="查看详情">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
|
||||
<circle cx="12" cy="12" r="3"></circle>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="action-btn block" title="阻止连接" style="background: var(--danger); color: white;">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="12" cy="12" r="10"></circle>
|
||||
<path d="m4.93 4.93 14.14 14.14"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 进程管理视图(隐藏) -->
|
||||
<div id="process-view" class="view-content" style="display: none;">
|
||||
<div class="process-list">
|
||||
<div class="list-header">
|
||||
<span>进程名称</span>
|
||||
<span>连接数</span>
|
||||
<span>下载速度</span>
|
||||
<span>上传速度</span>
|
||||
<span>操作</span>
|
||||
</div>
|
||||
<div class="list-item">
|
||||
<div class="process-info">
|
||||
<div class="process-icon">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="12" cy="12" r="10"></circle>
|
||||
<path d="M8 12h8"></path>
|
||||
<path d="M12 8v8"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<h4>Chrome.exe</h4>
|
||||
<p style="font-size: 12px; color: var(--text-secondary);">PID: 12456</p>
|
||||
</div>
|
||||
</div>
|
||||
<span>23</span>
|
||||
<span style="color: var(--success);">458 KB/s</span>
|
||||
<span>24 KB/s</span>
|
||||
<div class="action-buttons">
|
||||
<button class="action-btn">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
|
||||
<polyline points="14 2 14 8 20 8"></polyline>
|
||||
<line x1="16" y1="13" x2="8" y2="13"></line>
|
||||
<line x1="16" y1="17" x2="8" y2="17"></line>
|
||||
<polyline points="10 9 9 9 8 9"></polyline>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="action-btn block">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="12" cy="12" r="10"></circle>
|
||||
<path d="m4.93 4.93 14.14 14.14"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 网络拓扑视图(隐藏) -->
|
||||
<div id="topology-view" class="view-content" style="display: none;">
|
||||
<div class="topology-container">
|
||||
<div class="topology-node node-central">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<rect x="2" y="7" width="20" height="14" rx="2" ry="2"></rect>
|
||||
<path d="M16 21V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16"></path>
|
||||
</svg>
|
||||
<span style="font-size: 12px; margin-top: 4px;">本机</span>
|
||||
</div>
|
||||
<div class="topology-node" style="left: 20%; top: 20%;">
|
||||
<span style="font-size: 14px; font-weight: 500;">Google</span>
|
||||
<span style="font-size: 11px; color: var(--text-secondary);">8 连接</span>
|
||||
</div>
|
||||
<div class="topology-node" style="left: 70%; top: 25%;">
|
||||
<span style="font-size: 14px; font-weight: 500;">Microsoft</span>
|
||||
<span style="font-size: 11px; color: var(--text-secondary);">5 连接</span>
|
||||
</div>
|
||||
<div class="topology-node" style="left: 15%; top: 70%;">
|
||||
<span style="font-size: 14px; font-weight: 500;">AWS</span>
|
||||
<span style="font-size: 11px; color: var(--text-secondary);">3 连接</span>
|
||||
</div>
|
||||
<div class="topology-node" style="left: 75%; top: 75%;">
|
||||
<span style="font-size: 14px; font-weight: 500;">Cloudflare</span>
|
||||
<span style="font-size: 11px; color: var(--text-secondary);">12 连接</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 设置视图(隐藏) -->
|
||||
<div id="settings-view" class="view-content" style="display: none;">
|
||||
<div class="settings-panel">
|
||||
<div class="settings-section">
|
||||
<h3 class="settings-title">常规设置</h3>
|
||||
<div class="setting-item">
|
||||
<div class="setting-label">
|
||||
<h4>开机自启动</h4>
|
||||
<p>Windows 启动时自动运行 NetGuard</p>
|
||||
</div>
|
||||
<div class="toggle-switch active">
|
||||
<div class="toggle-slider"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="setting-label">
|
||||
<h4>最小化到托盘</h4>
|
||||
<p>关闭窗口时最小化到系统托盘</p>
|
||||
</div>
|
||||
<div class="toggle-switch">
|
||||
<div class="toggle-slider"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="setting-label">
|
||||
<h4>自动更新</h4>
|
||||
<p>自动下载并安装最新版本</p>
|
||||
</div>
|
||||
<div class="toggle-switch active">
|
||||
<div class="toggle-slider"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-section">
|
||||
<h3 class="settings-title">监控设置</h3>
|
||||
<div class="setting-item">
|
||||
<div class="setting-label">
|
||||
<h4>异常行为检测</h4>
|
||||
<p>检测并警告可疑的网络活动</p>
|
||||
</div>
|
||||
<div class="toggle-switch active">
|
||||
<div class="toggle-slider"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="setting-label">
|
||||
<h4>DNS 解析监控</h4>
|
||||
<p>记录并显示域名解析信息</p>
|
||||
</div>
|
||||
<div class="toggle-switch active">
|
||||
<div class="toggle-slider"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="setting-label">
|
||||
<h4>流量统计</h4>
|
||||
<p>保存历史流量数据用于分析</p>
|
||||
</div>
|
||||
<div class="toggle-switch active">
|
||||
<div class="toggle-slider"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-section">
|
||||
<h3 class="settings-title">界面设置</h3>
|
||||
<div class="setting-item">
|
||||
<div class="setting-label">
|
||||
<h4>暗色主题</h4>
|
||||
<p>使用暗色界面主题</p>
|
||||
</div>
|
||||
<div class="toggle-switch active">
|
||||
<div class="toggle-slider"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="setting-label">
|
||||
<h4>动画效果</h4>
|
||||
<p>启用界面过渡动画</p>
|
||||
</div>
|
||||
<div class="toggle-switch active">
|
||||
<div class="toggle-slider"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- 通知示例 -->
|
||||
<div class="notification" style="display: none;">
|
||||
<div class="notification-header">
|
||||
<div class="notification-icon warning">!</div>
|
||||
<h4 style="font-size: 14px;">异常行为检测</h4>
|
||||
</div>
|
||||
<p style="font-size: 13px; color: var(--text-secondary);">Unknown.exe 在过去1分钟内连接了 47 个不同的IP地址</p>
|
||||
<div style="display: flex; gap: 8px; margin-top: 12px;">
|
||||
<button class="action-btn" style="flex: 1; height: 32px;">查看详情</button>
|
||||
<button class="action-btn block" style="flex: 1; height: 32px;">立即阻止</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 导航切换
|
||||
document.querySelectorAll('.nav-item').forEach(item => {
|
||||
item.addEventListener('click', function() {
|
||||
// 移除所有活跃状态
|
||||
document.querySelectorAll('.nav-item').forEach(nav => nav.classList.remove('active'));
|
||||
this.classList.add('active');
|
||||
|
||||
// 隐藏所有视图
|
||||
document.querySelectorAll('.view-content').forEach(view => view.style.display = 'none');
|
||||
|
||||
// 显示对应视图
|
||||
const views = ['realtime-view', 'process-view', 'settings-view', 'process-view', 'topology-view', 'settings-view'];
|
||||
const index = Array.from(document.querySelectorAll('.nav-item')).indexOf(this);
|
||||
const targetView = document.getElementById(views[index]);
|
||||
if (targetView) {
|
||||
targetView.style.display = 'block';
|
||||
targetView.classList.add('fade-in');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 开关切换
|
||||
document.querySelectorAll('.toggle-switch').forEach(toggle => {
|
||||
toggle.addEventListener('click', function() {
|
||||
this.classList.toggle('active');
|
||||
});
|
||||
});
|
||||
|
||||
// 模拟实时数据更新
|
||||
function updateRealtimeData() {
|
||||
// 更新速度显示
|
||||
const downloadSpeed = document.querySelector('.status-item:nth-child(2) .status-value');
|
||||
const uploadSpeed = document.querySelector('.status-item:nth-child(3) .status-value');
|
||||
|
||||
if (downloadSpeed) {
|
||||
const newDownload = (Math.random() * 5).toFixed(1);
|
||||
downloadSpeed.textContent = `↓ ${newDownload} MB/s`;
|
||||
}
|
||||
|
||||
if (uploadSpeed) {
|
||||
const newUpload = (Math.random() * 500).toFixed(0);
|
||||
uploadSpeed.textContent = `↑ ${newUpload} KB/s`;
|
||||
}
|
||||
|
||||
// 更新连接数
|
||||
const connections = document.querySelector('.status-item:nth-child(4) .status-value');
|
||||
if (connections) {
|
||||
connections.textContent = Math.floor(Math.random() * 20 + 40);
|
||||
}
|
||||
}
|
||||
|
||||
// 每秒更新数据
|
||||
setInterval(updateRealtimeData, 1000);
|
||||
|
||||
// 模拟通知
|
||||
setTimeout(() => {
|
||||
const notification = document.querySelector('.notification');
|
||||
if (notification) {
|
||||
notification.style.display = 'block';
|
||||
setTimeout(() => {
|
||||
notification.style.display = 'none';
|
||||
}, 5000);
|
||||
}
|
||||
}, 3000);
|
||||
|
||||
// 搜索功能
|
||||
const searchInput = document.querySelector('.search-input');
|
||||
if (searchInput) {
|
||||
searchInput.addEventListener('input', function(e) {
|
||||
const searchTerm = e.target.value.toLowerCase();
|
||||
document.querySelectorAll('.connection-card').forEach(card => {
|
||||
const text = card.textContent.toLowerCase();
|
||||
card.style.display = text.includes(searchTerm) ? 'grid' : 'none';
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 添加连接卡片hover效果的动态阴影
|
||||
document.querySelectorAll('.connection-card').forEach(card => {
|
||||
card.addEventListener('mousemove', function(e) {
|
||||
const rect = this.getBoundingClientRect();
|
||||
const x = e.clientX - rect.left;
|
||||
const y = e.clientY - rect.top;
|
||||
|
||||
this.style.background = `radial-gradient(circle at ${x}px ${y}px, rgba(0, 212, 255, 0.05), var(--bg-secondary))`;
|
||||
});
|
||||
|
||||
card.addEventListener('mouseleave', function() {
|
||||
this.style.background = 'var(--bg-secondary)';
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html><!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>NetGuard - 现代网络监控工具</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:root {
|
||||
--bg-primary: #0a0a0a;
|
||||
--bg-secondary: #141414;
|
||||
--bg-tertiary: #1a1a1a;
|
||||
--bg-hover: #252525;
|
||||
--text-primary: #ffffff;
|
||||
--text-secondary: #a0a0a0;
|
||||
--text-tertiary: #707070;
|
||||
--accent: #00d4ff;
|
||||
--accent-dim: #0099cc;
|
||||
--success: #00ff88;
|
||||
--warning: #ffaa00;
|
||||
--danger: #ff3366;
|
||||
--border: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Microsoft YaHei', sans-serif;
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* 主布局 */
|
||||
.app-container {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 顶部状态栏 */
|
||||
.top-bar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 48px;
|
||||
background: var(--bg-secondary);
|
||||
border-bottom: 1px solid var(--border);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 20px;
|
||||
z-index: 1000;
|
||||
backdrop-filter: blur(10px);
|
||||
background: rgba(20, 20, 20, 0.9);
|
||||
}
|
||||
|
||||
.status-indicators {
|
||||
display: flex;
|
||||
gap: 30px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.status-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.status-icon {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
.status-icon.active {
|
||||
background: var(--success);
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.5; }
|
||||
}
|
||||
|
||||
.status-value {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-label {
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
/* 侧边栏 */
|
||||
.sidebar {
|
||||
width: 200px;
|
||||
background: var(--bg-secondary);
|
||||
border-right: 1px solid var(--border);
|
||||
padding-top: 48px;
|
||||
transition: width 0.3s ease;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.sidebar.collapsed {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 20px;
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.nav-item:hover {
|
||||
background: var(--bg-hover);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.nav-item.active {
|
||||
background: var(--bg-tertiary);
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.nav-item.active::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 3px;
|
||||
background: var(--accent);
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-right: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.sidebar.collapsed .nav-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 主内容区 */
|
||||
.main-content {
|
||||
flex: 1;
|
||||
padding: 68px 20px 20px;
|
||||
overflow-y: auto;
|
||||
background: var(--bg-primary);
|
||||
}
|
||||
|
||||
/* 实时连接视图 */
|
||||
.connections-grid {
|
||||
display: grid;
|
||||
gap: 15px;
|
||||
animation: fadeIn 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(10px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.connection-card {
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
border: 1px solid var(--border);
|
||||
transition: all 0.2s ease;
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr auto auto;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.connection-card:hover {
|
||||
border-color: var(--accent-dim);
|
||||
transform: translateX(2px);
|
||||
box-shadow: 0 4px 20px rgba(0, 212, 255, 0.1);
|
||||
}
|
||||
|
||||
.process-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.process-icon {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
background: var(--bg-tertiary);
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.process-details h4 {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.process-details p {
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.connection-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.connection-path {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.connection-arrow {
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.ip-address {
|
||||
color: var(--accent);
|
||||
font-family: 'Consolas', monospace;
|
||||
}
|
||||
|
||||
.domain-name {
|
||||
color: var(--text-secondary);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.traffic-info {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.speed-value {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: var(--success);
|
||||
}
|
||||
|
||||
.total-traffic {
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--border);
|
||||
background: transparent;
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.action-btn:hover {
|
||||
background: var(--bg-hover);
|
||||
color: var(--text-primary);
|
||||
border-color: var(--accent-dim);
|
||||
}
|
||||
|
||||
.action-btn.block {
|
||||
color: var(--danger);
|
||||
}
|
||||
|
||||
/* 流量概览卡片 */
|
||||
.overview-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.overview-card {
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 16px;
|
||||
padding: 20px;
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.overview-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.overview-title {
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.overview-value {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.mini-chart {
|
||||
height: 60px;
|
||||
background: var(--bg-tertiary);
|
||||
border-radius: 8px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.chart-line {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 100%;
|
||||
background: linear-gradient(to top, rgba(0, 212, 255, 0.2), transparent);
|
||||
border-top: 2px solid var(--accent);
|
||||
}
|
||||
|
||||
/* 实时图表区域 */
|
||||
.chart-container {
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 16px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
border: 1px solid var(--border);
|
||||
height: 300px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.chart-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.chart-title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.chart-controls {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.chart-btn {
|
||||
padding: 6px 12px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--border);
|
||||
background: transparent;
|
||||
color: var(--text-secondary);
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.chart-btn.active {
|
||||
background: var(--accent);
|
||||
color: var(--bg-primary);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
/* 进程列表 */
|
||||
.process-list {
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.list-header {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 1fr 1fr 1fr 100px;
|
||||
padding: 12px 20px;
|
||||
background: var(--bg-tertiary);
|
||||
border-bottom: 1px solid var(--border);
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.list-item {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 1fr 1fr 1fr 100px;
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
transition: background 0.2s ease;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.list-item:hover {
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
/* 拓扑图容器 */
|
||||
.topology-container {
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 16px;
|
||||
padding: 20px;
|
||||
height: 500px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.topology-node {
|
||||
position: absolute;
|
||||
width: 120px;
|
||||
height: 60px;
|
||||
background: var(--bg-tertiary);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.topology-node:hover {
|
||||
transform: scale(1.05);
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 4px 20px rgba(0, 212, 255, 0.3);
|
||||
}
|
||||
|
||||
.node-central {
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background: var(--accent-dim);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
/* 设置面板 */
|
||||
.settings-panel {
|
||||
display: grid;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.settings-section {
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 16px;
|
||||
padding: 24px;
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.settings-title {
|
||||
font-size: 18px;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.setting-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px 0;
|
||||
}
|
||||
|
||||
.setting-label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.setting-label h4 {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.setting-label p {
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
/* 开关组件 */
|
||||
.toggle-switch {
|
||||
position: relative;
|
||||
width: 48px;
|
||||
height: 24px;
|
||||
background: var(--bg-tertiary);
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
transition: background 0.3s ease;
|
||||
}
|
||||
|
||||
.toggle-switch.active {
|
||||
background: var(--accent);
|
||||
}
|
||||
|
||||
.toggle-slider {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: var(--text-primary);
|
||||
border-radius: 50%;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.toggle-switch.active .toggle-slider {
|
||||
transform: translateX(24px);
|
||||
}
|
||||
|
||||
/* 搜索栏 */
|
||||
.search-bar {
|
||||
position: relative;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 100%;
|
||||
padding: 12px 20px 12px 48px;
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
color: var(--text-primary);
|
||||
font-size: 14px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.search-input:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 0 3px rgba(0, 212, 255, 0.1);
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 1200px) {
|
||||
.overview-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.sidebar {
|
||||
position: fixed;
|
||||
left: -200px;
|
||||
height: 100vh;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.sidebar.open {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.overview-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.connection-card {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 动画效果 */
|
||||
.fade-in {
|
||||
animation: fadeIn 0.3s ease;
|
||||
}
|
||||
|
||||
.slide-in {
|
||||
animation: slideIn 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from { transform: translateX(-10px); opacity: 0; }
|
||||
to { transform: translateX(0); opacity: 1; }
|
||||
}
|
||||
|
||||
/* 提示标签 */
|
||||
.tag {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.tag.trusted {
|
||||
background: rgba(0, 255, 136, 0.2);
|
||||
color: var(--success);
|
||||
}
|
||||
|
||||
.tag.system {
|
||||
background: rgba(0, 212, 255, 0.2);
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.tag.warning {
|
||||
background: rgba(255, 170, 0, 0.2);
|
||||
color: var(--warning);
|
||||
}
|
||||
|
||||
/* 通知提示 */
|
||||
.notification {
|
||||
position: fixed;
|
||||
top: 68px;
|
||||
right: 20px;
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
padding: 16px 20px;
|
||||
min-width: 300px;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
|
||||
animation: slideInRight 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes slideInRight {
|
||||
from { transform: translateX(100%); }
|
||||
to { transform: translateX(0); }
|
||||
}
|
||||
|
||||
.notification-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.notification-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.notification-icon.warning {
|
||||
background: var(--warning);
|
||||
color: var(--bg-primary);
|
||||
}
|
||||
|
||||
/* 滚动条样式 */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: var(--bg-secondary);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--bg-hover);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--text-tertiary);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-container">
|
||||
<!-- 顶部状态栏 -->
|
||||
<div class="top-bar">
|
||||
<div class="status-indicators">
|
||||
<div class="status-item">
|
||||
<div class="status-icon active"></div>
|
||||
<span class="status-value">防火墙已启用</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-value">↓ 2.4 MB/s</span>
|
||||
<span class="status-label">下载</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-value">↑ 156 KB/s</span>
|
||||
<span class="status-label">上传</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-value">47</span>
|
||||
<span class="status-label">活跃连接</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="top-actions">
|
||||
<button class="action-btn">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="11" cy="11" r="8"></circle>
|
||||
<path d="m21 21-4.35-4.35"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="action-btn">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M12 2v20M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 侧边栏 -->
|
||||
<aside class="sidebar">
|
||||
<nav>
|
||||
<div class="nav-item active">
|
||||
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"></path>
|
||||
</svg>
|
||||
<span class="nav-text">实时监控</span>
|
||||
</div>
|
||||
<div class="nav-item">
|
||||
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M3 12h18m-9-9v18"></path>
|
||||
</svg>
|
||||
<span class="nav-text">进程管理</span>
|
||||
</div>
|
||||
<div class="nav-item">
|
||||
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
|
||||
</svg>
|
||||
<span class="nav-text">防火墙规则</span>
|
||||
</div>
|
||||
<div class="nav-item">
|
||||
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="12" cy="12" r="10"></circle>
|
||||
<polyline points="12 6 12 12 16 14"></polyline>
|
||||
</svg>
|
||||
<span class="nav-text">流量分析</span>
|
||||
</div>
|
||||
<div class="nav-item">
|
||||
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"></path>
|
||||
</svg>
|
||||
<span class="nav-text">网络拓扑</span>
|
||||
</div>
|
||||
<div class="nav-item">
|
||||
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"></path>
|
||||
<circle cx="12" cy="12" r="3"></circle>
|
||||
</svg>
|
||||
<span class="nav-text">设置</span>
|
||||
</div>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<!-- 主内容区 -->
|
||||
<main class="main-content">
|
||||
<!-- 实时监控视图 -->
|
||||
<div id="realtime-view" class="view-content">
|
||||
<!-- 概览卡片 -->
|
||||
<div class="overview-grid">
|
||||
<div class="overview-card">
|
||||
<div class="overview-header">
|
||||
<span class="overview-title">今日总流量</span>
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="color: var(--accent)">
|
||||
<path d="M3 3v18h18"></path>
|
||||
<path d="m19 9-5 5-4-4-3 3"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="overview-value">12.4 GB</div>
|
||||
<div class="mini-chart">
|
||||
<div class="chart-line" style="clip-path: polygon(0 100%, 10% 80%, 20% 85%, 30% 70%, 40% 60%, 50% 65%, 60% 50%, 70% 40%, 80% 45%, 90% 30%, 100% 20%, 100% 100%);"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overview-card">
|
||||
<div class="overview-header">
|
||||
<span class="overview-title">活跃进程</span>
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="color: var(--success)">
|
||||
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
|
||||
<line x1="9" y1="9" x2="15" y2="15"></line>
|
||||
<line x1="15" y1="9" x2="9" y2="15"></line>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="overview-value">23</div>
|
||||
<div class="mini-chart">
|
||||
<div class="chart-line" style="background: linear-gradient(to top, rgba(0, 255, 136, 0.2), transparent); border-color: var(--success); clip-path: polygon(0 100%, 10% 90%, 20% 85%, 30% 88%, 40% 85%, 50% 82%, 60% 80%, 70% 78%, 80% 75%, 90% 70%, 100% 65%, 100% 100%);"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overview-card">
|
||||
<div class="overview-header">
|
||||
<span class="overview-title">阻止的连接</span>
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="color: var(--danger)">
|
||||
<circle cx="12" cy="12" r="10"></circle>
|
||||
<line x1="4.93" y1="4.93" x2="19.07" y2="19.07"></line>
|
||||
@@ -0,0 +1,806 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Network Monitor - 界面设计概念</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:root {
|
||||
--bg-primary: #0a0a0a;
|
||||
--bg-secondary: #141414;
|
||||
--bg-tertiary: #1a1a1a;
|
||||
--bg-hover: #252525;
|
||||
--text-primary: #ffffff;
|
||||
--text-secondary: #a0a0a0;
|
||||
--text-tertiary: #666666;
|
||||
--accent: #0ea5e9;
|
||||
--accent-hover: #38bdf8;
|
||||
--success: #22c55e;
|
||||
--warning: #f59e0b;
|
||||
--danger: #ef4444;
|
||||
--border: #2a2a2a;
|
||||
--glass: rgba(255, 255, 255, 0.03);
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
font-size: 13px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 主容器 */
|
||||
.app-container {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
background: var(--bg-primary);
|
||||
}
|
||||
|
||||
/* 侧边栏 */
|
||||
.sidebar {
|
||||
width: 56px;
|
||||
background: var(--bg-secondary);
|
||||
border-right: 1px solid var(--border);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
transition: width 0.2s ease;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.sidebar.expanded {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.sidebar-toggle {
|
||||
height: 48px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
position: relative;
|
||||
height: 48px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 16px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.nav-item:hover {
|
||||
background: var(--bg-hover);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.nav-item.active {
|
||||
background: var(--glass);
|
||||
color: var(--accent);
|
||||
border-left: 2px solid var(--accent);
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.nav-label {
|
||||
margin-left: 12px;
|
||||
white-space: nowrap;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.sidebar.expanded .nav-label {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* 主内容区 */
|
||||
.main-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 顶部栏 */
|
||||
.topbar {
|
||||
height: 48px;
|
||||
background: var(--bg-secondary);
|
||||
border-bottom: 1px solid var(--border);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 16px;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
flex: 1;
|
||||
max-width: 400px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 100%;
|
||||
height: 32px;
|
||||
background: var(--bg-tertiary);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
padding: 0 36px 0 12px;
|
||||
color: var(--text-primary);
|
||||
font-size: 13px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.search-input:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent);
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.status-indicators {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.status-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.status-label {
|
||||
color: var(--text-secondary);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.status-value {
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* 内容区域 */
|
||||
.content-area {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 实时监控视图 */
|
||||
.monitor-view {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
padding: 16px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* 统计卡片 */
|
||||
.stats-cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
border-color: var(--accent);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.stat-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.stat-title {
|
||||
color: var(--text-secondary);
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.stat-trend {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.trend-up {
|
||||
color: var(--success);
|
||||
}
|
||||
|
||||
.trend-down {
|
||||
color: var(--danger);
|
||||
}
|
||||
|
||||
/* 连接列表 */
|
||||
.connections-section {
|
||||
flex: 1;
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.filter-buttons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.filter-btn {
|
||||
padding: 4px 12px;
|
||||
background: var(--bg-tertiary);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 4px;
|
||||
color: var(--text-secondary);
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.filter-btn:hover {
|
||||
background: var(--bg-hover);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.filter-btn.active {
|
||||
background: var(--accent);
|
||||
color: white;
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
/* 连接表格 */
|
||||
.connections-table {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.table-header {
|
||||
display: grid;
|
||||
grid-template-columns: 200px 150px 120px 150px 100px 100px 80px;
|
||||
padding: 12px 16px;
|
||||
background: var(--bg-tertiary);
|
||||
border-bottom: 1px solid var(--border);
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.table-row {
|
||||
display: grid;
|
||||
grid-template-columns: 200px 150px 120px 150px 100px 100px 80px;
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
transition: all 0.2s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.table-row:hover {
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
.process-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.process-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background: var(--accent);
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.process-name {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.connection-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.status-dot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.status-active {
|
||||
background: var(--success);
|
||||
box-shadow: 0 0 8px var(--success);
|
||||
}
|
||||
|
||||
.speed-indicator {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.speed-up {
|
||||
color: var(--success);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.speed-down {
|
||||
color: var(--accent);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
/* 详情面板 */
|
||||
.detail-panel {
|
||||
width: 320px;
|
||||
background: var(--bg-secondary);
|
||||
border-left: 1px solid var(--border);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.detail-panel.collapsed {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
|
||||
.detail-header {
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.detail-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.detail-section {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.detail-section-title {
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 12px;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.detail-label {
|
||||
color: var(--text-secondary);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.detail-value {
|
||||
color: var(--text-primary);
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 图表容器 */
|
||||
.chart-container {
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
height: 200px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
/* 浮动操作按钮 */
|
||||
.fab {
|
||||
position: fixed;
|
||||
bottom: 24px;
|
||||
right: 24px;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
background: var(--accent);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 4px 12px rgba(14, 165, 233, 0.4);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.fab:hover {
|
||||
background: var(--accent-hover);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
/* 响应式调整 */
|
||||
@media (max-width: 1200px) {
|
||||
.table-header,
|
||||
.table-row {
|
||||
grid-template-columns: 180px 120px 100px 120px 80px 80px 60px;
|
||||
}
|
||||
|
||||
.detail-panel {
|
||||
width: 280px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.sidebar {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
|
||||
.sidebar.expanded {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.stats-cards {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-container">
|
||||
<!-- 侧边栏 -->
|
||||
<div class="sidebar" id="sidebar">
|
||||
<div class="sidebar-toggle" onclick="toggleSidebar()">
|
||||
<span class="nav-icon">☰</span>
|
||||
</div>
|
||||
<div class="nav-item active">
|
||||
<span class="nav-icon">📊</span>
|
||||
<span class="nav-label">实时监控</span>
|
||||
</div>
|
||||
<div class="nav-item">
|
||||
<span class="nav-icon">🛡️</span>
|
||||
<span class="nav-label">防火墙规则</span>
|
||||
</div>
|
||||
<div class="nav-item">
|
||||
<span class="nav-icon">📈</span>
|
||||
<span class="nav-label">流量分析</span>
|
||||
</div>
|
||||
<div class="nav-item">
|
||||
<span class="nav-icon">🌍</span>
|
||||
<span class="nav-label">连接地图</span>
|
||||
</div>
|
||||
<div class="nav-item">
|
||||
<span class="nav-icon">📝</span>
|
||||
<span class="nav-label">日志审计</span>
|
||||
</div>
|
||||
<div class="nav-item">
|
||||
<span class="nav-icon">⚙️</span>
|
||||
<span class="nav-label">设置</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 主内容区 -->
|
||||
<div class="main-content">
|
||||
<!-- 顶部栏 -->
|
||||
<div class="topbar">
|
||||
<div class="search-box">
|
||||
<input type="text" class="search-input" placeholder="搜索进程、IP、域名...">
|
||||
<span class="search-icon">🔍</span>
|
||||
</div>
|
||||
|
||||
<div class="status-indicators">
|
||||
<div class="status-item">
|
||||
<span class="status-label">活动连接</span>
|
||||
<span class="status-value">127</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-label">总速率</span>
|
||||
<span class="status-value">12.4 MB/s</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-label">阻止</span>
|
||||
<span class="status-value" style="color: var(--danger)">23</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<div class="content-area">
|
||||
<!-- 监控视图 -->
|
||||
<div class="monitor-view">
|
||||
<!-- 统计卡片 -->
|
||||
<div class="stats-cards">
|
||||
<div class="stat-card">
|
||||
<div class="stat-header">
|
||||
<span class="stat-title">上传速率</span>
|
||||
<span class="stat-icon">↑</span>
|
||||
</div>
|
||||
<div class="stat-value">3.2 MB/s</div>
|
||||
<div class="stat-trend trend-up">
|
||||
<span>↑ 12%</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card">
|
||||
<div class="stat-header">
|
||||
<span class="stat-title">下载速率</span>
|
||||
<span class="stat-icon">↓</span>
|
||||
</div>
|
||||
<div class="stat-value">9.2 MB/s</div>
|
||||
<div class="stat-trend trend-down">
|
||||
<span>↓ 5%</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card">
|
||||
<div class="stat-header">
|
||||
<span class="stat-title">活跃进程</span>
|
||||
<span class="stat-icon">💻</span>
|
||||
</div>
|
||||
<div class="stat-value">48</div>
|
||||
<div class="stat-trend">
|
||||
<span style="color: var(--text-secondary)">→ 0%</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card">
|
||||
<div class="stat-header">
|
||||
<span class="stat-title">威胁检测</span>
|
||||
<span class="stat-icon">⚠️</span>
|
||||
</div>
|
||||
<div class="stat-value">2</div>
|
||||
<div class="stat-trend trend-up" style="color: var(--warning)">
|
||||
<span>新增警告</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 实时速率图表 -->
|
||||
<div class="chart-container">
|
||||
<span>实时网络速率图表 (ECharts)</span>
|
||||
</div>
|
||||
|
||||
<!-- 连接列表 -->
|
||||
<div class="connections-section">
|
||||
<div class="section-header">
|
||||
<h3 class="section-title">活动连接</h3>
|
||||
<div class="filter-buttons">
|
||||
<button class="filter-btn active">全部</button>
|
||||
<button class="filter-btn">TCP</button>
|
||||
<button class="filter-btn">UDP</button>
|
||||
<button class="filter-btn">已阻止</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="connections-table">
|
||||
<div class="table-header">
|
||||
<div>进程</div>
|
||||
<div>远程地址</div>
|
||||
<div>域名</div>
|
||||
<div>本地端口</div>
|
||||
<div>协议</div>
|
||||
<div>速率</div>
|
||||
<div>状态</div>
|
||||
</div>
|
||||
|
||||
<div class="table-row" onclick="showDetail()">
|
||||
<div class="process-info">
|
||||
<div class="process-icon">Ch</div>
|
||||
<span class="process-name">Chrome.exe</span>
|
||||
</div>
|
||||
<div>142.251.42.142</div>
|
||||
<div>youtube.com</div>
|
||||
<div>58432:443</div>
|
||||
<div>TCP</div>
|
||||
<div class="speed-indicator">
|
||||
<span class="speed-up">↑ 124 KB/s</span>
|
||||
<span class="speed-down">↓ 2.1 MB/s</span>
|
||||
</div>
|
||||
<div class="connection-status">
|
||||
<span class="status-dot status-active"></span>
|
||||
<span>活动</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-row">
|
||||
<div class="process-info">
|
||||
<div class="process-icon" style="background: var(--success)">St</div>
|
||||
<span class="process-name">Steam.exe</span>
|
||||
</div>
|
||||
<div>185.25.182.77</div>
|
||||
<div>steamcdn.com</div>
|
||||
<div>62145:443</div>
|
||||
<div>TCP</div>
|
||||
<div class="speed-indicator">
|
||||
<span class="speed-up">↑ 12 KB/s</span>
|
||||
<span class="speed-down">↓ 6.8 MB/s</span>
|
||||
</div>
|
||||
<div class="connection-status">
|
||||
<span class="status-dot status-active"></span>
|
||||
<span>活动</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-row">
|
||||
<div class="process-info">
|
||||
<div class="process-icon" style="background: var(--warning)">Un</div>
|
||||
<span class="process-name">Unknown.exe</span>
|
||||
</div>
|
||||
<div>45.33.32.156</div>
|
||||
<div>-</div>
|
||||
<div>51234:80</div>
|
||||
<div>TCP</div>
|
||||
<div class="speed-indicator">
|
||||
<span class="speed-up">↑ 524 KB/s</span>
|
||||
<span class="speed-down">↓ 0 KB/s</span>
|
||||
</div>
|
||||
<div class="connection-status">
|
||||
<span class="status-dot" style="background: var(--warning)"></span>
|
||||
<span style="color: var(--warning)">可疑</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 详情面板 -->
|
||||
<div class="detail-panel collapsed" id="detailPanel">
|
||||
<div class="detail-header">
|
||||
<h3>连接详情</h3>
|
||||
<span style="cursor: pointer" onclick="hideDetail()">✕</span>
|
||||
</div>
|
||||
<div class="detail-content">
|
||||
<div class="detail-section">
|
||||
<h4 class="detail-section-title">进程信息</h4>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">进程名称</span>
|
||||
<span class="detail-value">Chrome.exe</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">PID</span>
|
||||
<span class="detail-value">12456</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">路径</span>
|
||||
<span class="detail-value">C:\Program Files\...</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">启动时间</span>
|
||||
<span class="detail-value">2025-01-15 09:23</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<h4 class="detail-section-title">连接信息</h4>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">远程IP</span>
|
||||
<span class="detail-value">142.251.42.142</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">地理位置</span>
|
||||
<span class="detail-value">美国 加利福尼亚</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">建立时间</span>
|
||||
<span class="detail-value">10分钟前</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">总流量</span>
|
||||
<span class="detail-value">125.4 MB</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<h4 class="detail-section-title">流量统计</h4>
|
||||
<div class="chart-container" style="height: 150px;">
|
||||
<span>连接流量图表</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 浮动操作按钮 -->
|
||||
<div class="fab">
|
||||
<span>+</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function toggleSidebar() {
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
sidebar.classList.toggle('expanded');
|
||||
}
|
||||
|
||||
function showDetail() {
|
||||
const panel = document.getElementById('detailPanel');
|
||||
panel.classList.remove('collapsed');
|
||||
}
|
||||
|
||||
function hideDetail() {
|
||||
const panel = document.getElementById('detailPanel');
|
||||
panel.classList.add('collapsed');
|
||||
}
|
||||
|
||||
// 模拟实时数据更新
|
||||
setInterval(() => {
|
||||
const values = document.querySelectorAll('.stat-value');
|
||||
values[0].textContent = (Math.random() * 5).toFixed(1) + ' MB/s';
|
||||
values[1].textContent = (Math.random() * 10 + 5).toFixed(1) + ' MB/s';
|
||||
}, 2000);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,861 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>NetGuard Pro - 网络管理工具</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:root {
|
||||
--bg-primary: #0f172a;
|
||||
--bg-secondary: #1e293b;
|
||||
--bg-tertiary: #334155;
|
||||
--text-primary: #f8fafc;
|
||||
--text-secondary: #cbd5e1;
|
||||
--text-muted: #64748b;
|
||||
--accent-primary: #3b82f6;
|
||||
--accent-secondary: #06b6d4;
|
||||
--success: #10b981;
|
||||
--warning: #f59e0b;
|
||||
--danger: #ef4444;
|
||||
--border: #475569;
|
||||
--shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
||||
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
|
||||
--blur: blur(8px);
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
overflow: hidden;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.app-container {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
/* 顶部标题栏 */
|
||||
.title-bar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 40px;
|
||||
background: var(--bg-secondary);
|
||||
border-bottom: 1px solid var(--border);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 16px;
|
||||
z-index: 1000;
|
||||
-webkit-app-region: drag;
|
||||
}
|
||||
|
||||
.app-logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.logo-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.window-controls {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
|
||||
.control-btn {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.control-btn:hover { opacity: 0.8; }
|
||||
.control-btn.close { background: #ef4444; }
|
||||
.control-btn.minimize { background: #f59e0b; }
|
||||
.control-btn.maximize { background: #10b981; }
|
||||
|
||||
/* 侧边栏 */
|
||||
.sidebar {
|
||||
width: 240px;
|
||||
background: var(--bg-secondary);
|
||||
border-right: 1px solid var(--border);
|
||||
padding-top: 40px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.sidebar.collapsed {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
padding: 20px 16px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.status-overview {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.status-card {
|
||||
background: var(--bg-tertiary);
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.status-value {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--accent-primary);
|
||||
}
|
||||
|
||||
.status-label {
|
||||
font-size: 11px;
|
||||
color: var(--text-muted);
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.nav-menu {
|
||||
flex: 1;
|
||||
padding: 16px 0;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
margin: 2px 8px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.nav-item:hover {
|
||||
background: var(--bg-tertiary);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.nav-item.active {
|
||||
background: var(--accent-primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-right: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* 主内容区 */
|
||||
.main-content {
|
||||
flex: 1;
|
||||
padding-top: 40px;
|
||||
background: var(--bg-primary);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.content-header {
|
||||
padding: 20px 24px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
background: var(--bg-secondary);
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.page-description {
|
||||
color: var(--text-muted);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.content-body {
|
||||
flex: 1;
|
||||
padding: 24px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* 卡片组件 */
|
||||
.card {
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.card-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 6px 12px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--accent-primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: var(--bg-tertiary);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
/* 实时连接表格 */
|
||||
.connections-grid {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.connection-item {
|
||||
background: var(--bg-tertiary);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr auto auto auto;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.connection-item:hover {
|
||||
background: var(--bg-primary);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.connection-status {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: var(--success);
|
||||
}
|
||||
|
||||
.connection-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.connection-process {
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.connection-target {
|
||||
color: var(--text-muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.connection-protocol {
|
||||
background: var(--accent-primary);
|
||||
color: white;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-size: 10px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.connection-speed {
|
||||
text-align: right;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.speed-up { color: var(--success); }
|
||||
.speed-down { color: var(--accent-secondary); }
|
||||
|
||||
/* 图表区域 */
|
||||
.chart-container {
|
||||
height: 200px;
|
||||
background: var(--bg-tertiary);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* 进程列表 */
|
||||
.process-grid {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.process-item {
|
||||
background: var(--bg-tertiary);
|
||||
border-radius: 8px;
|
||||
padding: 12px 16px;
|
||||
display: grid;
|
||||
grid-template-columns: 32px 1fr auto auto;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.process-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background: var(--accent-primary);
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.process-details h4 {
|
||||
font-size: 14px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.process-details p {
|
||||
font-size: 11px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.process-stats {
|
||||
text-align: right;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.toggle-btn {
|
||||
width: 40px;
|
||||
height: 20px;
|
||||
background: var(--bg-primary);
|
||||
border-radius: 10px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
transition: background 0.3s;
|
||||
}
|
||||
|
||||
.toggle-btn.active {
|
||||
background: var(--success);
|
||||
}
|
||||
|
||||
.toggle-btn::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: white;
|
||||
border-radius: 50%;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
.toggle-btn.active::after {
|
||||
transform: translateX(20px);
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 1200px) {
|
||||
.sidebar {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.status-overview {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.nav-item span {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* 滚动条样式 */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: var(--bg-secondary);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--border);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--text-muted);
|
||||
}
|
||||
|
||||
/* 动画 */
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.5; }
|
||||
}
|
||||
|
||||
.pulse {
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
/* 工具提示 */
|
||||
.tooltip {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tooltip::after {
|
||||
content: attr(data-tooltip);
|
||||
position: absolute;
|
||||
bottom: -30px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
white-space: nowrap;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.3s;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.tooltip:hover::after {
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-container">
|
||||
<!-- 标题栏 -->
|
||||
<div class="title-bar">
|
||||
<div class="app-logo">
|
||||
<div class="logo-icon">🛡️</div>
|
||||
<span>NetGuard Pro</span>
|
||||
</div>
|
||||
<div class="window-controls">
|
||||
<div class="control-btn minimize"></div>
|
||||
<div class="control-btn maximize"></div>
|
||||
<div class="control-btn close"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 侧边栏 -->
|
||||
<div class="sidebar" id="sidebar">
|
||||
<div class="sidebar-header">
|
||||
<div class="status-overview">
|
||||
<div class="status-card">
|
||||
<div class="status-value">24</div>
|
||||
<div class="status-label">活跃连接</div>
|
||||
</div>
|
||||
<div class="status-card">
|
||||
<div class="status-value">1.2MB/s</div>
|
||||
<div class="status-label">当前流量</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="nav-menu">
|
||||
<div class="nav-item active" data-page="dashboard">
|
||||
<div class="nav-icon">📊</div>
|
||||
<span>实时监控</span>
|
||||
</div>
|
||||
<div class="nav-item" data-page="processes">
|
||||
<div class="nav-icon">⚙️</div>
|
||||
<span>进程管理</span>
|
||||
</div>
|
||||
<div class="nav-item" data-page="firewall">
|
||||
<div class="nav-icon">🛡️</div>
|
||||
<span>防火墙</span>
|
||||
</div>
|
||||
<div class="nav-item" data-page="analytics">
|
||||
<div class="nav-icon">📈</div>
|
||||
<span>流量分析</span>
|
||||
</div>
|
||||
<div class="nav-item" data-page="network-map">
|
||||
<div class="nav-icon">🌐</div>
|
||||
<span>连接地图</span>
|
||||
</div>
|
||||
<div class="nav-item" data-page="settings">
|
||||
<div class="nav-icon">⚡</div>
|
||||
<span>系统设置</span>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- 主内容区 -->
|
||||
<div class="main-content">
|
||||
<div class="content-header">
|
||||
<h1 class="page-title" id="pageTitle">实时网络监控</h1>
|
||||
<p class="page-description" id="pageDescription">监控当前系统的网络连接状态和流量情况</p>
|
||||
</div>
|
||||
|
||||
<div class="content-body" id="contentBody">
|
||||
<!-- 实时监控页面 -->
|
||||
<div id="dashboard-content">
|
||||
<!-- 活跃连接 -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">活跃连接</h3>
|
||||
<div class="card-actions">
|
||||
<button class="btn btn-secondary tooltip" data-tooltip="刷新连接列表">🔄</button>
|
||||
<button class="btn btn-secondary tooltip" data-tooltip="导出连接数据">📥</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="connections-grid">
|
||||
<div class="connection-item">
|
||||
<div class="connection-status pulse"></div>
|
||||
<div class="connection-info">
|
||||
<div class="connection-process">Chrome.exe (PID: 12340)</div>
|
||||
<div class="connection-target">192.168.1.1:443 → www.google.com</div>
|
||||
</div>
|
||||
<div class="connection-protocol">HTTPS</div>
|
||||
<div class="connection-speed">
|
||||
<div class="speed-up">↑ 245 KB/s</div>
|
||||
<div class="speed-down">↓ 1.2 MB/s</div>
|
||||
</div>
|
||||
<button class="btn btn-secondary">详情</button>
|
||||
</div>
|
||||
<div class="connection-item">
|
||||
<div class="connection-status"></div>
|
||||
<div class="connection-info">
|
||||
<div class="connection-process">Steam.exe (PID: 8956)</div>
|
||||
<div class="connection-target">103.28.54.10:27030 → Steam CDN</div>
|
||||
</div>
|
||||
<div class="connection-protocol">TCP</div>
|
||||
<div class="connection-speed">
|
||||
<div class="speed-up">↑ 12 KB/s</div>
|
||||
<div class="speed-down">↓ 856 KB/s</div>
|
||||
</div>
|
||||
<button class="btn btn-secondary">详情</button>
|
||||
</div>
|
||||
<div class="connection-item">
|
||||
<div class="connection-status"></div>
|
||||
<div class="connection-info">
|
||||
<div class="connection-process">System (PID: 4)</div>
|
||||
<div class="connection-target">8.8.8.8:53 → DNS</div>
|
||||
</div>
|
||||
<div class="connection-protocol">UDP</div>
|
||||
<div class="connection-speed">
|
||||
<div class="speed-up">↑ 2 KB/s</div>
|
||||
<div class="speed-down">↓ 1 KB/s</div>
|
||||
</div>
|
||||
<button class="btn btn-secondary">详情</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 实时流量图表 -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">实时流量监控</h3>
|
||||
<div class="card-actions">
|
||||
<button class="btn btn-secondary">1分钟</button>
|
||||
<button class="btn btn-primary">5分钟</button>
|
||||
<button class="btn btn-secondary">15分钟</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chart-container">
|
||||
📈 实时流量图表 (ECharts 集成区域)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 进程网络使用排行 -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">网络使用排行</h3>
|
||||
<div class="card-actions">
|
||||
<button class="btn btn-secondary">按流量</button>
|
||||
<button class="btn btn-secondary">按连接数</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="process-grid">
|
||||
<div class="process-item">
|
||||
<div class="process-icon">🌐</div>
|
||||
<div class="process-details">
|
||||
<h4>Google Chrome</h4>
|
||||
<p>C:\Program Files\Google\Chrome\Application\chrome.exe</p>
|
||||
</div>
|
||||
<div class="process-stats">
|
||||
<div>2.3 MB/s</div>
|
||||
<div style="color: var(--text-muted);">8 连接</div>
|
||||
</div>
|
||||
<div class="toggle-btn active"></div>
|
||||
</div>
|
||||
<div class="process-item">
|
||||
<div class="process-icon">🎮</div>
|
||||
<div class="process-details">
|
||||
<h4>Steam</h4>
|
||||
<p>C:\Program Files (x86)\Steam\steam.exe</p>
|
||||
</div>
|
||||
<div class="process-stats">
|
||||
<div>856 KB/s</div>
|
||||
<div style="color: var(--text-muted);">3 连接</div>
|
||||
</div>
|
||||
<div class="toggle-btn active"></div>
|
||||
</div>
|
||||
<div class="process-item">
|
||||
<div class="process-icon">💬</div>
|
||||
<div class="process-details">
|
||||
<h4>Microsoft Teams</h4>
|
||||
<p>C:\Users\User\AppData\Local\Microsoft\Teams\current\Teams.exe</p>
|
||||
</div>
|
||||
<div class="process-stats">
|
||||
<div>234 KB/s</div>
|
||||
<div style="color: var(--text-muted);">5 连接</div>
|
||||
</div>
|
||||
<div class="toggle-btn active"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 其他页面内容将通过 JavaScript 动态加载 -->
|
||||
<div id="processes-content" style="display: none;">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">进程网络权限管理</h3>
|
||||
<div class="card-actions">
|
||||
<button class="btn btn-primary">添加规则</button>
|
||||
<button class="btn btn-secondary">批量操作</button>
|
||||
</div>
|
||||
</div>
|
||||
<p style="color: var(--text-muted); text-align: center; padding: 40px;">进程管理功能区域 - 显示所有联网进程的详细信息和权限控制</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="firewall-content" style="display: none;">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">防火墙规则管理</h3>
|
||||
<div class="card-actions">
|
||||
<button class="btn btn-primary">新建规则</button>
|
||||
<button class="btn btn-secondary">导入/导出</button>
|
||||
</div>
|
||||
</div>
|
||||
<p style="color: var(--text-muted); text-align: center; padding: 40px;">防火墙管理功能区域 - 统一的规则管理界面</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="analytics-content" style="display: none;">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">流量分析与统计</h3>
|
||||
<div class="card-actions">
|
||||
<button class="btn btn-secondary">日报告</button>
|
||||
<button class="btn btn-secondary">周报告</button>
|
||||
<button class="btn btn-secondary">月报告</button>
|
||||
</div>
|
||||
</div>
|
||||
<p style="color: var(--text-muted); text-align: center; padding: 40px;">流量分析功能区域 - 历史数据、趋势分析、可视化图表</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="network-map-content" style="display: none;">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">网络连接地图</h3>
|
||||
<div class="card-actions">
|
||||
<button class="btn btn-secondary">拓扑视图</button>
|
||||
<button class="btn btn-secondary">地理视图</button>
|
||||
</div>
|
||||
</div>
|
||||
<p style="color: var(--text-muted); text-align: center; padding: 40px;">网络地图功能区域 - 连接可视化、地理位置映射</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="settings-content" style="display: none;">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">系统设置</h3>
|
||||
</div>
|
||||
<p style="color: var(--text-muted); text-align: center; padding: 40px;">系统设置功能区域 - 配置选项、日志管理、系统偏好</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 页面切换功能
|
||||
const navItems = document.querySelectorAll('.nav-item');
|
||||
const pageTitle = document.getElementById('pageTitle');
|
||||
const pageDescription = document.getElementById('pageDescription');
|
||||
const contentSections = {
|
||||
'dashboard': {
|
||||
title: '实时网络监控',
|
||||
description: '监控当前系统的网络连接状态和流量情况',
|
||||
element: document.getElementById('dashboard-content')
|
||||
},
|
||||
'processes': {
|
||||
title: '进程网络管理',
|
||||
description: '管理应用程序的网络访问权限和行为监控',
|
||||
element: document.getElementById('processes-content')
|
||||
},
|
||||
'firewall': {
|
||||
title: '防火墙控制中心',
|
||||
description: '统一管理和配置Windows防火墙规则',
|
||||
element: document.getElementById('firewall-content')
|
||||
},
|
||||
'analytics': {
|
||||
title: '流量分析报告',
|
||||
description: '深入分析网络使用模式和历史趋势',
|
||||
element: document.getElementById('analytics-content')
|
||||
},
|
||||
'network-map': {
|
||||
title: '网络连接地图',
|
||||
description: '可视化网络连接和地理位置分布',
|
||||
element: document.getElementById('network-map-content')
|
||||
},
|
||||
'settings': {
|
||||
title: '系统设置',
|
||||
description: '配置应用程序行为和系统偏好',
|
||||
element: document.getElementById('settings-content')
|
||||
}
|
||||
};
|
||||
|
||||
navItems.forEach(item => {
|
||||
item.addEventListener('click', () => {
|
||||
// 移除所有活跃状态
|
||||
navItems.forEach(nav => nav.classList.remove('active'));
|
||||
// 添加当前活跃状态
|
||||
item.classList.add('active');
|
||||
|
||||
// 获取页面标识
|
||||
const page = item.getAttribute('data-page');
|
||||
const pageInfo = contentSections[page];
|
||||
|
||||
if (pageInfo) {
|
||||
// 更新页面标题和描述
|
||||
pageTitle.textContent = pageInfo.title;
|
||||
pageDescription.textContent = pageInfo.description;
|
||||
|
||||
// 隐藏所有内容区域
|
||||
Object.values(contentSections).forEach(section => {
|
||||
section.element.style.display = 'none';
|
||||
});
|
||||
|
||||
// 显示当前页面内容
|
||||
pageInfo.element.style.display = 'block';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 切换按钮功能
|
||||
document.querySelectorAll('.toggle-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
btn.classList.toggle('active');
|
||||
});
|
||||
});
|
||||
|
||||
// 模拟实时数据更新
|
||||
function updateRealTimeData() {
|
||||
// 更新状态卡片数据
|
||||
const connectionCount = document.querySelector('.status-value');
|
||||
const speedValue = document.querySelectorAll('.status-value')[1];
|
||||
|
||||
if (connectionCount && speedValue) {
|
||||
const connections = Math.floor(Math.random() * 10) + 20;
|
||||
const speed = (Math.random() * 2 + 0.5).toFixed(1);
|
||||
|
||||
connectionCount.textContent = connections;
|
||||
speedValue.textContent = `${speed}MB/s`;
|
||||
}
|
||||
|
||||
// 更新连接状态指示器
|
||||
document.querySelectorAll('.connection-status').forEach(status => {
|
||||
if (Math.random() > 0.8) {
|
||||
status.classList.toggle('pulse');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 每3秒更新一次数据
|
||||
setInterval(updateRealTimeData, 3000);
|
||||
|
||||
// 窗口控制按钮功能
|
||||
document.querySelector('.control-btn.minimize').addEventListener('click', () => {
|
||||
console.log('窗口最小化');
|
||||
});
|
||||
|
||||
document.querySelector('.control-btn.maximize').addEventListener('click', () => {
|
||||
console.log('窗口最大化/还原');
|
||||
});
|
||||
|
||||
document.querySelector('.control-btn.close').addEventListener('click', () => {
|
||||
console.log('关闭窗口');
|
||||
});
|
||||
|
||||
// 响应式侧边栏
|
||||
function handleResize() {
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
if (window.innerWidth < 1200) {
|
||||
sidebar.classList.add('collapsed');
|
||||
} else {
|
||||
sidebar.classList.remove('collapsed');
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('resize', handleResize);
|
||||
handleResize(); // 初始检查
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,746 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>NetGuard Pro - 网络监控工具</title>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/echarts/5.4.3/echarts.min.js"></script>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
|
||||
background: #0a0a0a;
|
||||
color: #e5e5e5;
|
||||
overflow: hidden;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.app-container {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
background: linear-gradient(135deg, #0a0a0a 0%, #1a1a1a 100%);
|
||||
}
|
||||
|
||||
/* 侧边栏 */
|
||||
.sidebar {
|
||||
width: 280px;
|
||||
background: rgba(20, 20, 20, 0.95);
|
||||
backdrop-filter: blur(20px);
|
||||
border-right: 1px solid #333;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.sidebar.collapsed {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
padding: 20px;
|
||||
border-bottom: 1px solid #333;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background: linear-gradient(45deg, #00d4ff, #0099cc);
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.app-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.nav-menu {
|
||||
flex: 1;
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 20px;
|
||||
color: #ccc;
|
||||
text-decoration: none;
|
||||
transition: all 0.2s ease;
|
||||
border-left: 3px solid transparent;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.nav-item:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.nav-item.active {
|
||||
background: rgba(0, 212, 255, 0.1);
|
||||
color: #00d4ff;
|
||||
border-left-color: #00d4ff;
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
/* 主内容区 */
|
||||
.main-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 顶部工具栏 */
|
||||
.toolbar {
|
||||
height: 60px;
|
||||
background: rgba(20, 20, 20, 0.95);
|
||||
backdrop-filter: blur(20px);
|
||||
border-bottom: 1px solid #333;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
flex: 1;
|
||||
max-width: 400px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 100%;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border: 1px solid #444;
|
||||
border-radius: 8px;
|
||||
padding: 8px 16px 8px 40px;
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.search-input::placeholder {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
position: absolute;
|
||||
left: 12px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.toolbar-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.status-indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
background: rgba(0, 212, 255, 0.1);
|
||||
padding: 6px 12px;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.status-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: #00ff88;
|
||||
border-radius: 50%;
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.5; }
|
||||
}
|
||||
|
||||
/* 内容区域 */
|
||||
.content-area {
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
overflow: auto;
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 1fr;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
/* 卡片样式 */
|
||||
.card {
|
||||
background: rgba(30, 30, 30, 0.8);
|
||||
backdrop-filter: blur(20px);
|
||||
border: 1px solid #333;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 32px rgba(0, 212, 255, 0.1);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* 实时连接表格 */
|
||||
.connections-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.connections-table th,
|
||||
.connections-table td {
|
||||
padding: 8px 12px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #333;
|
||||
}
|
||||
|
||||
.connections-table th {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
font-weight: 600;
|
||||
font-size: 12px;
|
||||
color: #ccc;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.connections-table td {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.process-name {
|
||||
color: #00d4ff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.connection-status {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-established {
|
||||
background: rgba(0, 255, 136, 0.2);
|
||||
color: #00ff88;
|
||||
}
|
||||
|
||||
.status-listening {
|
||||
background: rgba(255, 193, 7, 0.2);
|
||||
color: #ffc107;
|
||||
}
|
||||
|
||||
/* 统计卡片 */
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 16px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: rgba(40, 40, 40, 0.6);
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #444;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: #00d4ff;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
/* 图表容器 */
|
||||
.chart-container {
|
||||
height: 200px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
/* 进程列表 */
|
||||
.process-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid #333;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.process-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background: #00d4ff;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.process-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.process-name-text {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.process-path {
|
||||
font-size: 11px;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.process-stats {
|
||||
text-align: right;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.bandwidth {
|
||||
color: #00ff88;
|
||||
}
|
||||
|
||||
/* 底部状态栏 */
|
||||
.status-bar {
|
||||
height: 30px;
|
||||
background: rgba(20, 20, 20, 0.95);
|
||||
border-top: 1px solid #333;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 20px;
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 1200px) {
|
||||
.content-area {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* 滚动条样式 */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #444;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #555;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-container">
|
||||
<!-- 侧边栏 -->
|
||||
<div class="sidebar">
|
||||
<div class="sidebar-header">
|
||||
<div class="logo">N</div>
|
||||
<div class="app-title">NetGuard Pro</div>
|
||||
</div>
|
||||
|
||||
<nav class="nav-menu">
|
||||
<a href="#" class="nav-item active">
|
||||
<svg class="nav-icon" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M3 4a1 1 0 011-1h12a1 1 0 011 1v2a1 1 0 01-1 1H4a1 1 0 01-1-1V4zM3 10a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H4a1 1 0 01-1-1v-6zM14 9a1 1 0 00-1 1v6a1 1 0 001 1h2a1 1 0 001-1v-6a1 1 0 00-1-1h-2z"/>
|
||||
</svg>
|
||||
仪表板
|
||||
</a>
|
||||
<a href="#" class="nav-item">
|
||||
<svg class="nav-icon" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M3 3a1 1 0 000 2v8a2 2 0 002 2h2.586l-1.293 1.293a1 1 0 101.414 1.414L10 15.414l2.293 2.293a1 1 0 001.414-1.414L12.414 15H15a2 2 0 002-2V5a1 1 0 100-2H3zm11.707 4.707a1 1 0 00-1.414-1.414L10 9.586 8.707 8.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"/>
|
||||
</svg>
|
||||
实时连接
|
||||
</a>
|
||||
<a href="#" class="nav-item">
|
||||
<svg class="nav-icon" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
</svg>
|
||||
进程监控
|
||||
</a>
|
||||
<a href="#" class="nav-item">
|
||||
<svg class="nav-icon" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M2.166 4.999A11.954 11.954 0 0010 1.944 11.954 11.954 0 0017.834 5c.11.65.166 1.32.166 2.001 0 5.225-3.34 9.67-8 11.317C5.34 16.67 2 12.225 2 7c0-.682.057-1.35.166-2.001zm11.541 3.708a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"/>
|
||||
</svg>
|
||||
防火墙规则
|
||||
</a>
|
||||
<a href="#" class="nav-item">
|
||||
<svg class="nav-icon" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M2 11a1 1 0 011-1h2a1 1 0 011 1v5a1 1 0 01-1 1H3a1 1 0 01-1-1v-5zM8 7a1 1 0 011-1h2a1 1 0 011 1v9a1 1 0 01-1 1H9a1 1 0 01-1-1V7zM14 4a1 1 0 011-1h2a1 1 0 011 1v12a1 1 0 01-1 1h-2a1 1 0 01-1-1V4z"/>
|
||||
</svg>
|
||||
流量分析
|
||||
</a>
|
||||
<a href="#" class="nav-item">
|
||||
<svg class="nav-icon" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M11.49 3.17c-.38-1.56-2.6-1.56-2.98 0a1.532 1.532 0 01-2.286.948c-1.372-.836-2.942.734-2.106 2.106.54.886.061 2.042-.947 2.287-1.561.379-1.561 2.6 0 2.978a1.532 1.532 0 01.947 2.287c-.836 1.372.734 2.942 2.106 2.106a1.532 1.532 0 012.287.947c.379 1.561 2.6 1.561 2.978 0a1.533 1.533 0 012.287-.947c1.372.836 2.942-.734 2.106-2.106a1.533 1.533 0 01.947-2.287c1.561-.379 1.561-2.6 0-2.978a1.532 1.532 0 01-.947-2.287c.836-1.372-.734-2.942-2.106-2.106a1.532 1.532 0 01-2.287-.947zM10 13a3 3 0 100-6 3 3 0 000 6z"/>
|
||||
</svg>
|
||||
设置
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- 主内容区 -->
|
||||
<div class="main-content">
|
||||
<!-- 顶部工具栏 -->
|
||||
<div class="toolbar">
|
||||
<div class="search-box">
|
||||
<svg class="search-icon" width="16" height="16" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z"/>
|
||||
</svg>
|
||||
<input type="text" class="search-input" placeholder="搜索进程、IP 地址或域名...">
|
||||
</div>
|
||||
|
||||
<div class="toolbar-actions">
|
||||
<div class="status-indicator">
|
||||
<div class="status-dot"></div>
|
||||
监控中
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<div class="content-area">
|
||||
<!-- 左侧主区域 -->
|
||||
<div>
|
||||
<!-- 统计卡片 -->
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<div class="stat-value" id="activeConnections">247</div>
|
||||
<div class="stat-label">活跃连接</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value" id="totalProcesses">42</div>
|
||||
<div class="stat-label">联网进程</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value" id="uploadSpeed">1.2 MB/s</div>
|
||||
<div class="stat-label">上传速度</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value" id="downloadSpeed">5.8 MB/s</div>
|
||||
<div class="stat-label">下载速度</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 实时连接表格 -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">实时连接</h3>
|
||||
</div>
|
||||
<div style="overflow-x: auto;">
|
||||
<table class="connections-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>进程</th>
|
||||
<th>本地地址</th>
|
||||
<th>远程地址</th>
|
||||
<th>状态</th>
|
||||
<th>带宽</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="connectionsTableBody">
|
||||
<tr>
|
||||
<td><span class="process-name">chrome.exe</span></td>
|
||||
<td>192.168.1.100:52341</td>
|
||||
<td>142.250.191.14:443 (google.com)</td>
|
||||
<td><span class="connection-status status-established">已连接</span></td>
|
||||
<td class="bandwidth">↓ 850 KB/s</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="process-name">steam.exe</span></td>
|
||||
<td>192.168.1.100:27036</td>
|
||||
<td>208.78.164.9:27017 (steam)</td>
|
||||
<td><span class="connection-status status-established">已连接</span></td>
|
||||
<td class="bandwidth">↓ 45 KB/s</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="process-name">discord.exe</span></td>
|
||||
<td>192.168.1.100:0</td>
|
||||
<td>162.159.130.233:443</td>
|
||||
<td><span class="connection-status status-listening">监听</span></td>
|
||||
<td class="bandwidth">↓ 12 KB/s</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 流量图表 -->
|
||||
<div class="card" style="margin-top: 20px;">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">网络流量趋势</h3>
|
||||
</div>
|
||||
<div class="chart-container" id="trafficChart"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧边栏 -->
|
||||
<div>
|
||||
<!-- 活跃进程 -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">活跃进程</h3>
|
||||
</div>
|
||||
<div id="processesListContainer">
|
||||
<div class="process-item">
|
||||
<div class="process-icon">C</div>
|
||||
<div class="process-info">
|
||||
<div class="process-name-text">chrome.exe</div>
|
||||
<div class="process-path">C:\Program Files\Google\Chrome\Application\</div>
|
||||
</div>
|
||||
<div class="process-stats">
|
||||
<div class="bandwidth">↓ 850 KB/s</div>
|
||||
<div style="color: #888;">23 连接</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="process-item">
|
||||
<div class="process-icon">S</div>
|
||||
<div class="process-info">
|
||||
<div class="process-name-text">steam.exe</div>
|
||||
<div class="process-path">C:\Program Files (x86)\Steam\</div>
|
||||
</div>
|
||||
<div class="process-stats">
|
||||
<div class="bandwidth">↓ 45 KB/s</div>
|
||||
<div style="color: #888;">3 连接</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="process-item">
|
||||
<div class="process-icon">D</div>
|
||||
<div class="process-info">
|
||||
<div class="process-name-text">discord.exe</div>
|
||||
<div class="process-path">C:\Users\User\AppData\Local\Discord\</div>
|
||||
</div>
|
||||
<div class="process-stats">
|
||||
<div class="bandwidth">↓ 12 KB/s</div>
|
||||
<div style="color: #888;">2 连接</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 地理分布 -->
|
||||
<div class="card" style="margin-top: 20px;">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">连接地理分布</h3>
|
||||
</div>
|
||||
<div class="chart-container" id="geoChart"></div>
|
||||
</div>
|
||||
|
||||
<!-- 防火墙状态 -->
|
||||
<div class="card" style="margin-top: 20px;">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">防火墙状态</h3>
|
||||
</div>
|
||||
<div style="padding: 10px 0;">
|
||||
<div style="display: flex; justify-content: space-between; margin-bottom: 10px;">
|
||||
<span>已阻止连接</span>
|
||||
<span style="color: #ff6b6b;">142</span>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-between; margin-bottom: 10px;">
|
||||
<span>活跃规则</span>
|
||||
<span style="color: #00d4ff;">28</span>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-between;">
|
||||
<span>白名单进程</span>
|
||||
<span style="color: #00ff88;">15</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部状态栏 -->
|
||||
<div class="status-bar">
|
||||
<span>监控状态: 正常</span>
|
||||
<span>|</span>
|
||||
<span>ETW 事件: 1,247</span>
|
||||
<span>|</span>
|
||||
<span>内存使用: 45.2 MB</span>
|
||||
<span>|</span>
|
||||
<span>版本: 1.0.0</span>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 初始化图表
|
||||
function initCharts() {
|
||||
// 流量趋势图
|
||||
const trafficChart = echarts.init(document.getElementById('trafficChart'), 'dark');
|
||||
const trafficOption = {
|
||||
backgroundColor: 'transparent',
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
top: '10%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: ['00:00', '02:00', '04:00', '06:00', '08:00', '10:00', '12:00'],
|
||||
axisLine: { lineStyle: { color: '#333' } },
|
||||
axisLabel: { color: '#888' }
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
axisLine: { lineStyle: { color: '#333' } },
|
||||
axisLabel: { color: '#888' },
|
||||
splitLine: { lineStyle: { color: '#333' } }
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '下载',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
lineStyle: { color: '#00d4ff', width: 2 },
|
||||
areaStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(0, 212, 255, 0.3)' },
|
||||
{ offset: 1, color: 'rgba(0, 212, 255, 0.0)' }
|
||||
]
|
||||
}
|
||||
},
|
||||
data: [1.2, 2.1, 3.5, 2.8, 4.2, 5.8, 3.9]
|
||||
},
|
||||
{
|
||||
name: '上传',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
lineStyle: { color: '#00ff88', width: 2 },
|
||||
areaStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(0, 255, 136, 0.3)' },
|
||||
{ offset: 1, color: 'rgba(0, 255, 136, 0.0)' }
|
||||
]
|
||||
}
|
||||
},
|
||||
data: [0.5, 0.8, 1.2, 0.9, 1.5, 1.8, 1.2]
|
||||
}
|
||||
]
|
||||
};
|
||||
trafficChart.setOption(trafficOption);
|
||||
|
||||
// 地理分布图
|
||||
const geoChart = echarts.init(document.getElementById('geoChart'), 'dark');
|
||||
const geoOption = {
|
||||
backgroundColor: 'transparent',
|
||||
series: [{
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
center: ['50%', '50%'],
|
||||
data: [
|
||||
{ value: 45, name: '美国', itemStyle: { color: '#00d4ff' } },
|
||||
{ value: 25, name: '中国', itemStyle: { color: '#00ff88' } },
|
||||
{ value: 15, name: '日本', itemStyle: { color: '#ffc107' } },
|
||||
{ value: 10, name: '德国', itemStyle: { color: '#ff6b6b' } },
|
||||
{ value: 5, name: '其他', itemStyle: { color: '#888' } }
|
||||
],
|
||||
label: {
|
||||
show: true,
|
||||
color: '#e5e5e5',
|
||||
fontSize: 12
|
||||
},
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
||||
}
|
||||
}
|
||||
}]
|
||||
};
|
||||
geoChart.setOption(geoOption);
|
||||
}
|
||||
|
||||
// 模拟实时数据更新
|
||||
function simulateRealTimeData() {
|
||||
const connections = document.getElementById('activeConnections');
|
||||
const uploadSpeed = document.getElementById('uploadSpeed');
|
||||
const downloadSpeed = document.getElementById('downloadSpeed');
|
||||
|
||||
setInterval(() => {
|
||||
// 随机更新数据
|
||||
const newConnections = Math.floor(Math.random() * 50) + 220;
|
||||
const newUpload = (Math.random() * 2 + 0.5).toFixed(1);
|
||||
const newDownload = (Math.random() * 4 + 3).toFixed(1);
|
||||
|
||||
connections.textContent = newConnections;
|
||||
uploadSpeed.textContent = newUpload + ' MB/s';
|
||||
downloadSpeed.textContent = newDownload + ' MB/s';
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
// 初始化应用
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
initCharts();
|
||||
simulateRealTimeData();
|
||||
|
||||
// 响应式调整
|
||||
window.addEventListener('resize', function() {
|
||||
setTimeout(() => {
|
||||
echarts.getInstanceByDom(document.getElementById('trafficChart'))?.resize();
|
||||
echarts.getInstanceByDom(document.getElementById('geoChart'))?.resize();
|
||||
}, 200);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,911 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>NetGuard - 网络监控工具</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:root {
|
||||
--primary-color: #0078d4;
|
||||
--primary-hover: #106ebe;
|
||||
--secondary-color: #605e5c;
|
||||
--success-color: #107c10;
|
||||
--warning-color: #ffb900;
|
||||
--danger-color: #d13438;
|
||||
--bg-primary: #f3f2f1;
|
||||
--bg-secondary: #ffffff;
|
||||
--bg-tertiary: #faf9f8;
|
||||
--text-primary: #323130;
|
||||
--text-secondary: #605e5c;
|
||||
--text-muted: #a19f9d;
|
||||
--border-color: #e1dfdd;
|
||||
--hover-bg: #f3f2f1;
|
||||
--selected-bg: #deecf9;
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
--bg-primary: #1e1e1e;
|
||||
--bg-secondary: #2d2d30;
|
||||
--bg-tertiary: #252526;
|
||||
--text-primary: #cccccc;
|
||||
--text-secondary: #9d9d9d;
|
||||
--text-muted: #6d6d6d;
|
||||
--border-color: #3e3e42;
|
||||
--hover-bg: #2a2d2e;
|
||||
--selected-bg: #37373d;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
font-size: 13px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.app-container {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 工具栏 */
|
||||
.toolbar {
|
||||
height: 40px;
|
||||
background: var(--bg-tertiary);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 12px;
|
||||
gap: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.toolbar-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 0 8px;
|
||||
border-right: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.toolbar-group:last-child {
|
||||
border-right: none;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.toolbar-btn {
|
||||
height: 28px;
|
||||
padding: 0 8px;
|
||||
border: 1px solid transparent;
|
||||
background: transparent;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
color: var(--text-primary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.toolbar-btn:hover {
|
||||
background: var(--hover-bg);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
.toolbar-btn.active {
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.status-bar {
|
||||
height: 24px;
|
||||
background: var(--bg-tertiary);
|
||||
border-top: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 12px;
|
||||
font-size: 11px;
|
||||
color: var(--text-secondary);
|
||||
gap: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.status-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.status-indicator {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.status-indicator.active {
|
||||
background: var(--success-color);
|
||||
}
|
||||
|
||||
.status-indicator.warning {
|
||||
background: var(--warning-color);
|
||||
}
|
||||
|
||||
/* 主工作区 */
|
||||
.main-workspace {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 左侧面板 */
|
||||
.left-panel {
|
||||
width: 280px;
|
||||
background: var(--bg-secondary);
|
||||
border-right: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
height: 36px;
|
||||
background: var(--bg-tertiary);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 12px;
|
||||
font-weight: 600;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.panel-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* 进程树 */
|
||||
.process-tree {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.process-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 24px;
|
||||
padding: 0 8px;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.process-item:hover {
|
||||
background: var(--hover-bg);
|
||||
}
|
||||
|
||||
.process-item.selected {
|
||||
background: var(--selected-bg);
|
||||
}
|
||||
|
||||
.process-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-right: 8px;
|
||||
background: var(--secondary-color);
|
||||
border-radius: 2px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.process-name {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.connection-count {
|
||||
font-size: 10px;
|
||||
color: var(--text-muted);
|
||||
background: var(--bg-tertiary);
|
||||
padding: 1px 4px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
/* 右侧主视图 */
|
||||
.main-view {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--bg-secondary);
|
||||
}
|
||||
|
||||
.view-header {
|
||||
height: 36px;
|
||||
background: var(--bg-tertiary);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 12px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.view-title {
|
||||
font-weight: 600;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.view-controls {
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.control-input {
|
||||
height: 24px;
|
||||
padding: 0 6px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 2px;
|
||||
font-size: 11px;
|
||||
background: var(--bg-secondary);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* 连接表格 */
|
||||
.connections-grid {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.grid-container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
th {
|
||||
height: 28px;
|
||||
background: var(--bg-tertiary);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
border-right: 1px solid var(--border-color);
|
||||
padding: 0 8px;
|
||||
text-align: left;
|
||||
font-weight: 600;
|
||||
font-size: 11px;
|
||||
color: var(--text-secondary);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
th:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
td {
|
||||
height: 22px;
|
||||
padding: 0 8px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
border-right: 1px solid var(--border-color);
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
td:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
tr:hover {
|
||||
background: var(--hover-bg);
|
||||
}
|
||||
|
||||
tr.selected {
|
||||
background: var(--selected-bg);
|
||||
}
|
||||
|
||||
.cell-content {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 状态指示器 */
|
||||
.status-dot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.status-dot.established {
|
||||
background: var(--success-color);
|
||||
}
|
||||
|
||||
.status-dot.listening {
|
||||
background: var(--primary-color);
|
||||
}
|
||||
|
||||
.status-dot.blocked {
|
||||
background: var(--danger-color);
|
||||
}
|
||||
|
||||
/* 网速指示器 */
|
||||
.speed-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.speed-bar {
|
||||
width: 40px;
|
||||
height: 3px;
|
||||
background: var(--border-color);
|
||||
border-radius: 1px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.speed-fill {
|
||||
height: 100%;
|
||||
background: var(--success-color);
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.speed-text {
|
||||
font-size: 10px;
|
||||
color: var(--text-muted);
|
||||
min-width: 45px;
|
||||
}
|
||||
|
||||
/* 底部面板 */
|
||||
.bottom-panel {
|
||||
height: 160px;
|
||||
background: var(--bg-secondary);
|
||||
border-top: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.bottom-tabs {
|
||||
height: 28px;
|
||||
background: var(--bg-tertiary);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.bottom-tab {
|
||||
padding: 0 12px;
|
||||
height: 28px;
|
||||
border-right: 1px solid var(--border-color);
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: 11px;
|
||||
color: var(--text-secondary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.bottom-tab.active {
|
||||
background: var(--bg-secondary);
|
||||
color: var(--text-primary);
|
||||
border-bottom: 2px solid var(--primary-color);
|
||||
}
|
||||
|
||||
.bottom-content {
|
||||
flex: 1;
|
||||
padding: 8px;
|
||||
overflow-y: auto;
|
||||
font-family: 'Consolas', 'Monaco', monospace;
|
||||
font-size: 11px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.log-entry {
|
||||
margin-bottom: 2px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.log-time {
|
||||
color: var(--text-muted);
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.log-level {
|
||||
min-width: 40px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.log-level.info {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.log-level.warn {
|
||||
color: var(--warning-color);
|
||||
}
|
||||
|
||||
.log-level.error {
|
||||
color: var(--danger-color);
|
||||
}
|
||||
|
||||
/* 响应式调整 */
|
||||
@media (max-width: 1200px) {
|
||||
.left-panel {
|
||||
width: 240px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 800px) {
|
||||
.left-panel {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.bottom-panel {
|
||||
height: 120px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 滚动条样式 */
|
||||
::-webkit-scrollbar {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: var(--bg-tertiary);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--border-color);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--secondary-color);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-corner {
|
||||
background: var(--bg-tertiary);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-container">
|
||||
<!-- 工具栏 -->
|
||||
<div class="toolbar">
|
||||
<div class="toolbar-group">
|
||||
<button class="toolbar-btn active">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="12" cy="12" r="10"/>
|
||||
<path d="M12 6v6l4 2"/>
|
||||
</svg>
|
||||
实时监控
|
||||
</button>
|
||||
<button class="toolbar-btn">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M9 12l2 2 4-4"/>
|
||||
<path d="M21 12c-1 0-3-1-3-3s2-3 3-3 3 1 3 3-2 3-3 3"/>
|
||||
<path d="M3 12c1 0 3-1 3-3s-2-3-3-3-3 1-3 3 2 3 3 3"/>
|
||||
</svg>
|
||||
防火墙规则
|
||||
</button>
|
||||
<button class="toolbar-btn">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M18 20V10"/>
|
||||
<path d="M12 20V4"/>
|
||||
<path d="M6 20v-6"/>
|
||||
</svg>
|
||||
统计分析
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="toolbar-group">
|
||||
<button class="toolbar-btn" title="暂停监控">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<rect x="6" y="4" width="4" height="16"/>
|
||||
<rect x="14" y="4" width="4" height="16"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="toolbar-btn" title="清空列表">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M3 6h18"/>
|
||||
<path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6m3 0V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="toolbar-btn" title="导出数据">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
|
||||
<polyline points="7,10 12,15 17,10"/>
|
||||
<line x1="12" y1="15" x2="12" y2="3"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="toolbar-group">
|
||||
<span style="font-size: 11px; color: var(--text-muted);">更新间隔:</span>
|
||||
<select class="control-input" style="width: 60px;">
|
||||
<option>500ms</option>
|
||||
<option selected>1s</option>
|
||||
<option>2s</option>
|
||||
<option>5s</option>
|
||||
</select>
|
||||
<button class="toolbar-btn" title="设置">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="12" cy="12" r="3"/>
|
||||
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1 1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 主工作区 -->
|
||||
<div class="main-workspace">
|
||||
<!-- 左侧进程面板 -->
|
||||
<div class="left-panel">
|
||||
<div class="panel-header">
|
||||
活跃进程 (43)
|
||||
</div>
|
||||
<div class="panel-content">
|
||||
<div class="process-tree">
|
||||
<div class="process-item selected">
|
||||
<div class="process-icon">CH</div>
|
||||
<span class="process-name">chrome.exe</span>
|
||||
<span class="connection-count">15</span>
|
||||
</div>
|
||||
<div class="process-item">
|
||||
<div class="process-icon">ST</div>
|
||||
<span class="process-name">steam.exe</span>
|
||||
<span class="connection-count">7</span>
|
||||
</div>
|
||||
<div class="process-item">
|
||||
<div class="process-icon">SV</div>
|
||||
<span class="process-name">svchost.exe</span>
|
||||
<span class="connection-count">23</span>
|
||||
</div>
|
||||
<div class="process-item">
|
||||
<div class="process-icon">VS</div>
|
||||
<span class="process-name">Code.exe</span>
|
||||
<span class="connection-count">5</span>
|
||||
</div>
|
||||
<div class="process-item">
|
||||
<div class="process-icon">DC</div>
|
||||
<span class="process-name">Discord.exe</span>
|
||||
<span class="connection-count">3</span>
|
||||
</div>
|
||||
<div class="process-item">
|
||||
<div class="process-icon">UP</div>
|
||||
<span class="process-name">UpdateService.exe</span>
|
||||
<span class="connection-count">1</span>
|
||||
</div>
|
||||
<div class="process-item">
|
||||
<div class="process-icon">WI</div>
|
||||
<span class="process-name">winlogon.exe</span>
|
||||
<span class="connection-count">2</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧主视图 -->
|
||||
<div class="main-view">
|
||||
<div class="view-header">
|
||||
<span class="view-title">chrome.exe 的网络连接 (15个活跃)</span>
|
||||
<div class="view-controls">
|
||||
<input type="text" class="control-input" placeholder="过滤..." style="width: 120px;">
|
||||
<select class="control-input" style="width: 80px;">
|
||||
<option>全部</option>
|
||||
<option>TCP</option>
|
||||
<option>UDP</option>
|
||||
</select>
|
||||
<select class="control-input" style="width: 80px;">
|
||||
<option>全部</option>
|
||||
<option>建立</option>
|
||||
<option>监听</option>
|
||||
<option>已阻止</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="connections-grid">
|
||||
<div class="grid-container">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 60px;">状态</th>
|
||||
<th style="width: 80px;">协议</th>
|
||||
<th style="width: 140px;">本地地址</th>
|
||||
<th style="width: 140px;">远程地址</th>
|
||||
<th style="width: 200px;">远程主机</th>
|
||||
<th style="width: 80px;">网速</th>
|
||||
<th style="width: 80px;">发送</th>
|
||||
<th style="width: 80px;">接收</th>
|
||||
<th style="width: 100px;">持续时间</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="selected">
|
||||
<td><span class="status-dot established"></span>已建立</td>
|
||||
<td>TCP</td>
|
||||
<td class="cell-content">192.168.1.100:54321</td>
|
||||
<td class="cell-content">74.125.224.72:443</td>
|
||||
<td class="cell-content">google.com</td>
|
||||
<td>
|
||||
<div class="speed-cell">
|
||||
<div class="speed-bar">
|
||||
<div class="speed-fill" style="width: 75%"></div>
|
||||
</div>
|
||||
<span class="speed-text">1.2 MB/s</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="cell-content">45.2 MB</td>
|
||||
<td class="cell-content">156.8 MB</td>
|
||||
<td class="cell-content">00:02:34</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="status-dot established"></span>已建立</td>
|
||||
<td>TCP</td>
|
||||
<td class="cell-content">192.168.1.100:54322</td>
|
||||
<td class="cell-content">172.217.24.14:443</td>
|
||||
<td class="cell-content">googleapis.com</td>
|
||||
<td>
|
||||
<div class="speed-cell">
|
||||
<div class="speed-bar">
|
||||
<div class="speed-fill" style="width: 25%"></div>
|
||||
</div>
|
||||
<span class="speed-text">256 KB/s</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="cell-content">2.1 MB</td>
|
||||
<td class="cell-content">8.3 MB</td>
|
||||
<td class="cell-content">00:01:12</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="status-dot blocked"></span>已阻止</td>
|
||||
<td>TCP</td>
|
||||
<td class="cell-content">192.168.1.100:54323</td>
|
||||
<td class="cell-content">185.199.108.153:443</td>
|
||||
<td class="cell-content">github.com</td>
|
||||
<td>
|
||||
<div class="speed-cell">
|
||||
<div class="speed-bar">
|
||||
<div class="speed-fill" style="width: 0%"></div>
|
||||
</div>
|
||||
<span class="speed-text">0 B/s</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="cell-content">0 B</td>
|
||||
<td class="cell-content">0 B</td>
|
||||
<td class="cell-content">-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="status-dot established"></span>已建立</td>
|
||||
<td>TCP</td>
|
||||
<td class="cell-content">192.168.1.100:54324</td>
|
||||
<td class="cell-content">140.82.114.25:443</td>
|
||||
<td class="cell-content">github.com</td>
|
||||
<td>
|
||||
<div class="speed-cell">
|
||||
<div class="speed-bar">
|
||||
<div class="speed-fill" style="width: 15%"></div>
|
||||
</div>
|
||||
<span class="speed-text">84 KB/s</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="cell-content">456 KB</td>
|
||||
<td class="cell-content">1.2 MB</td>
|
||||
<td class="cell-content">00:00:45</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部面板 -->
|
||||
<div class="bottom-panel">
|
||||
<div class="bottom-tabs">
|
||||
<button class="bottom-tab active">连接详情</button>
|
||||
<button class="bottom-tab">事件日志</button>
|
||||
<button class="bottom-tab">防火墙规则</button>
|
||||
<button class="bottom-tab">DNS解析</button>
|
||||
</div>
|
||||
<div class="bottom-content">
|
||||
<div class="log-entry">
|
||||
<span class="log-time">14:32:15</span>
|
||||
<span class="log-level info">INFO</span>
|
||||
<span>chrome.exe (PID: 4852) 建立连接到 google.com:443</span>
|
||||
</div>
|
||||
<div class="log-entry">
|
||||
<span class="log-time">14:32:14</span>
|
||||
<span class="log-level warn">WARN</span>
|
||||
<span>检测到 chrome.exe 尝试连接被阻止的地址 185.199.108.153:443</span>
|
||||
</div>
|
||||
<div class="log-entry">
|
||||
<span class="log-time">14:32:12</span>
|
||||
<span class="log-level info">INFO</span>
|
||||
<span>DNS解析: google.com -> 74.125.224.72</span>
|
||||
</div>
|
||||
<div class="log-entry">
|
||||
<span class="log-time">14:32:10</span>
|
||||
<span class="log-level error">ERROR</span>
|
||||
<span>防火墙规则冲突: 规则 "Block GitHub" 与 "Allow Chrome" 存在冲突</span>
|
||||
</div>
|
||||
<div class="log-entry">
|
||||
<span class="log-time">14:32:08</span>
|
||||
<span class="log-level info">INFO</span>
|
||||
<span>steam.exe (PID: 3421) 连接已断开 104.96.230.4:80</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 状态栏 -->
|
||||
<div class="status-bar">
|
||||
<div class="status-item">
|
||||
<span class="status-indicator active"></span>
|
||||
<span>监控中</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span>247 个活跃连接</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span>网速: ↑ 2.1 MB/s ↓ 5.3 MB/s</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span>今日流量: 1.2 GB</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span>规则: 156 条激活</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-indicator warning"></span>
|
||||
<span>3 个异常连接</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 工具软件交互逻辑
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 进程列表选择
|
||||
document.querySelectorAll('.process-item').forEach(item => {
|
||||
item.addEventListener('click', function() {
|
||||
document.querySelectorAll('.process-item').forEach(i => i.classList.remove('selected'));
|
||||
this.classList.add('selected');
|
||||
|
||||
// 更新主视图标题
|
||||
const processName = this.querySelector('.process-name').textContent;
|
||||
const connectionCount = this.querySelector('.connection-count').textContent;
|
||||
document.querySelector('.view-title').textContent =
|
||||
`${processName} 的网络连接 (${connectionCount}个活跃)`;
|
||||
});
|
||||
});
|
||||
|
||||
// 表格行选择
|
||||
document.querySelectorAll('tbody tr').forEach(row => {
|
||||
row.addEventListener('click', function() {
|
||||
document.querySelectorAll('tbody tr').forEach(r => r.classList.remove('selected'));
|
||||
this.classList.add('selected');
|
||||
});
|
||||
});
|
||||
|
||||
// 底部标签页切换
|
||||
document.querySelectorAll('.bottom-tab').forEach(tab => {
|
||||
tab.addEventListener('click', function() {
|
||||
document.querySelectorAll('.bottom-tab').forEach(t => t.classList.remove('active'));
|
||||
this.classList.add('active');
|
||||
|
||||
// 这里可以切换不同的内容
|
||||
const tabText = this.textContent;
|
||||
console.log('切换到标签页:', tabText);
|
||||
});
|
||||
});
|
||||
|
||||
// 工具栏按钮功能
|
||||
document.querySelectorAll('.toolbar-btn').forEach(btn => {
|
||||
btn.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
if (this.title) {
|
||||
console.log('执行操作:', this.title);
|
||||
|
||||
// 模拟操作反馈
|
||||
const originalText = this.innerHTML;
|
||||
this.style.opacity = '0.5';
|
||||
setTimeout(() => {
|
||||
this.style.opacity = '1';
|
||||
}, 200);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 实时数据更新模拟
|
||||
setInterval(() => {
|
||||
// 更新网速条
|
||||
document.querySelectorAll('.speed-fill').forEach(fill => {
|
||||
const randomWidth = Math.random() * 100;
|
||||
fill.style.width = randomWidth + '%';
|
||||
});
|
||||
|
||||
// 更新网速文本
|
||||
const speeds = ['1.2 MB/s', '856 KB/s', '2.1 MB/s', '124 KB/s', '0 B/s'];
|
||||
document.querySelectorAll('.speed-text').forEach(text => {
|
||||
if (!text.textContent.includes('0 B/s')) {
|
||||
text.textContent = speeds[Math.floor(Math.random() * speeds.length - 1)];
|
||||
}
|
||||
});
|
||||
|
||||
// 更新状态栏网速
|
||||
const statusSpeed = document.querySelector('.status-bar .status-item:nth-child(3) span');
|
||||
if (statusSpeed) {
|
||||
const up = (Math.random() * 5).toFixed(1);
|
||||
const down = (Math.random() * 10).toFixed(1);
|
||||
statusSpeed.textContent = `网速: ↑ ${up} MB/s ↓ ${down} MB/s`;
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
// 搜索过滤功能
|
||||
const searchInput = document.querySelector('input[placeholder="过滤..."]');
|
||||
if (searchInput) {
|
||||
searchInput.addEventListener('input', function() {
|
||||
const searchTerm = this.value.toLowerCase();
|
||||
document.querySelectorAll('tbody tr').forEach(row => {
|
||||
const text = row.textContent.toLowerCase();
|
||||
row.style.display = text.includes(searchTerm) ? '' : 'none';
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 右键菜单模拟
|
||||
document.querySelectorAll('tbody tr').forEach(row => {
|
||||
row.addEventListener('contextmenu', function(e) {
|
||||
e.preventDefault();
|
||||
console.log('右键菜单: 阻止连接、查看详情、复制地址等');
|
||||
});
|
||||
});
|
||||
|
||||
// 键盘快捷键支持
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.ctrlKey) {
|
||||
switch(e.key) {
|
||||
case 'f':
|
||||
e.preventDefault();
|
||||
searchInput?.focus();
|
||||
break;
|
||||
case 'r':
|
||||
e.preventDefault();
|
||||
console.log('刷新数据');
|
||||
break;
|
||||
case 's':
|
||||
e.preventDefault();
|
||||
console.log('停止/开始监控');
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,1310 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>NetGuard - 智能网络监控</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:root {
|
||||
--primary: #6366f1;
|
||||
--primary-dark: #4f46e5;
|
||||
--secondary: #8b5cf6;
|
||||
--accent: #06b6d4;
|
||||
--success: #10b981;
|
||||
--warning: #f59e0b;
|
||||
--danger: #ef4444;
|
||||
|
||||
--bg-primary: #0a0a0b;
|
||||
--bg-secondary: #111113;
|
||||
--bg-tertiary: #1a1a1d;
|
||||
--bg-elevated: #202024;
|
||||
--bg-card: rgba(26, 26, 29, 0.7);
|
||||
|
||||
--text-primary: #f8fafc;
|
||||
--text-secondary: #cbd5e1;
|
||||
--text-muted: #64748b;
|
||||
--text-inverse: #0f172a;
|
||||
|
||||
--border-subtle: rgba(148, 163, 184, 0.1);
|
||||
--border-muted: rgba(148, 163, 184, 0.2);
|
||||
|
||||
--glow-primary: rgba(99, 102, 241, 0.5);
|
||||
--glow-accent: rgba(6, 182, 212, 0.3);
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
|
||||
background: radial-gradient(ellipse at top, var(--bg-secondary) 0%, var(--bg-primary) 100%);
|
||||
color: var(--text-primary);
|
||||
overflow: hidden;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* 全局网格布局 */
|
||||
.app-grid {
|
||||
display: grid;
|
||||
grid-template-areas:
|
||||
"titlebar titlebar titlebar"
|
||||
"sidebar main-view inspector"
|
||||
"sidebar timeline inspector";
|
||||
grid-template-columns: 260px 1fr 320px;
|
||||
grid-template-rows: 40px 1fr 180px;
|
||||
height: 100vh;
|
||||
gap: 1px;
|
||||
background: var(--border-subtle);
|
||||
}
|
||||
|
||||
/* 标题栏 */
|
||||
.titlebar {
|
||||
grid-area: titlebar;
|
||||
background: var(--bg-elevated);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 16px;
|
||||
border-bottom: 1px solid var(--border-subtle);
|
||||
backdrop-filter: blur(20px);
|
||||
}
|
||||
|
||||
.titlebar-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.app-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-weight: 600;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.app-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: linear-gradient(135deg, var(--primary), var(--secondary));
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 0 20px var(--glow-primary);
|
||||
}
|
||||
|
||||
.view-switcher {
|
||||
display: flex;
|
||||
background: var(--bg-tertiary);
|
||||
border-radius: 8px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.view-tab {
|
||||
padding: 6px 12px;
|
||||
border-radius: 6px;
|
||||
font-size: 13px;
|
||||
color: var(--text-muted);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
border: none;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.view-tab.active {
|
||||
background: var(--primary);
|
||||
color: white;
|
||||
box-shadow: 0 2px 8px var(--glow-primary);
|
||||
}
|
||||
|
||||
.titlebar-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.control-btn {
|
||||
width: 32px;
|
||||
height: 24px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
color: var(--text-muted);
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.control-btn:hover {
|
||||
background: var(--bg-tertiary);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.status-indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.status-dot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background: var(--success);
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.5; }
|
||||
}
|
||||
|
||||
/* 侧边栏 */
|
||||
.sidebar {
|
||||
grid-area: sidebar;
|
||||
background: var(--bg-card);
|
||||
backdrop-filter: blur(20px);
|
||||
border-right: 1px solid var(--border-subtle);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sidebar-section {
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid var(--border-subtle);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--text-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.quick-stats {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 8px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.stat-pill {
|
||||
background: var(--bg-tertiary);
|
||||
border: 1px solid var(--border-muted);
|
||||
border-radius: 8px;
|
||||
padding: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: var(--primary);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 10px;
|
||||
color: var(--text-muted);
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.process-list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.process-group {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.group-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 6px 8px;
|
||||
background: var(--bg-tertiary);
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.process-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.process-item:hover {
|
||||
background: var(--bg-tertiary);
|
||||
}
|
||||
|
||||
.process-item.active {
|
||||
background: linear-gradient(135deg, var(--primary), var(--secondary));
|
||||
box-shadow: 0 4px 12px var(--glow-primary);
|
||||
}
|
||||
|
||||
.process-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 6px;
|
||||
margin-right: 10px;
|
||||
background: var(--accent);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.process-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.process-name {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--text-primary);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.process-meta {
|
||||
font-size: 11px;
|
||||
color: var(--text-muted);
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.activity-indicator {
|
||||
width: 32px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.activity-bar {
|
||||
width: 2px;
|
||||
height: 16px;
|
||||
background: var(--bg-tertiary);
|
||||
border-radius: 1px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.activity-fill {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
background: linear-gradient(to top, var(--success), var(--accent));
|
||||
border-radius: 1px;
|
||||
transition: height 0.3s ease;
|
||||
}
|
||||
|
||||
/* 主视图 */
|
||||
.main-view {
|
||||
grid-area: main-view;
|
||||
background: var(--bg-secondary);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.main-header {
|
||||
padding: 16px 20px;
|
||||
background: var(--bg-card);
|
||||
backdrop-filter: blur(20px);
|
||||
border-bottom: 1px solid var(--border-subtle);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.main-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.main-subtitle {
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: var(--bg-tertiary);
|
||||
border: 1px solid var(--border-muted);
|
||||
border-radius: 8px;
|
||||
padding: 0 12px;
|
||||
width: 280px;
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--text-primary);
|
||||
font-size: 13px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.search-input::placeholder {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* 连接网格 */
|
||||
.connections-grid {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.grid-header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background: var(--bg-elevated);
|
||||
backdrop-filter: blur(20px);
|
||||
border-bottom: 1px solid var(--border-subtle);
|
||||
display: grid;
|
||||
grid-template-columns: 140px 100px 180px 180px 1fr 80px 100px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: var(--text-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.grid-header > div {
|
||||
padding: 12px 16px;
|
||||
border-right: 1px solid var(--border-subtle);
|
||||
}
|
||||
|
||||
.grid-content {
|
||||
position: absolute;
|
||||
top: 44px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.connection-row {
|
||||
display: grid;
|
||||
grid-template-columns: 140px 100px 180px 180px 1fr 80px 100px;
|
||||
align-items: center;
|
||||
min-height: 48px;
|
||||
border-bottom: 1px solid var(--border-subtle);
|
||||
transition: all 0.2s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.connection-row:hover {
|
||||
background: var(--bg-card);
|
||||
backdrop-filter: blur(20px);
|
||||
}
|
||||
|
||||
.connection-row.selected {
|
||||
background: linear-gradient(90deg, var(--glow-primary), transparent);
|
||||
border-left: 3px solid var(--primary);
|
||||
}
|
||||
|
||||
.connection-row > div {
|
||||
padding: 0 16px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.status-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.status-badge.established {
|
||||
background: var(--success);
|
||||
box-shadow: 0 0 6px var(--success);
|
||||
}
|
||||
|
||||
.status-badge.listening {
|
||||
background: var(--accent);
|
||||
box-shadow: 0 0 6px var(--accent);
|
||||
}
|
||||
|
||||
.status-badge.blocked {
|
||||
background: var(--danger);
|
||||
box-shadow: 0 0 6px var(--danger);
|
||||
}
|
||||
|
||||
.protocol-badge {
|
||||
padding: 2px 6px;
|
||||
background: var(--bg-tertiary);
|
||||
border: 1px solid var(--border-muted);
|
||||
border-radius: 4px;
|
||||
font-size: 10px;
|
||||
font-weight: 500;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.address-cell {
|
||||
font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.domain-cell {
|
||||
color: var(--accent);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.speed-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.speed-chart {
|
||||
width: 40px;
|
||||
height: 20px;
|
||||
background: var(--bg-tertiary);
|
||||
border-radius: 3px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.speed-wave {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 60%;
|
||||
background: linear-gradient(to top, var(--success), var(--accent));
|
||||
border-radius: 3px 3px 0 0;
|
||||
animation: wave 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes wave {
|
||||
0%, 100% { height: 20%; }
|
||||
50% { height: 80%; }
|
||||
}
|
||||
|
||||
.speed-text {
|
||||
font-size: 11px;
|
||||
color: var(--text-muted);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 检查器面板 */
|
||||
.inspector {
|
||||
grid-area: inspector;
|
||||
background: var(--bg-card);
|
||||
backdrop-filter: blur(20px);
|
||||
border-left: 1px solid var(--border-subtle);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.inspector-header {
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid var(--border-subtle);
|
||||
}
|
||||
|
||||
.inspector-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.inspector-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.detail-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.detail-label {
|
||||
font-size: 11px;
|
||||
color: var(--text-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.detail-value {
|
||||
font-size: 13px;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.detail-value.code {
|
||||
font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
|
||||
background: var(--bg-tertiary);
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.geo-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px;
|
||||
background: var(--bg-tertiary);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.country-flag {
|
||||
width: 20px;
|
||||
height: 15px;
|
||||
background: linear-gradient(45deg, var(--primary), var(--secondary));
|
||||
border-radius: 2px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 8px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* 时间轴 */
|
||||
.timeline {
|
||||
grid-area: timeline;
|
||||
background: var(--bg-card);
|
||||
backdrop-filter: blur(20px);
|
||||
border-top: 1px solid var(--border-subtle);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.timeline-header {
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid var(--border-subtle);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.timeline-title {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.timeline-controls {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.timeline-btn {
|
||||
padding: 4px 8px;
|
||||
background: var(--bg-tertiary);
|
||||
border: 1px solid var(--border-muted);
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.timeline-btn:hover {
|
||||
background: var(--primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.timeline-content {
|
||||
flex: 1;
|
||||
padding: 16px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.event-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
padding: 8px;
|
||||
border-radius: 6px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.event-item:hover {
|
||||
background: var(--bg-tertiary);
|
||||
}
|
||||
|
||||
.event-time {
|
||||
font-size: 10px;
|
||||
color: var(--text-muted);
|
||||
font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
|
||||
min-width: 60px;
|
||||
}
|
||||
|
||||
.event-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 8px;
|
||||
color: white;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.event-icon.connection {
|
||||
background: var(--success);
|
||||
}
|
||||
|
||||
.event-icon.blocked {
|
||||
background: var(--danger);
|
||||
}
|
||||
|
||||
.event-icon.dns {
|
||||
background: var(--accent);
|
||||
}
|
||||
|
||||
.event-content {
|
||||
flex: 1;
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.event-title {
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.event-details {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* 滚动条样式 */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--border-muted);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--text-muted);
|
||||
}
|
||||
|
||||
/* 响应式适配 */
|
||||
@media (max-width: 1400px) {
|
||||
.app-grid {
|
||||
grid-template-columns: 240px 1fr 280px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.app-grid {
|
||||
grid-template-areas:
|
||||
"titlebar titlebar"
|
||||
"sidebar main-view"
|
||||
"timeline timeline";
|
||||
grid-template-columns: 240px 1fr;
|
||||
grid-template-rows: 40px 1fr 160px;
|
||||
}
|
||||
|
||||
.inspector {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-grid">
|
||||
<!-- 标题栏 -->
|
||||
<div class="titlebar">
|
||||
<div class="titlebar-left">
|
||||
<div class="app-title">
|
||||
<div class="app-icon">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M12 2L2 7l10 5 10-5-10-5z"/>
|
||||
<path d="M2 17l10 5 10-5"/>
|
||||
</svg>
|
||||
</div>
|
||||
NetGuard
|
||||
</div>
|
||||
|
||||
<div class="view-switcher">
|
||||
<button class="view-tab active">实时监控</button>
|
||||
<button class="view-tab">防火墙</button>
|
||||
<button class="view-tab">分析</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="titlebar-right">
|
||||
<div class="status-indicator">
|
||||
<div class="status-dot"></div>
|
||||
实时监控中
|
||||
</div>
|
||||
|
||||
<button class="control-btn" title="设置">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="12" cy="12" r="3"/>
|
||||
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1 1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 侧边栏 -->
|
||||
<div class="sidebar">
|
||||
<div class="sidebar-section">
|
||||
<div class="section-title">系统概览</div>
|
||||
<div class="quick-stats">
|
||||
<div class="stat-pill">
|
||||
<div class="stat-value">247</div>
|
||||
<div class="stat-label">活跃连接</div>
|
||||
</div>
|
||||
<div class="stat-pill">
|
||||
<div class="stat-value">43</div>
|
||||
<div class="stat-label">进程数</div>
|
||||
</div>
|
||||
<div class="stat-pill">
|
||||
<div class="stat-value">5.2</div>
|
||||
<div class="stat-label">MB/s</div>
|
||||
</div>
|
||||
<div class="stat-pill">
|
||||
<div class="stat-value">156</div>
|
||||
<div class="stat-label">规则</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="process-list">
|
||||
<div class="process-group">
|
||||
<div class="group-header">
|
||||
<span>浏览器应用</span>
|
||||
<span>23</span>
|
||||
</div>
|
||||
|
||||
<div class="process-item active">
|
||||
<div class="process-icon" style="background: #4285f4;">CH</div>
|
||||
<div class="process-info">
|
||||
<div class="process-name">Chrome</div>
|
||||
<div class="process-meta">
|
||||
<span>15 连接</span>
|
||||
<span>2.1 MB/s</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="activity-indicator">
|
||||
<div class="activity-bar">
|
||||
<div class="activity-fill" style="height: 75%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="process-item">
|
||||
<div class="process-icon" style="background: #ff6b35;">FF</div>
|
||||
<div class="process-info">
|
||||
<div class="process-name">Firefox</div>
|
||||
<div class="process-meta">
|
||||
<span>8 连接</span>
|
||||
<span>456 KB/s</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="activity-indicator">
|
||||
<div class="activity-bar">
|
||||
<div class="activity-fill" style="height: 35%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="process-group">
|
||||
<div class="group-header">
|
||||
<span>开发工具</span>
|
||||
<span>12</span>
|
||||
</div>
|
||||
|
||||
<div class="process-item">
|
||||
<div class="process-icon" style="background: #007acc;">VS</div>
|
||||
<div class="process-info">
|
||||
<div class="process-name">Visual Studio Code</div>
|
||||
<div class="process-meta">
|
||||
<span>5 连接</span>
|
||||
<span>128 KB/s</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="activity-indicator">
|
||||
<div class="activity-bar">
|
||||
<div class="activity-fill" style="height: 20%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="process-group">
|
||||
<div class="group-header">
|
||||
<span>系统服务</span>
|
||||
<span>8</span>
|
||||
</div>
|
||||
|
||||
<div class="process-item">
|
||||
<div class="process-icon" style="background: #00bcf2;">SV</div>
|
||||
<div class="process-info">
|
||||
<div class="process-name">svchost.exe</div>
|
||||
<div class="process-meta">
|
||||
<span>23 连接</span>
|
||||
<span>89 KB/s</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="activity-indicator">
|
||||
<div class="activity-bar">
|
||||
<div class="activity-fill" style="height: 45%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 主视图 -->
|
||||
<div class="main-view">
|
||||
<div class="main-header">
|
||||
<div>
|
||||
<div class="main-title">Chrome 网络活动</div>
|
||||
<div class="main-subtitle">15 个活跃连接 • 平均速度 2.1 MB/s</div>
|
||||
</div>
|
||||
|
||||
<div class="search-bar">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="11" cy="11" r="8"/>
|
||||
<path d="M21 21l-4.35-4.35"/>
|
||||
</svg>
|
||||
<input type="text" class="search-input" placeholder="搜索连接、域名或IP地址...">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="connections-grid">
|
||||
<div class="grid-header">
|
||||
<div>状态</div>
|
||||
<div>协议</div>
|
||||
<div>本地地址</div>
|
||||
<div>远程地址</div>
|
||||
<div>域名/主机</div>
|
||||
<div>网速</div>
|
||||
<div>流量</div>
|
||||
</div>
|
||||
|
||||
<div class="grid-content">
|
||||
<div class="connection-row selected">
|
||||
<div class="status-cell">
|
||||
<div class="status-badge established"></div>
|
||||
<span>已建立</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="protocol-badge">TCP</span>
|
||||
</div>
|
||||
<div class="address-cell">192.168.1.100:54321</div>
|
||||
<div class="address-cell">74.125.224.72:443</div>
|
||||
<div class="domain-cell">google.com</div>
|
||||
<div class="speed-cell">
|
||||
<div class="speed-chart">
|
||||
<div class="speed-wave"></div>
|
||||
</div>
|
||||
<span class="speed-text">1.2 MB/s</span>
|
||||
</div>
|
||||
<div>156.8 MB</div>
|
||||
</div>
|
||||
|
||||
<div class="connection-row">
|
||||
<div class="status-cell">
|
||||
<div class="status-badge established"></div>
|
||||
<span>已建立</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="protocol-badge">TCP</span>
|
||||
</div>
|
||||
<div class="address-cell">192.168.1.100:54322</div>
|
||||
<div class="address-cell">172.217.24.14:443</div>
|
||||
<div class="domain-cell">googleapis.com</div>
|
||||
<div class="speed-cell">
|
||||
<div class="speed-chart">
|
||||
<div class="speed-wave" style="animation-delay: 0.5s;"></div>
|
||||
</div>
|
||||
<span class="speed-text">456 KB/s</span>
|
||||
</div>
|
||||
<div>8.3 MB</div>
|
||||
</div>
|
||||
|
||||
<div class="connection-row">
|
||||
<div class="status-cell">
|
||||
<div class="status-badge blocked"></div>
|
||||
<span>已阻止</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="protocol-badge">TCP</span>
|
||||
</div>
|
||||
<div class="address-cell">192.168.1.100:54323</div>
|
||||
<div class="address-cell">185.199.108.153:443</div>
|
||||
<div class="domain-cell">github.com</div>
|
||||
<div class="speed-cell">
|
||||
<div class="speed-chart">
|
||||
<div class="speed-wave" style="height: 0;"></div>
|
||||
</div>
|
||||
<span class="speed-text">0 B/s</span>
|
||||
</div>
|
||||
<div>0 B</div>
|
||||
</div>
|
||||
|
||||
<div class="connection-row">
|
||||
<div class="status-cell">
|
||||
<div class="status-badge listening"></div>
|
||||
<span>监听中</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="protocol-badge">UDP</span>
|
||||
</div>
|
||||
<div class="address-cell">0.0.0.0:53</div>
|
||||
<div class="address-cell">-</div>
|
||||
<div class="domain-cell">DNS服务</div>
|
||||
<div class="speed-cell">
|
||||
<div class="speed-chart">
|
||||
<div class="speed-wave" style="animation-delay: 1s; height: 30%;"></div>
|
||||
</div>
|
||||
<span class="speed-text">12 KB/s</span>
|
||||
</div>
|
||||
<div>2.1 MB</div>
|
||||
</div>
|
||||
|
||||
<div class="connection-row">
|
||||
<div class="status-cell">
|
||||
<div class="status-badge established"></div>
|
||||
<span>已建立</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="protocol-badge">TCP</span>
|
||||
</div>
|
||||
<div class="address-cell">192.168.1.100:54324</div>
|
||||
<div class="address-cell">140.82.114.25:443</div>
|
||||
<div class="domain-cell">github.com</div>
|
||||
<div class="speed-cell">
|
||||
<div class="speed-chart">
|
||||
<div class="speed-wave" style="animation-delay: 1.5s; height: 40%;"></div>
|
||||
</div>
|
||||
<span class="speed-text">84 KB/s</span>
|
||||
</div>
|
||||
<div>1.2 MB</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 检查器面板 -->
|
||||
<div class="inspector">
|
||||
<div class="inspector-header">
|
||||
<div class="inspector-title">连接详情</div>
|
||||
</div>
|
||||
|
||||
<div class="inspector-content">
|
||||
<div class="detail-group">
|
||||
<div class="detail-label">连接信息</div>
|
||||
<div class="detail-value">状态: 已建立 (ESTABLISHED)</div>
|
||||
<div class="detail-value">协议: TCP</div>
|
||||
<div class="detail-value">持续时间: 00:02:34</div>
|
||||
<div class="detail-value">PID: 4852</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-group">
|
||||
<div class="detail-label">本地端点</div>
|
||||
<div class="detail-value code">192.168.1.100:54321</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-group">
|
||||
<div class="detail-label">远程端点</div>
|
||||
<div class="detail-value code">74.125.224.72:443</div>
|
||||
<div class="detail-value">google.com</div>
|
||||
|
||||
<div class="geo-info">
|
||||
<div class="country-flag">US</div>
|
||||
<div>
|
||||
<div style="font-size: 12px; font-weight: 500;">美国</div>
|
||||
<div style="font-size: 10px; color: var(--text-muted);">加利福尼亚州,山景城</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-group">
|
||||
<div class="detail-label">流量统计</div>
|
||||
<div class="detail-value">发送: 45.2 MB</div>
|
||||
<div class="detail-value">接收: 156.8 MB</div>
|
||||
<div class="detail-value">当前速度: 1.2 MB/s</div>
|
||||
<div class="detail-value">峰值速度: 3.8 MB/s</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-group">
|
||||
<div class="detail-label">进程信息</div>
|
||||
<div class="detail-value">chrome.exe</div>
|
||||
<div class="detail-value" style="font-size: 11px; color: var(--text-muted);">
|
||||
C:\Program Files\Google\Chrome\Application\chrome.exe
|
||||
</div>
|
||||
<div class="detail-value">版本: 120.0.6099.129</div>
|
||||
<div class="detail-value">签名: Google LLC (已验证)</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-group">
|
||||
<div class="detail-label">安全信息</div>
|
||||
<div class="detail-value">TLS 1.3</div>
|
||||
<div class="detail-value">证书: *.google.com</div>
|
||||
<div class="detail-value" style="color: var(--success);">✓ 证书有效</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 时间轴 -->
|
||||
<div class="timeline">
|
||||
<div class="timeline-header">
|
||||
<div class="timeline-title">实时事件流</div>
|
||||
<div class="timeline-controls">
|
||||
<button class="timeline-btn">暂停</button>
|
||||
<button class="timeline-btn">清空</button>
|
||||
<button class="timeline-btn">导出</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="timeline-content">
|
||||
<div class="event-item">
|
||||
<div class="event-time">14:32:15</div>
|
||||
<div class="event-icon connection">→</div>
|
||||
<div class="event-content">
|
||||
<div class="event-title">新连接建立</div>
|
||||
<div class="event-details">chrome.exe → google.com:443 (TLS)</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="event-item">
|
||||
<div class="event-time">14:32:14</div>
|
||||
<div class="event-icon blocked">✕</div>
|
||||
<div class="event-content">
|
||||
<div class="event-title">连接被阻止</div>
|
||||
<div class="event-details">chrome.exe → github.com:443 (规则: Block GitHub)</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="event-item">
|
||||
<div class="event-time">14:32:12</div>
|
||||
<div class="event-icon dns">?</div>
|
||||
<div class="event-content">
|
||||
<div class="event-title">DNS 查询</div>
|
||||
<div class="event-details">google.com → 74.125.224.72 (89ms)</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="event-item">
|
||||
<div class="event-time">14:32:10</div>
|
||||
<div class="event-icon connection">←</div>
|
||||
<div class="event-content">
|
||||
<div class="event-title">连接关闭</div>
|
||||
<div class="event-details">steam.exe ← 104.96.230.4:80 (正常断开)</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="event-item">
|
||||
<div class="event-time">14:32:08</div>
|
||||
<div class="event-icon connection">→</div>
|
||||
<div class="event-content">
|
||||
<div class="event-title">高速传输检测</div>
|
||||
<div class="event-details">chrome.exe → google.com 峰值速度 3.8 MB/s</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="event-item">
|
||||
<div class="event-time">14:32:05</div>
|
||||
<div class="event-icon dns">?</div>
|
||||
<div class="event-content">
|
||||
<div class="event-title">DNS 查询</div>
|
||||
<div class="event-details">googleapis.com → 172.217.24.14 (45ms)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 现代化交互逻辑
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 进程选择交互
|
||||
document.querySelectorAll('.process-item').forEach(item => {
|
||||
item.addEventListener('click', function() {
|
||||
document.querySelectorAll('.process-item').forEach(i => i.classList.remove('active'));
|
||||
this.classList.add('active');
|
||||
|
||||
// 更新主视图内容
|
||||
const processName = this.querySelector('.process-name').textContent;
|
||||
const connections = this.querySelector('.process-meta').textContent.split(' ')[0];
|
||||
const speed = this.querySelector('.process-meta').textContent.split('• ')[1] || '0 B/s';
|
||||
|
||||
document.querySelector('.main-title').textContent = `${processName} 网络活动`;
|
||||
document.querySelector('.main-subtitle').textContent = `${connections} 个活跃连接 • 平均速度 ${speed}`;
|
||||
});
|
||||
});
|
||||
|
||||
// 连接行选择
|
||||
document.querySelectorAll('.connection-row').forEach(row => {
|
||||
row.addEventListener('click', function() {
|
||||
document.querySelectorAll('.connection-row').forEach(r => r.classList.remove('selected'));
|
||||
this.classList.add('selected');
|
||||
|
||||
// 这里可以更新检查器面板的内容
|
||||
console.log('选中连接,更新详情面板');
|
||||
});
|
||||
});
|
||||
|
||||
// 视图切换
|
||||
document.querySelectorAll('.view-tab').forEach(tab => {
|
||||
tab.addEventListener('click', function() {
|
||||
document.querySelectorAll('.view-tab').forEach(t => t.classList.remove('active'));
|
||||
this.classList.add('active');
|
||||
|
||||
// 切换视图逻辑
|
||||
console.log('切换视图:', this.textContent);
|
||||
});
|
||||
});
|
||||
|
||||
// 搜索功能
|
||||
const searchInput = document.querySelector('.search-input');
|
||||
searchInput.addEventListener('input', function() {
|
||||
const searchTerm = this.value.toLowerCase();
|
||||
document.querySelectorAll('.connection-row').forEach(row => {
|
||||
const text = row.textContent.toLowerCase();
|
||||
row.style.display = text.includes(searchTerm) ? 'grid' : 'none';
|
||||
});
|
||||
});
|
||||
|
||||
// 时间轴控制
|
||||
document.querySelectorAll('.timeline-btn').forEach(btn => {
|
||||
btn.addEventListener('click', function() {
|
||||
const action = this.textContent;
|
||||
console.log('时间轴操作:', action);
|
||||
|
||||
if (action === '暂停') {
|
||||
this.textContent = '继续';
|
||||
} else if (action === '继续') {
|
||||
this.textContent = '暂停';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 实时数据模拟
|
||||
setInterval(() => {
|
||||
// 更新活动指示器
|
||||
document.querySelectorAll('.activity-fill').forEach(fill => {
|
||||
const height = Math.random() * 100;
|
||||
fill.style.height = height + '%';
|
||||
});
|
||||
|
||||
// 随机添加新事件
|
||||
if (Math.random() > 0.7) {
|
||||
addNewEvent();
|
||||
}
|
||||
|
||||
// 更新速度文本
|
||||
document.querySelectorAll('.speed-text').forEach(text => {
|
||||
if (!text.textContent.includes('0 B/s')) {
|
||||
const speeds = ['1.2 MB/s', '856 KB/s', '2.1 MB/s', '124 KB/s'];
|
||||
text.textContent = speeds[Math.floor(Math.random() * speeds.length)];
|
||||
}
|
||||
});
|
||||
}, 2000);
|
||||
|
||||
// 添加新事件到时间轴
|
||||
function addNewEvent() {
|
||||
const events = [
|
||||
{ type: 'connection', title: '新连接建立', details: 'chrome.exe → example.com:443' },
|
||||
{ type: 'dns', title: 'DNS 查询', details: 'example.com → 93.184.216.34' },
|
||||
{ type: 'blocked', title: '连接被阻止', details: 'app.exe → suspicious.site:80' }
|
||||
];
|
||||
|
||||
const event = events[Math.floor(Math.random() * events.length)];
|
||||
const now = new Date();
|
||||
const timeStr = now.toLocaleTimeString('zh-CN', { hour12: false }).slice(0, 8);
|
||||
|
||||
const eventElement = document.createElement('div');
|
||||
eventElement.className = 'event-item';
|
||||
eventElement.innerHTML = `
|
||||
<div class="event-time">${timeStr}</div>
|
||||
<div class="event-icon ${event.type}">${event.type === 'connection' ? '→' : event.type === 'dns' ? '?' : '✕'}</div>
|
||||
<div class="event-content">
|
||||
<div class="event-title">${event.title}</div>
|
||||
<div class="event-details">${event.details}</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const timelineContent = document.querySelector('.timeline-content');
|
||||
timelineContent.insertBefore(eventElement, timelineContent.firstChild);
|
||||
|
||||
// 限制事件数量
|
||||
const events_list = timelineContent.children;
|
||||
if (events_list.length > 10) {
|
||||
timelineContent.removeChild(events_list[events_list.length - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
// 键盘快捷键
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
switch(e.key) {
|
||||
case 'f':
|
||||
e.preventDefault();
|
||||
searchInput.focus();
|
||||
break;
|
||||
case '1':
|
||||
e.preventDefault();
|
||||
document.querySelector('.view-tab').click();
|
||||
break;
|
||||
case '2':
|
||||
e.preventDefault();
|
||||
document.querySelectorAll('.view-tab')[1].click();
|
||||
break;
|
||||
case '3':
|
||||
e.preventDefault();
|
||||
document.querySelectorAll('.view-tab')[2].click();
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,760 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>NetGuard - 现代化网络监控工具</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:root {
|
||||
--primary-color: #2563eb;
|
||||
--primary-hover: #1d4ed8;
|
||||
--secondary-color: #64748b;
|
||||
--success-color: #10b981;
|
||||
--warning-color: #f59e0b;
|
||||
--danger-color: #ef4444;
|
||||
--bg-primary: #0f172a;
|
||||
--bg-secondary: #1e293b;
|
||||
--bg-tertiary: #334155;
|
||||
--text-primary: #f8fafc;
|
||||
--text-secondary: #cbd5e1;
|
||||
--text-muted: #64748b;
|
||||
--border-color: #334155;
|
||||
--glass-bg: rgba(30, 41, 59, 0.7);
|
||||
--glass-border: rgba(148, 163, 184, 0.1);
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
|
||||
color: var(--text-primary);
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.app-container {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 侧边栏 */
|
||||
.sidebar {
|
||||
width: 280px;
|
||||
background: var(--glass-bg);
|
||||
backdrop-filter: blur(20px);
|
||||
border-right: 1px solid var(--glass-border);
|
||||
padding: 24px;
|
||||
overflow-y: auto;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 32px;
|
||||
padding-bottom: 24px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.logo-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: linear-gradient(45deg, var(--primary-color), #3b82f6);
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 12px;
|
||||
box-shadow: 0 4px 20px rgba(37, 99, 235, 0.3);
|
||||
}
|
||||
|
||||
.logo-text {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
background: linear-gradient(45deg, var(--primary-color), #3b82f6);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
.nav-menu {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
border-radius: 12px;
|
||||
text-decoration: none;
|
||||
color: var(--text-secondary);
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.nav-link::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(37, 99, 235, 0.1), transparent);
|
||||
transition: left 0.5s ease;
|
||||
}
|
||||
|
||||
.nav-link:hover::before {
|
||||
left: 100%;
|
||||
}
|
||||
|
||||
.nav-link:hover, .nav-link.active {
|
||||
background: rgba(37, 99, 235, 0.1);
|
||||
color: var(--text-primary);
|
||||
transform: translateX(4px);
|
||||
}
|
||||
|
||||
.nav-link.active {
|
||||
background: linear-gradient(135deg, rgba(37, 99, 235, 0.2), rgba(59, 130, 246, 0.1));
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-right: 12px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.nav-link:hover .nav-icon, .nav-link.active .nav-icon {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* 主内容区 */
|
||||
.main-content {
|
||||
flex: 1;
|
||||
padding: 24px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 32px;
|
||||
padding-bottom: 24px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
background: linear-gradient(135deg, var(--text-primary), var(--text-secondary));
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 10px 20px;
|
||||
border-radius: 10px;
|
||||
border: none;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, var(--primary-color), #3b82f6);
|
||||
color: white;
|
||||
box-shadow: 0 4px 20px rgba(37, 99, 235, 0.3);
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 25px rgba(37, 99, 235, 0.4);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: var(--glass-bg);
|
||||
color: var(--text-secondary);
|
||||
border: 1px solid var(--glass-border);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: var(--bg-tertiary);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* 仪表板网格 */
|
||||
.dashboard-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 24px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: var(--glass-bg);
|
||||
backdrop-filter: blur(20px);
|
||||
border-radius: 16px;
|
||||
border: 1px solid var(--glass-border);
|
||||
padding: 24px;
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 1px;
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.1), transparent);
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 36px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 8px;
|
||||
background: linear-gradient(135deg, var(--primary-color), #3b82f6);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
color: var(--text-secondary);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.stat-change {
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.stat-change.positive {
|
||||
color: var(--success-color);
|
||||
}
|
||||
|
||||
.stat-change.negative {
|
||||
color: var(--danger-color);
|
||||
}
|
||||
|
||||
/* 连接表格 */
|
||||
.connections-table {
|
||||
background: var(--glass-bg);
|
||||
backdrop-filter: blur(20px);
|
||||
border-radius: 16px;
|
||||
border: 1px solid var(--glass-border);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.table-header {
|
||||
padding: 20px 24px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.table-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 16px 24px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
th {
|
||||
background: rgba(37, 99, 235, 0.05);
|
||||
font-weight: 600;
|
||||
color: var(--text-secondary);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
tr:hover {
|
||||
background: rgba(37, 99, 235, 0.05);
|
||||
}
|
||||
|
||||
.process-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.process-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background: linear-gradient(135deg, var(--primary-color), #3b82f6);
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 4px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.status-active {
|
||||
background: rgba(16, 185, 129, 0.2);
|
||||
color: var(--success-color);
|
||||
}
|
||||
|
||||
.status-blocked {
|
||||
background: rgba(239, 68, 68, 0.2);
|
||||
color: var(--danger-color);
|
||||
}
|
||||
|
||||
.speed-indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.speed-bar {
|
||||
width: 60px;
|
||||
height: 4px;
|
||||
background: var(--bg-tertiary);
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.speed-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, var(--success-color), var(--warning-color));
|
||||
border-radius: 2px;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
/* 工具栏 */
|
||||
.toolbar {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-bottom: 24px;
|
||||
padding: 16px;
|
||||
background: var(--glass-bg);
|
||||
backdrop-filter: blur(20px);
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--glass-border);
|
||||
}
|
||||
|
||||
.search-box {
|
||||
flex: 1;
|
||||
padding: 10px 16px;
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
color: var(--text-primary);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.search-box::placeholder {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.filter-select {
|
||||
padding: 10px 16px;
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
color: var(--text-primary);
|
||||
font-size: 14px;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.sidebar {
|
||||
width: 60px;
|
||||
padding: 24px 12px;
|
||||
}
|
||||
|
||||
.logo-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
justify-content: center;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.nav-link span {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dashboard-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* 动画效果 */
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.fade-in {
|
||||
animation: fadeIn 0.5s ease-out;
|
||||
}
|
||||
|
||||
/* 滚动条样式 */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--bg-tertiary);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--secondary-color);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-container">
|
||||
<!-- 侧边栏 -->
|
||||
<nav class="sidebar">
|
||||
<div class="logo">
|
||||
<div class="logo-icon">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M12 2L2 7l10 5 10-5-10-5z"/>
|
||||
<path d="M2 17l10 5 10-5"/>
|
||||
<path d="M2 12l10 5 10-5"/>
|
||||
</svg>
|
||||
</div>
|
||||
<span class="logo-text">NetGuard</span>
|
||||
</div>
|
||||
|
||||
<ul class="nav-menu">
|
||||
<li class="nav-item">
|
||||
<a href="#" class="nav-link active">
|
||||
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<rect x="3" y="3" width="7" height="7"/>
|
||||
<rect x="14" y="3" width="7" height="7"/>
|
||||
<rect x="14" y="14" width="7" height="7"/>
|
||||
<rect x="3" y="14" width="7" height="7"/>
|
||||
</svg>
|
||||
<span>仪表板</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="#" class="nav-link">
|
||||
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/>
|
||||
<rect x="8" y="2" width="8" height="4" rx="1" ry="1"/>
|
||||
</svg>
|
||||
<span>实时连接</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="#" class="nav-link">
|
||||
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M12 2v20M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/>
|
||||
</svg>
|
||||
<span>进程监控</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="#" class="nav-link">
|
||||
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<rect x="2" y="3" width="20" height="14" rx="2" ry="2"/>
|
||||
<line x1="8" y1="21" x2="16" y2="21"/>
|
||||
<line x1="12" y1="17" x2="12" y2="21"/>
|
||||
</svg>
|
||||
<span>防火墙规则</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="#" class="nav-link">
|
||||
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M18 20V10"/>
|
||||
<path d="M12 20V4"/>
|
||||
<path d="M6 20v-6"/>
|
||||
</svg>
|
||||
<span>统计分析</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="#" class="nav-link">
|
||||
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="12" cy="12" r="3"/>
|
||||
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1 1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/>
|
||||
</svg>
|
||||
<span>设置</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<!-- 主内容区 -->
|
||||
<main class="main-content">
|
||||
<div class="header">
|
||||
<h1 class="page-title">网络监控仪表板</h1>
|
||||
<div class="header-actions">
|
||||
<button class="btn btn-secondary">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/>
|
||||
<circle cx="12" cy="12" r="3"/>
|
||||
</svg>
|
||||
查看详情
|
||||
</button>
|
||||
<button class="btn btn-primary">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M12 2L2 7l10 5 10-5-10-5z"/>
|
||||
<path d="M2 17l10 5 10-5"/>
|
||||
<path d="M2 12l10 5 10-5"/>
|
||||
</svg>
|
||||
添加规则
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<div class="dashboard-grid fade-in">
|
||||
<div class="card stat-card">
|
||||
<div class="stat-value">247</div>
|
||||
<div class="stat-label">活跃连接</div>
|
||||
<div class="stat-change positive">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M7 14l5-5 5 5"/>
|
||||
</svg>
|
||||
+12%
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card stat-card">
|
||||
<div class="stat-value">1.2 GB</div>
|
||||
<div class="stat-label">今日流量</div>
|
||||
<div class="stat-change positive">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M7 14l5-5 5 5"/>
|
||||
</svg>
|
||||
+8%
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card stat-card">
|
||||
<div class="stat-value">43</div>
|
||||
<div class="stat-label">活跃进程</div>
|
||||
<div class="stat-change negative">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M17 10l-5 5-5-5"/>
|
||||
</svg>
|
||||
-3%
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card stat-card">
|
||||
<div class="stat-value">156</div>
|
||||
<div class="stat-label">防火墙规则</div>
|
||||
<div class="stat-change positive">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M7 14l5-5 5 5"/>
|
||||
</svg>
|
||||
+2
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 工具栏 -->
|
||||
<div class="toolbar">
|
||||
<input type="text" class="search-box" placeholder="搜索进程、IP或端口...">
|
||||
<select class="filter-select">
|
||||
<option>所有连接</option>
|
||||
<option>活跃连接</option>
|
||||
<option>已阻止</option>
|
||||
</select>
|
||||
<select class="filter-select">
|
||||
<option>所有协议</option>
|
||||
<option>TCP</option>
|
||||
<option>UDP</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- 连接表格 -->
|
||||
<div class="connections-table fade-in">
|
||||
<div class="table-header">
|
||||
<h2 class="table-title">实时网络连接</h2>
|
||||
<span class="stat-change positive">实时更新</span>
|
||||
</div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>进程</th>
|
||||
<th>本地地址</th>
|
||||
<th>远程地址</th>
|
||||
<th>协议</th>
|
||||
<th>状态</th>
|
||||
<th>网速</th>
|
||||
<th>流量</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="process-info">
|
||||
<div class="process-icon">CH</div>
|
||||
<div>
|
||||
<div>Chrome.exe</div>
|
||||
<div style="font-size: 12px; color: var(--text-muted);">Google Chrome</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>192.168.1.100:54321</td>
|
||||
<td>74.125.224.72:443</td>
|
||||
<td>TCP</td>
|
||||
<td><span class="status-badge status-active">活跃</span></td>
|
||||
<td>
|
||||
<div class="speed-indicator">
|
||||
<span>1.2 MB/s</span>
|
||||
<div class="speed-bar">
|
||||
<div class="speed-fill" style="width: 75%"></div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>245.6 MB</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="process-info">
|
||||
<div class="process-icon">ST</div>
|
||||
<div>
|
||||
<div>Steam.exe</div>
|
||||
<div style="font-size: 12px; color: var(--text-muted);">Steam Client</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>192.168.1.100:27036</td>
|
||||
<td>104.96.230.4:80</td>
|
||||
<td>TCP</td>
|
||||
<td><span class="status-badge status-active">活跃</span></td>
|
||||
<td>
|
||||
<div class="speed-indicator">
|
||||
<span>456 KB/s</span>
|
||||
<div class="speed-bar">
|
||||
<div class="speed-fill" style="width: 35%"></div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>89.2 MB</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="process-info">
|
||||
<div class="process-icon">SV</div>
|
||||
<div>
|
||||
<div>svchost.exe</div>
|
||||
<div style="font-size: 12px; color: var(--text-muted);">Windows Service</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>192.168.1.100:49152</td>
|
||||
<td>20.42.73.24:443</td>
|
||||
<td>TCP</td>
|
||||
<td><span class="status-badge status-blocked">已阻止</span></td>
|
||||
<td>
|
||||
<div class="speed-indicator">
|
||||
<span>0 B/s</span>
|
||||
<div class="speed-bar">
|
||||
<div class="speed-fill" style="width: 0%"></div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>0 B</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 简单的交互效果
|
||||
document.querySelectorAll('.nav-link').forEach(link => {
|
||||
link.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
document.querySelectorAll('.nav-link').forEach(l => l.classList.remove('active'));
|
||||
link.classList.add('active');
|
||||
});
|
||||
});
|
||||
|
||||
// 模拟数据更新
|
||||
setInterval(() => {
|
||||
document.querySelectorAll('.speed-fill').forEach(fill => {
|
||||
const randomWidth = Math.random() * 100;
|
||||
fill.style.width = randomWidth + '%';
|
||||
});
|
||||
}, 2000);
|
||||
|
||||
// 添加搜索功能
|
||||
document.querySelector('.search-box').addEventListener('input', (e) => {
|
||||
const searchTerm = e.target.value.toLowerCase();
|
||||
document.querySelectorAll('tbody tr').forEach(row => {
|
||||
const text = row.textContent.toLowerCase();
|
||||
row.style.display = text.includes(searchTerm) ? '' : 'none';
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in new issue
Block a user