A:剩余代码提交
This commit is contained in:
1 parent
1d30e4fe77
commit
87f94a216b
9 files changed
+1152
-31
No files matched your search
@@ -51,7 +51,7 @@ public class NetworkDataCollectionService : IDisposable
|
||||
|
||||
// 每小时检测清理并合并旧数据
|
||||
_dataCleanupTimer = new Timer(TriggerDataCleanupAndAggregation, null,
|
||||
TimeSpan.FromHours(1), TimeSpan.FromHours(1));
|
||||
TimeSpan.FromMinutes(30), TimeSpan.FromMinutes(30));
|
||||
|
||||
Log.Information("网络数据收集服务已启动");
|
||||
}
|
||||
@@ -378,4 +378,4 @@ public class NetworkCollectionStats
|
||||
public DateTime LastNetworkInterfaceUpdate { get; set; }
|
||||
public int CachedProcessCount { get; set; }
|
||||
public int ActiveConnectionsCount { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1180,7 +1180,7 @@ public partial class App : System.Windows.Application
|
||||
string displayName = app.AppName ?? "";
|
||||
string iconBase64 = "";
|
||||
|
||||
if (string.IsNullOrEmpty(displayName) && !string.IsNullOrEmpty(app.AppId))
|
||||
if (!string.IsNullOrEmpty(displayName) && !string.IsNullOrEmpty(app.AppId))
|
||||
{
|
||||
// 如果JOIN没有获取到应用名称,尝试单独查询
|
||||
var appInfo = await DatabaseManager.Instance.AppInfos.GetAppByAppIdAsync(app.AppId);
|
||||
@@ -1271,8 +1271,12 @@ public partial class App : System.Windows.Application
|
||||
string processName = System.IO.Path.GetFileName(app.AppPath ?? "");
|
||||
string displayName = app.AppName ?? "";
|
||||
string iconBase64 = "";
|
||||
|
||||
if (string.IsNullOrEmpty(displayName) && !string.IsNullOrEmpty(app.AppId))
|
||||
string version = "";
|
||||
string company = "";
|
||||
|
||||
var b = !string.IsNullOrEmpty(app.AppId);
|
||||
var isNullOrEmpty = !string.IsNullOrEmpty(displayName);
|
||||
if (isNullOrEmpty && b)
|
||||
{
|
||||
// 如果JOIN没有获取到应用名称,尝试单独查询
|
||||
var appInfo = await DatabaseManager.Instance.AppInfos.GetAppByAppIdAsync(app.AppId);
|
||||
@@ -1280,10 +1284,12 @@ public partial class App : System.Windows.Application
|
||||
{
|
||||
displayName = appInfo.Name ?? "";
|
||||
iconBase64 = appInfo.Base64Icon ?? "";
|
||||
version = appInfo.Version ?? "";
|
||||
company = appInfo.Company ?? "";
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(displayName))
|
||||
if (isNullOrEmpty)
|
||||
{
|
||||
displayName = processName.Replace(".exe", "") ?? "Unknown";
|
||||
}
|
||||
@@ -1295,6 +1301,8 @@ public partial class App : System.Windows.Application
|
||||
displayName = displayName,
|
||||
processPath = app.AppPath ?? "",
|
||||
icon = iconBase64,
|
||||
version = version,
|
||||
company = company,
|
||||
totalBytes = app.TotalTraffic,
|
||||
uploadBytes = app.TotalUpload,
|
||||
connectionCount = app.ConnectionCount
|
||||
@@ -1757,4 +1765,4 @@ public class SubscriptionAppInfo : SubscriptionInfo
|
||||
/// 订阅的应用程序路径
|
||||
/// </summary>
|
||||
public string ApplicationPath { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
### NetVizor API Test Cases
|
||||
|
||||
### Get network interfaces list - default 1 hour range
|
||||
GET http://localhost:8268/api/statistics/interfaces
|
||||
|
||||
###
|
||||
|
||||
### Get network interfaces list - specify time range
|
||||
GET http://localhost:8268/api/statistics/interfaces?timeRange=day
|
||||
|
||||
###
|
||||
|
||||
### Get network interfaces list - 1 week time range
|
||||
GET http://localhost:8268/api/statistics/interfaces?timeRange=week
|
||||
|
||||
###
|
||||
|
||||
### Get network interfaces list - 1 month time range
|
||||
GET http://localhost:8268/api/statistics/interfaces?timeRange=month
|
||||
|
||||
###
|
||||
|
||||
### Get available time ranges
|
||||
GET http://localhost:8268/api/statistics/available-ranges
|
||||
|
||||
###
|
||||
|
||||
### Clear network interface cache
|
||||
POST http://localhost:8268/api/statistics/clear-cache
|
||||
Content-Type: application/json
|
||||
|
||||
{}
|
||||
|
||||
###
|
||||
|
||||
### Get traffic trends data - default 1 hour all interfaces
|
||||
GET http://localhost:8268/api/traffic/trends
|
||||
|
||||
###
|
||||
|
||||
### Get traffic trends data - specify time range and interface
|
||||
GET http://localhost:8268/api/traffic/trends?timeRange=1day&interfaceId=all
|
||||
|
||||
###
|
||||
|
||||
### Get traffic trends data - 1 week data
|
||||
GET http://localhost:8268/api/traffic/trends?timeRange=7days&interfaceId=all
|
||||
|
||||
###
|
||||
|
||||
### Get traffic trends data - 1 month data
|
||||
GET http://localhost:8268/api/traffic/trends?timeRange=30days&interfaceId=all
|
||||
|
||||
###
|
||||
|
||||
### Get top apps traffic data - default 1 hour top 10
|
||||
GET http://localhost:8268/api/traffic/top-apps
|
||||
|
||||
###
|
||||
|
||||
### Get top apps traffic data - specify time range and limit
|
||||
GET http://localhost:8268/api/traffic/top-apps?timeRange=1day&limit=20
|
||||
|
||||
###
|
||||
|
||||
### Get top apps traffic data - 1 week data
|
||||
GET http://localhost:8268/api/traffic/top-apps?timeRange=7days&limit=15
|
||||
|
||||
###
|
||||
|
||||
### Get top apps traffic data - 1 month data
|
||||
GET http://localhost:8268/api/traffic/top-apps?timeRange=30days&limit=50
|
||||
|
||||
###
|
||||
|
||||
### Get software traffic top ranking - default 1 hour page 1 100 items
|
||||
GET http://localhost:8268/api/apps/top-traffic
|
||||
|
||||
###
|
||||
|
||||
### Get software traffic top ranking - specify time range and pagination
|
||||
GET http://localhost:8268/api/apps/top-traffic?timeRange=1day&page=1&pageSize=50
|
||||
|
||||
###
|
||||
|
||||
### Get software traffic top ranking - page 2 data
|
||||
GET http://localhost:8268/api/apps/top-traffic?timeRange=1day&page=2&pageSize=30
|
||||
|
||||
###
|
||||
|
||||
### Get software traffic top ranking - 1 week data
|
||||
GET http://localhost:8268/api/apps/top-traffic?timeRange=7days&page=1&pageSize=100
|
||||
|
||||
###
|
||||
|
||||
### Get software traffic top ranking - 1 month data
|
||||
GET http://localhost:8268/api/apps/top-traffic?timeRange=30days&page=1&pageSize=100
|
||||
@@ -2,6 +2,9 @@
|
||||
这是一个网络管理项目,它用的是Vue进行展示的,集成防火墙API,提供HTTP和WebSocket接口来实现数据的传递和接收。使用Vue作为前端并通过WebView2套壳实现主页面渲染
|
||||
我使用了 TS 和Vue,UI样式使用的 Naive UI
|
||||
|
||||
\src\NetVizor.Frontend\.env.development 中有我前端的配置
|
||||
vue-virtual-scroller 中是我项目主要使用的虚拟列表组件
|
||||
|
||||
我所有的环境并非全部在默认位置安装,比如部分环境如 Git 之类的在 D:\Env 下
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,355 @@
|
||||
# NetVizor Frontend - API 定义文档
|
||||
|
||||
## 1. 网络接口API
|
||||
|
||||
### 1.1 获取网络接口列表(在全局网速数据表中,把存在的网卡都展示出来,其中需要传递timeType,以表示拿哪里的数据,day指的是1天的数据,你应该去 GlobalNetworkHourly 中寻找,因为其内部最小单位为小时。如果是 week 则应该去 GlobalNetworkDaily 因为这个表的时间为天,mouth 也对应的是GlobalNetworkDaily,如果是 hour 则应该去 GlobalNetwork 找,他内部的时间是 5 秒。这个值一共4中可能,hour,day,week,mouth,默认 hour
|
||||
目前数据库存储的有对应的 网卡ID,需要查询到所有的网卡ID再单独获取对应的网卡信息并返回 Common\Utils\NetworkAdapterHelper.cs 提供了网卡信息的获取方法
|
||||
```
|
||||
GET /api/statistics/interfaces?timeType=day
|
||||
```
|
||||
**返回:**
|
||||
```json
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"id": "eth0",
|
||||
"name": "以太网",
|
||||
"displayName": "Realtek PCIe GbE Family Controller",
|
||||
"isActive": true,
|
||||
"macAddress": "00:11:22:33:44:55"
|
||||
},
|
||||
{
|
||||
"id": "wifi0",
|
||||
"name": "WiFi",
|
||||
"displayName": "Intel Wi-Fi 6 AX200",
|
||||
"isActive": false,
|
||||
"macAddress": "66:77:88:99:AA:BB"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 1.2 获取可用时间范围(这里的逻辑是是否有对应的数据,因为我们的数据库会定时的收集和整理信息,如果用户刚安装软件,肯定不会有月数据,也不会有周数据,所以需要据此来确定用户能展示的数据范围,同样分为 hour,day,week,mouth 返回这四个等级就行,hour只要用户安装超过10秒就有数据了,所以最低返回 hour 就行
|
||||
```
|
||||
GET /api/statistics/available-ranges
|
||||
```
|
||||
**返回:**
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"type": "hour",
|
||||
"name": "1小时",
|
||||
"available": true,
|
||||
"startTime": "2025-01-08T15:00:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 2. 流量数据API
|
||||
|
||||
### 2.1 获取流量趋势数据
|
||||
```
|
||||
GET /api/traffic/trends
|
||||
```
|
||||
**参数:**
|
||||
- `timeRange`: 时间范围 (1hour|1day|7days|30days)
|
||||
- `interfaceId`: 网卡ID,可选,默认为"all"表示所有网卡
|
||||
|
||||
**返回:**
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"interface": "all",
|
||||
"timeRange": "1hour",
|
||||
"points": [
|
||||
{
|
||||
"timestamp": "1754565364",
|
||||
"uploadSpeed": 1024000,
|
||||
"downloadSpeed": 5120000
|
||||
},{
|
||||
"timestamp": "1754565364",
|
||||
"uploadSpeed": 1024000,
|
||||
"downloadSpeed": 5120000
|
||||
},{
|
||||
"timestamp": "1754565364",
|
||||
"uploadSpeed": 1024000,
|
||||
"downloadSpeed": 5120000
|
||||
},{
|
||||
"timestamp": "1754565364",
|
||||
"uploadSpeed": 1024000,
|
||||
"downloadSpeed": 5120000
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 获取Top应用流量数据
|
||||
```
|
||||
GET /api/traffic/top-apps
|
||||
```
|
||||
**参数:**
|
||||
- `timeRange`: 时间范围
|
||||
- `limit`: 返回数量,默认10
|
||||
|
||||
**返回:**
|
||||
```json
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"processName": "chrome.exe",
|
||||
"displayName": "Google Chrome",
|
||||
"icon": "+Uj0Iu09gH8tBUABUAB2HeA3IEEdYdHLY0EAAAAASUVORK5CYII=",
|
||||
"totalBytes": 1073741824
|
||||
},{
|
||||
"processName": "chrome.exe",
|
||||
"displayName": "Google Chrome",
|
||||
"icon": "+Uj0Iu09gH8tBUABUAB2HeA3IEEdYdHLY0EAAAAASUVORK5CYII=",
|
||||
"totalBytes": 1073741824
|
||||
},{
|
||||
"processName": "chrome.exe",
|
||||
"displayName": "Google Chrome",
|
||||
"icon": "+Uj0Iu09gH8tBUABUAB2HeA3IEEdYdHLY0EAAAAASUVORK5CYII=",
|
||||
"totalBytes": 1073741824
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 3. 软件排行榜API
|
||||
|
||||
### 3.1 获取软件流量TOP100排行
|
||||
```
|
||||
GET /api/apps/top-traffic
|
||||
```
|
||||
**参数:**
|
||||
- `timeRange`: 时间范围 (1hour|1day|7days|30days)
|
||||
- `page`: 页码,默认1
|
||||
- `pageSize`: 每页数量,默认100
|
||||
|
||||
**返回:**
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"total": 156,
|
||||
"page": 1,
|
||||
"pageSize": 100,
|
||||
"items": [
|
||||
{
|
||||
"rank": 1,
|
||||
"processName": "chrome.exe",
|
||||
"displayName": "Google Chrome",
|
||||
"processPath": "C:\\Program Files\\Google\\Chrome\\chrome.exe",
|
||||
"icon": "H8tBUABUAB2HeA3IEEdYdHLY0EAAAAASUVORK5CYII",
|
||||
"totalBytes": 1073741824,
|
||||
"uploadBytes": 104857600,
|
||||
"connectionCount": 23
|
||||
},{
|
||||
"rank": 2,
|
||||
"processName": "chrome.exe1",
|
||||
"displayName": "Google Chrome1",
|
||||
"processPath": "C:\\Program Files\\Google\\Chrome\\chrome.exe",
|
||||
"icon": "H8tBUABUAB2HeA3IEEdYdHLY0EAAAAASUVORK5CYII",
|
||||
"totalBytes": 1073741824,
|
||||
"uploadBytes": 104857600,
|
||||
"connectionCount": 32
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 4. 软件详情弹窗API(整合所有数据)
|
||||
|
||||
### 4.1 获取软件完整详情信息(新接口)
|
||||
```
|
||||
GET /api/apps/{processName}/complete-details
|
||||
```
|
||||
**参数:**
|
||||
- `timeRange`: 时间范围
|
||||
|
||||
**返回(包含弹窗所需的所有数据):**
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"basicInfo": {
|
||||
"processName": "chrome.exe",
|
||||
"displayName": "Google Chrome",
|
||||
"version": "119.0.6045.160",
|
||||
"company": "Google LLC",
|
||||
"processPath": "C:\\Program Files\\Google\\Chrome\\chrome.exe",
|
||||
"icon": "/path/to/icon.ico",
|
||||
"fileSize": 2467840,
|
||||
"createdTime": "2023-11-15T10:30:00Z",
|
||||
"modifiedTime": "2023-11-15T10:30:00Z"
|
||||
},
|
||||
"trafficStats": {
|
||||
"totalBytes": 1073741824,
|
||||
"uploadBytes": 104857600,
|
||||
"downloadBytes": 968884224,
|
||||
"percentage": 45.2,
|
||||
"connectionCount": 23,
|
||||
"averageSpeed": 298666
|
||||
},
|
||||
"protocolDistribution": [
|
||||
{
|
||||
"protocol": "HTTPS",
|
||||
"bytes": 1610612736,
|
||||
"percentage": 37.5,
|
||||
"color": "#3b82f6"
|
||||
},
|
||||
{
|
||||
"protocol": "HTTP",
|
||||
"bytes": 1073741824,
|
||||
"percentage": 25.0,
|
||||
"color": "#10b981"
|
||||
},
|
||||
{
|
||||
"protocol": "DNS",
|
||||
"bytes": 751619276,
|
||||
"percentage": 17.5,
|
||||
"color": "#f59e0b"
|
||||
},
|
||||
{
|
||||
"protocol": "其他",
|
||||
"bytes": 858993459,
|
||||
"percentage": 20.0,
|
||||
"color": "#ef4444"
|
||||
}
|
||||
],
|
||||
"networkRelations": {
|
||||
"nodes": [
|
||||
{
|
||||
"id": "app_chrome",
|
||||
"name": "Chrome",
|
||||
"type": "application",
|
||||
"size": 45.2,
|
||||
"category": 0
|
||||
},
|
||||
{
|
||||
"id": "port_443",
|
||||
"name": "443/TCP",
|
||||
"type": "port",
|
||||
"size": 30.5,
|
||||
"category": 1
|
||||
},
|
||||
{
|
||||
"id": "host_google",
|
||||
"name": "google.com",
|
||||
"type": "remote_host",
|
||||
"size": 25.8,
|
||||
"category": 2
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
{
|
||||
"source": "app_chrome",
|
||||
"target": "port_443",
|
||||
"value": 30.5,
|
||||
"label": "30.5MB"
|
||||
},
|
||||
{
|
||||
"source": "port_443",
|
||||
"target": "host_google",
|
||||
"value": 25.8,
|
||||
"label": "25.8MB"
|
||||
}
|
||||
]
|
||||
},
|
||||
"portStats": [
|
||||
{
|
||||
"port": 443,
|
||||
"protocol": "TCP",
|
||||
"connectionCount": 15,
|
||||
"totalBytes": 52428800,
|
||||
"remoteHosts": [
|
||||
"142.251.42.227",
|
||||
"172.217.164.46"
|
||||
]
|
||||
},
|
||||
{
|
||||
"port": 80,
|
||||
"protocol": "TCP",
|
||||
"connectionCount": 5,
|
||||
"totalBytes": 10485760,
|
||||
"remoteHosts": [
|
||||
"93.184.216.34"
|
||||
]
|
||||
}
|
||||
],
|
||||
"connections": [
|
||||
{
|
||||
"id": "conn_001",
|
||||
"localAddress": "192.168.1.100",
|
||||
"localPort": 52341,
|
||||
"remoteAddress": "142.251.42.227",
|
||||
"remotePort": 443,
|
||||
"protocol": "TCP",
|
||||
"state": "ESTABLISHED",
|
||||
"uploadBytes": 2048000,
|
||||
"downloadBytes": 8192000,
|
||||
"duration": 1800,
|
||||
"lastActivity": "2025-01-08T15:45:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 5. 仪表盘指标API
|
||||
|
||||
### 5.1 获取关键指标数据
|
||||
```
|
||||
GET /api/dashboard/metrics
|
||||
```
|
||||
**参数:**
|
||||
- `timeRange`: 时间范围
|
||||
|
||||
**返回:**
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"totalTraffic": {
|
||||
"value": "2.45 TB",
|
||||
"trend": 12.5,
|
||||
"trendDirection": "up"
|
||||
},
|
||||
"threatDetection": {
|
||||
"value": 156,
|
||||
"trend": -8.2,
|
||||
"trendDirection": "down"
|
||||
},
|
||||
"averageLatency": {
|
||||
"value": "12.3 ms",
|
||||
"trend": 0.0,
|
||||
"trendDirection": "stable"
|
||||
},
|
||||
"packetLoss": {
|
||||
"value": "0.02%",
|
||||
"trend": 0.01,
|
||||
"trendDirection": "up"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 说明
|
||||
|
||||
### 重要更新:
|
||||
1. **整合弹窗API**: 新增 `/api/apps/{processName}/complete-details` 接口,一次性返回弹窗所需的所有数据(基本信息、协议分布、网络关系、端口统计等)
|
||||
2. **简化交互**: 主页面只显示软件列表,详细信息通过弹窗展示
|
||||
3. **数据优化**: 减少页面初始加载的数据量,按需加载详细信息
|
||||
|
||||
### Mock数据说明:
|
||||
所有API接口在开发阶段使用Mock数据进行测试,生产环境对接真实的RESTful API。成功响应状态码200,失败时返回对应错误码和错误信息。
|
||||
|
||||
错误响应格式:
|
||||
```json
|
||||
{
|
||||
"error": {
|
||||
"code": "ERROR_CODE",
|
||||
"message": "错误信息",
|
||||
"details": {}
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,89 @@
|
||||
### NetVizor API Test Cases
|
||||
|
||||
### Get network interfaces list - default 1 hour range
|
||||
GET http://localhost:8268/api/statistics/interfaces
|
||||
|
||||
###
|
||||
|
||||
### Get network interfaces list - specify time range
|
||||
GET http://localhost:8268/api/statistics/interfaces?timeRange=day
|
||||
|
||||
###
|
||||
|
||||
### Get network interfaces list - 1 week time range
|
||||
GET http://localhost:8268/api/statistics/interfaces?timeRange=week
|
||||
|
||||
###
|
||||
|
||||
### Get network interfaces list - 1 month time range
|
||||
GET http://localhost:8268/api/statistics/interfaces?timeRange=month
|
||||
|
||||
###
|
||||
|
||||
### Get available time ranges
|
||||
GET http://localhost:8268/api/statistics/available-ranges
|
||||
|
||||
###
|
||||
|
||||
### Get traffic trends data - default 1 hour all interfaces
|
||||
GET http://localhost:8268/api/traffic/trends
|
||||
|
||||
###
|
||||
|
||||
### Get traffic trends data - specify time range and interface
|
||||
GET http://localhost:8268/api/traffic/trends?timeRange=1day&interfaceId=all
|
||||
|
||||
###
|
||||
|
||||
### Get traffic trends data - 1 week data
|
||||
GET http://localhost:8268/api/traffic/trends?timeRange=7days&interfaceId=all
|
||||
|
||||
###
|
||||
|
||||
### Get traffic trends data - 1 month data
|
||||
GET http://localhost:8268/api/traffic/trends?timeRange=30days&interfaceId=all
|
||||
|
||||
###
|
||||
|
||||
### Get top apps traffic data - default 1 hour top 10
|
||||
GET http://localhost:8268/api/traffic/top-apps
|
||||
|
||||
###
|
||||
|
||||
### Get top apps traffic data - specify time range and limit
|
||||
GET http://localhost:8268/api/traffic/top-apps?timeRange=1day&limit=20
|
||||
|
||||
###
|
||||
|
||||
### Get top apps traffic data - 1 week data
|
||||
GET http://localhost:8268/api/traffic/top-apps?timeRange=7days&limit=15
|
||||
|
||||
###
|
||||
|
||||
### Get top apps traffic data - 1 month data
|
||||
GET http://localhost:8268/api/traffic/top-apps?timeRange=30days&limit=50
|
||||
|
||||
###
|
||||
|
||||
### Get software traffic top ranking - default 1 hour page 1 100 items
|
||||
GET http://localhost:8268/api/apps/top-traffic
|
||||
|
||||
###
|
||||
|
||||
### Get software traffic top ranking - specify time range and pagination
|
||||
GET http://localhost:8268/api/apps/top-traffic?timeRange=1day&page=1&pageSize=50
|
||||
|
||||
###
|
||||
|
||||
### Get software traffic top ranking - page 2 data
|
||||
GET http://localhost:8268/api/apps/top-traffic?timeRange=1day&page=2&pageSize=30
|
||||
|
||||
###
|
||||
|
||||
### Get software traffic top ranking - 1 week data
|
||||
GET http://localhost:8268/api/apps/top-traffic?timeRange=7days&page=1&pageSize=100
|
||||
|
||||
###
|
||||
|
||||
### Get software traffic top ranking - 1 month data
|
||||
GET http://localhost:8268/api/apps/top-traffic?timeRange=30days&page=1&pageSize=100
|
||||
@@ -10,8 +10,8 @@
|
||||
<div class="header-controls">
|
||||
<div class="time-selector">
|
||||
<n-button-group>
|
||||
<n-button
|
||||
v-for="range in timeRanges"
|
||||
<n-button
|
||||
v-for="range in timeRanges"
|
||||
:key="range.type"
|
||||
:type="selectedTimeRange === range.type ? 'primary' : 'default'"
|
||||
:disabled="!range.available"
|
||||
@@ -31,7 +31,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 关键指标卡片 -->
|
||||
<div class="metrics-grid">
|
||||
<div class="metrics-grid" v-if="false ">
|
||||
<div class="metric-card">
|
||||
<div class="metric-header">
|
||||
<span class="metric-title">总流量</span>
|
||||
@@ -88,8 +88,8 @@
|
||||
<div class="chart-header">
|
||||
<h3 class="chart-title">流量趋势</h3>
|
||||
<div class="chart-controls">
|
||||
<n-select
|
||||
v-model:value="selectedInterface"
|
||||
<n-select
|
||||
v-model:value="selectedInterface"
|
||||
:options="interfaceOptions"
|
||||
size="small"
|
||||
style="min-width: 120px;"
|
||||
@@ -97,8 +97,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="chart-body">
|
||||
<TrafficTrendChart
|
||||
:data="trafficTrendData"
|
||||
<TrafficTrendChart
|
||||
:data="trafficTrendData"
|
||||
:interface-id="selectedInterface"
|
||||
/>
|
||||
</div>
|
||||
@@ -111,7 +111,7 @@
|
||||
<h3 class="chart-title">Top应用流量</h3>
|
||||
</div>
|
||||
<div class="chart-body">
|
||||
<TopAppsChart
|
||||
<TopAppsChart
|
||||
:data="topAppsData"
|
||||
:time-range="selectedTimeRange"
|
||||
/>
|
||||
@@ -126,8 +126,8 @@
|
||||
<h3 class="panel-title">软件流量TOP榜</h3>
|
||||
<div class="panel-controls">
|
||||
<n-button-group size="small">
|
||||
<n-button
|
||||
v-for="range in rankingTimeRanges"
|
||||
<n-button
|
||||
v-for="range in rankingTimeRanges"
|
||||
:key="range"
|
||||
:type="selectedRankingRange === range ? 'primary' : 'default'"
|
||||
@click="selectedRankingRange = range"
|
||||
@@ -138,7 +138,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="ranking-content">
|
||||
<SoftwareRankingList
|
||||
<SoftwareRankingList
|
||||
:data="softwareRankingData"
|
||||
:time-range="selectedRankingRange"
|
||||
@select-software="showSoftwareDetail"
|
||||
@@ -223,21 +223,21 @@ const topAppsData = ref([
|
||||
])
|
||||
|
||||
const softwareRankingData = ref([
|
||||
{
|
||||
rank: 1,
|
||||
processName: 'chrome.exe',
|
||||
displayName: 'Google Chrome',
|
||||
totalBytes: 1073741824,
|
||||
{
|
||||
rank: 1,
|
||||
processName: 'chrome.exe',
|
||||
displayName: 'Google Chrome',
|
||||
totalBytes: 1073741824,
|
||||
percentage: 45.2,
|
||||
connectionCount: 23
|
||||
connectionCount: 23
|
||||
},
|
||||
{
|
||||
rank: 2,
|
||||
processName: 'teams.exe',
|
||||
displayName: 'Microsoft Teams',
|
||||
totalBytes: 536870912,
|
||||
{
|
||||
rank: 2,
|
||||
processName: 'teams.exe',
|
||||
displayName: 'Microsoft Teams',
|
||||
totalBytes: 536870912,
|
||||
percentage: 22.5,
|
||||
connectionCount: 12
|
||||
connectionCount: 12
|
||||
},
|
||||
// ... 更多数据
|
||||
])
|
||||
@@ -668,7 +668,7 @@ onMounted(() => {
|
||||
.chart-card.large {
|
||||
grid-column: span 1;
|
||||
}
|
||||
|
||||
|
||||
.software-ranking-panel {
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
<template>
|
||||
<div class="protocol-chart">
|
||||
<v-chart
|
||||
ref="chartRef"
|
||||
class="chart"
|
||||
:option="chartOption"
|
||||
:autoresize="true"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import VChart from 'vue-echarts'
|
||||
import { use } from 'echarts/core'
|
||||
import { CanvasRenderer } from 'echarts/renderers'
|
||||
import { PieChart } from 'echarts/charts'
|
||||
import {
|
||||
TitleComponent,
|
||||
TooltipComponent,
|
||||
LegendComponent
|
||||
} from 'echarts/components'
|
||||
import type { EChartsOption } from 'echarts'
|
||||
|
||||
// 注册ECharts组件
|
||||
use([
|
||||
CanvasRenderer,
|
||||
PieChart,
|
||||
TitleComponent,
|
||||
TooltipComponent,
|
||||
LegendComponent
|
||||
])
|
||||
|
||||
// Props定义
|
||||
interface ProtocolData {
|
||||
protocol: string
|
||||
bytes: number
|
||||
percentage: number
|
||||
color: string
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
data: ProtocolData[]
|
||||
}>()
|
||||
|
||||
// 生成饼图配置
|
||||
const chartOption = computed<EChartsOption>(() => {
|
||||
return {
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: (params: any) => {
|
||||
const data = params.data
|
||||
return `
|
||||
<div style="margin-bottom: 4px; font-weight: bold;">${data.name}</div>
|
||||
<div>占比: ${data.value}%</div>
|
||||
<div>流量: ${formatBytes(data.bytes)}</div>
|
||||
`
|
||||
},
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.8)',
|
||||
borderColor: 'rgba(255, 255, 255, 0.1)',
|
||||
textStyle: {
|
||||
color: '#ffffff',
|
||||
fontSize: 12
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
right: 10,
|
||||
top: 20,
|
||||
textStyle: {
|
||||
color: 'var(--text-secondary)',
|
||||
fontSize: 11
|
||||
},
|
||||
formatter: (name: string) => {
|
||||
const item = props.data.find(d => d.protocol === name)
|
||||
return `${name} ${item?.percentage.toFixed(1)}%`
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '协议分布',
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
center: ['35%', '50%'],
|
||||
data: props.data.map(item => ({
|
||||
value: item.percentage,
|
||||
name: item.protocol,
|
||||
bytes: item.bytes,
|
||||
itemStyle: {
|
||||
color: item.color,
|
||||
borderWidth: 2,
|
||||
borderColor: '#fff'
|
||||
}
|
||||
})),
|
||||
label: {
|
||||
show: false
|
||||
},
|
||||
labelLine: {
|
||||
show: false
|
||||
},
|
||||
animationType: 'scale',
|
||||
animationEasing: 'elasticOut',
|
||||
animationDelayUpdate: (idx: number) => idx * 100
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
// 格式化字节数
|
||||
const formatBytes = (bytes: number): string => {
|
||||
if (bytes === 0) return '0 B'
|
||||
|
||||
const k = 1024
|
||||
const sizes = ['B', 'KB', 'MB', 'GB', 'TB']
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||
|
||||
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.protocol-chart {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.chart {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,437 @@
|
||||
<template>
|
||||
<n-modal
|
||||
v-model:show="visible"
|
||||
:mask-closable="false"
|
||||
preset="card"
|
||||
style="width: 90%; max-width: 1000px;"
|
||||
:title="software?.displayName || '软件详情'"
|
||||
size="huge"
|
||||
:bordered="false"
|
||||
:segmented="false"
|
||||
>
|
||||
<div v-if="software" class="software-detail-content">
|
||||
<!-- 基本信息和统计 -->
|
||||
<div class="detail-header">
|
||||
<div class="software-icon">
|
||||
<div class="icon-placeholder">
|
||||
{{ software.displayName.charAt(0).toUpperCase() }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="software-summary">
|
||||
<h2 class="software-name">{{ software.displayName }}</h2>
|
||||
<div class="software-meta">
|
||||
<span class="process-name">{{ software.processName }}</span>
|
||||
<span class="traffic-info">{{ formatBytes(software.totalBytes) }} ({{ software.percentage.toFixed(1) }}%)</span>
|
||||
<span class="connection-info">{{ software.connectionCount }} 个连接</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail-stats">
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">流量占比</span>
|
||||
<span class="stat-value">{{ software.percentage.toFixed(1) }}%</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">连接数</span>
|
||||
<span class="stat-value">{{ software.connectionCount }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 选项卡内容 -->
|
||||
<div class="detail-tabs">
|
||||
<n-tabs default-value="overview" type="line">
|
||||
<!-- 概览 -->
|
||||
<n-tab-pane name="overview" tab="概览">
|
||||
<div class="tab-content">
|
||||
<div class="overview-grid">
|
||||
<!-- 软件信息卡片 -->
|
||||
<div class="info-section">
|
||||
<h4 class="section-title">软件信息</h4>
|
||||
<SoftwareInfoCard :software-info="softwareInfo" />
|
||||
</div>
|
||||
|
||||
<!-- 协议分布图 -->
|
||||
<div class="protocol-section">
|
||||
<h4 class="section-title">协议分布</h4>
|
||||
<ProtocolChart :data="protocolData" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</n-tab-pane>
|
||||
|
||||
<!-- 网络连接 -->
|
||||
<n-tab-pane name="network" tab="网络连接">
|
||||
<div class="tab-content">
|
||||
<div class="network-grid">
|
||||
<!-- 连接关系图 -->
|
||||
<div class="relation-section">
|
||||
<h4 class="section-title">连接关系</h4>
|
||||
<NetworkRelationChart
|
||||
:data="networkRelationData"
|
||||
:software="software"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 端口统计表 -->
|
||||
<div class="ports-section">
|
||||
<h4 class="section-title">端口统计</h4>
|
||||
<PortStatsTable :data="portStatsData" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</n-tab-pane>
|
||||
|
||||
<!-- 流量详情 -->
|
||||
<n-tab-pane name="traffic" tab="流量详情">
|
||||
<div class="tab-content">
|
||||
<div class="traffic-details">
|
||||
<div class="traffic-chart-section">
|
||||
<h4 class="section-title">流量趋势 ({{ timeRange }})</h4>
|
||||
<div class="traffic-chart-placeholder">
|
||||
<div class="placeholder-content">
|
||||
<n-icon :component="TrendingUpOutline" size="48" />
|
||||
<p>流量趋势图</p>
|
||||
<p class="placeholder-desc">展示该软件的上传/下载流量变化</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="traffic-stats-section">
|
||||
<h4 class="section-title">流量统计</h4>
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">总流量</div>
|
||||
<div class="stat-value">{{ formatBytes(software.totalBytes || 0) }}</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">上传</div>
|
||||
<div class="stat-value">{{ formatBytes(software.uploadBytes || 0) }}</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">下载</div>
|
||||
<div class="stat-value">{{ formatBytes(software.downloadBytes || 0) }}</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">平均速度</div>
|
||||
<div class="stat-value">{{ formatSpeed(averageSpeed) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #action>
|
||||
<div class="modal-actions">
|
||||
<n-button @click="visible = false">关闭</n-button>
|
||||
<n-button type="primary" @click="exportData">
|
||||
<template #icon>
|
||||
<n-icon :component="DownloadOutline" />
|
||||
</template>
|
||||
导出数据
|
||||
</n-button>
|
||||
</div>
|
||||
</template>
|
||||
</n-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { NModal, NTabs, NTabPane, NButton, NIcon } from 'naive-ui'
|
||||
import { TrendingUpOutline, DownloadOutline } from '@vicons/ionicons5'
|
||||
|
||||
// 导入子组件
|
||||
import SoftwareInfoCard from './SoftwareInfoCard.vue'
|
||||
import NetworkRelationChart from './NetworkRelationChart.vue'
|
||||
import PortStatsTable from './PortStatsTable.vue'
|
||||
import ProtocolChart from './ProtocolChart.vue'
|
||||
|
||||
// Props定义
|
||||
const props = defineProps<{
|
||||
show: boolean
|
||||
software?: any
|
||||
softwareInfo?: any
|
||||
networkRelationData?: any
|
||||
portStatsData?: any[]
|
||||
protocolData?: any[]
|
||||
timeRange?: string
|
||||
}>()
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits<{
|
||||
'update:show': [value: boolean]
|
||||
}>()
|
||||
|
||||
// 计算属性
|
||||
const visible = computed({
|
||||
get: () => props.show,
|
||||
set: (value) => emit('update:show', value)
|
||||
})
|
||||
|
||||
// 计算平均速度
|
||||
const averageSpeed = computed(() => {
|
||||
if (!props.software?.totalBytes) return 0
|
||||
// 模拟计算,实际应根据时间范围计算
|
||||
return props.software.totalBytes / 3600 // 假设1小时的数据
|
||||
})
|
||||
|
||||
// 格式化字节数
|
||||
const formatBytes = (bytes: number): string => {
|
||||
if (bytes === 0) return '0 B'
|
||||
|
||||
const k = 1024
|
||||
const sizes = ['B', 'KB', 'MB', 'GB', 'TB']
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||
|
||||
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`
|
||||
}
|
||||
|
||||
// 格式化速度
|
||||
const formatSpeed = (bytesPerSecond: number): string => {
|
||||
return formatBytes(bytesPerSecond) + '/s'
|
||||
}
|
||||
|
||||
// 导出数据
|
||||
const exportData = () => {
|
||||
// 实现数据导出功能
|
||||
console.log('Export data for:', props.software?.processName)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.software-detail-content {
|
||||
max-height: 70vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* 详情头部 */
|
||||
.detail-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
margin-bottom: 24px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid var(--border-secondary);
|
||||
}
|
||||
|
||||
.software-icon .icon-placeholder {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 12px;
|
||||
background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.software-summary {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.software-name {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
|
||||
.software-meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.process-name {
|
||||
font-family: 'JetBrains Mono', 'Fira Code', monospace;
|
||||
color: var(--accent-primary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.traffic-info {
|
||||
color: var(--text-secondary);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.connection-info {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.detail-stats {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 11px;
|
||||
color: var(--text-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* 选项卡内容 */
|
||||
.detail-tabs {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
padding: 16px 0;
|
||||
}
|
||||
|
||||
/* 概览页面 */
|
||||
.overview-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 300px;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--text-secondary);
|
||||
margin: 0 0 12px 0;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.protocol-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 网络连接页面 */
|
||||
.network-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.relation-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.ports-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 流量详情页面 */
|
||||
.traffic-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.traffic-chart-placeholder {
|
||||
height: 200px;
|
||||
background: var(--bg-tertiary);
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px dashed var(--border-secondary);
|
||||
}
|
||||
|
||||
.placeholder-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.placeholder-desc {
|
||||
font-size: 12px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: var(--bg-tertiary);
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--border-secondary);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stat-card .stat-label {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 8px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.stat-card .stat-value {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
font-family: 'JetBrains Mono', 'Fira Code', monospace;
|
||||
}
|
||||
|
||||
/* 模态框操作 */
|
||||
.modal-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
/* 响应式 */
|
||||
@media (max-width: 768px) {
|
||||
.detail-header {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.detail-stats {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.overview-grid,
|
||||
.network-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
/* Naive UI 覆盖样式 */
|
||||
:deep(.n-tabs .n-tab-pane) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
:deep(.n-tabs .n-tabs-nav) {
|
||||
--n-tab-color-hover: var(--bg-hover);
|
||||
--n-tab-text-color-active: var(--accent-primary);
|
||||
--n-bar-color: var(--accent-primary);
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user