whistle.mock-plugins 1.3.1 → 1.4.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/VERSION.md +29 -0
  2. package/app/cgi-bin/interfaces.js +2 -0
  3. package/app/cgi-bin/settings.js +1 -0
  4. package/app/cgi-bin/stats.js +22 -12
  5. package/app/cgi-bin/webdav.js +56 -0
  6. package/app/public/index.html +1 -1
  7. package/app/public/js/11.index.js +2 -0
  8. package/app/public/js/11.index.js.LICENSE.txt +5 -0
  9. package/app/public/js/704.index.js +2 -0
  10. package/app/public/js/704.index.js.LICENSE.txt +1 -0
  11. package/app/public/js/798.index.js +2 -0
  12. package/app/public/js/798.index.js.LICENSE.txt +53 -0
  13. package/app/public/js/807.index.js +2 -0
  14. package/app/public/js/807.index.js.LICENSE.txt +1 -0
  15. package/app/public/js/968.index.js +2 -0
  16. package/app/public/js/968.index.js.LICENSE.txt +12 -0
  17. package/app/public/js/index.js +1 -1
  18. package/app/public/js/index.js.LICENSE.txt +0 -173
  19. package/lib/routes/settings.js +1 -0
  20. package/lib/webdav-sync.js +806 -0
  21. package/package.json +9 -2
  22. package/API.md +0 -247
  23. package/COLUMN_RESIZE_IMPLEMENTATION.md +0 -162
  24. package/FEATURE_ENHANCEMENT.md +0 -157
  25. package/TEST_GUIDE.md +0 -156
  26. package/data/features.json +0 -1
  27. package/data/interfaces.json +0 -1
  28. package/index.html +0 -52
  29. package/initialize.js +0 -32
  30. package/public/bundle.js +0 -2
  31. package/public/bundle.js.LICENSE.txt +0 -38
  32. package/public/index.html +0 -1
  33. package/public/plugin.mock-plugin.html +0 -14
  34. package/rules.txt +0 -22
  35. package/ui/src/App.css +0 -173
  36. package/ui/src/components/AppLayout.js +0 -407
  37. package/ui/src/components/FeatureCard.js +0 -58
  38. package/ui/src/components/KeyboardShortcutsHelp.js +0 -58
  39. package/ui/src/components/Layout.js +0 -94
  40. package/ui/src/components/MainNav.js +0 -42
  41. package/ui/src/components/PageContainer.js +0 -16
  42. package/ui/src/components/VersionModal.js +0 -99
  43. package/ui/src/components/interface-management/DataManagementDrawer.js +0 -115
  44. package/ui/src/components/interface-management/DataManagementModal.js +0 -110
  45. package/ui/src/components/interface-management/InterfaceTestModal.js +0 -313
  46. package/ui/src/components/interface-management/PreviewModal.js +0 -48
  47. package/ui/src/components/interface-management/README.md +0 -184
  48. package/ui/src/components/interface-management/RealResponseRecorder.js +0 -165
  49. package/ui/src/components/interface-management/ResponseContentEditor.js +0 -645
  50. package/ui/src/components/interface-management/constants.js +0 -36
  51. package/ui/src/components/interface-management/index.js +0 -7
  52. package/ui/src/components/interface-management/utils.js +0 -103
  53. package/ui/src/hooks/useKeyboardShortcuts.js +0 -99
  54. package/ui/src/index.html +0 -14
  55. package/ui/src/index.js +0 -50
  56. package/ui/src/pages/InterfaceManagementV2.js +0 -2351
  57. package/ui/src/pages/MockDataV2.js +0 -1089
  58. package/ui/src/pages/Settings.js +0 -299
  59. package/ui/src/pages/Sniffer.js +0 -521
  60. package/ui/src/pages/Stats.js +0 -288
  61. package/ui/src/services/del_api.js +0 -192
  62. package/ui/src/styles/app-layout.css +0 -373
  63. package/ui/src/styles/cyber-theme.css +0 -1234
  64. package/ui/src/styles/earth-theme.css +0 -618
  65. package/ui/src/styles/global.css +0 -224
  66. package/ui/src/styles/interface-management.css +0 -1321
  67. package/ui/src/styles/mock-data.css +0 -1185
  68. package/ui/src/styles/settings.css +0 -252
  69. package/ui/src/styles/version-modal.css +0 -116
  70. package/webpack.config.js +0 -54
package/VERSION.md CHANGED
@@ -1,5 +1,34 @@
1
1
  # 🔄 版本更新记录
2
2
 
3
+ ## 1.4.0-beta.2(2026-10-13)
4
+
5
+ - UI 3.0、新旧界面切换、工作台独立滚动与恢复默认布局反馈的 beta 发布。
6
+ - 与待审批 beta.1 功能一致,重新构建为独立版本;两个界面继续共用原有数据。
7
+
8
+
9
+ ## 1.4.0-beta.1(2026-10-13,待发布)
10
+
11
+ - 统一顶部导航、页面层级和四套主题,增加布局偏好与恢复入口。
12
+ - 接口页升级为模块 / 接口列表 / 详情三栏工作台;支持统一编辑、多响应管理、批量表格列设置和未保存草稿保护。
13
+ - 重整模块、嗅探、统计、设置页面;嗅探支持完整历史与追加响应,统计统一日期 / URL 筛选。
14
+ - 详细验收记录见仓库 `docs/ui-3.0-todo.md`。
15
+
16
+
17
+ ## v1.4.0-beta.0
18
+
19
+ ### ✨ Feature
20
+ - **WebDAV 模块同步**:新增 WebDAV 同步能力,可将功能模块及接口配置同步到 WebDAV 服务器
21
+ - **连接管理**:支持配置 WebDAV 地址与账号密码,支持测试连接、连接、刷新状态与断开连接
22
+ - **推送 / 拉取**:支持按模块推送至远端或从远端拉取,支持强制覆盖(force)并处理远端清单冲突
23
+ - **同步设置开关**:系统设置新增「WebDAV 同步」开关,开启后在功能模块管理页显示同步入口
24
+ - **同步状态展示**:同步弹窗展示连接状态、最近同步时间与远端模块清单
25
+
26
+ ### 🔧 Technical
27
+ - 新增后端 `app/cgi-bin/webdav.js` 与 `lib/webdav-sync.js`,实现 WebDAV 客户端与同步逻辑
28
+ - 新增前端 `ui/src/components/WebDavSyncModal.js` 与 `ui/src/styles/webdav-sync.css`
29
+ - 新增 `ui/src/utils/api-error.js`,统一解析 WebDAV 错误响应
30
+ - 新增 `test/webdav-sync.test.js`、`test/api-error.test.js` 单元测试
31
+
3
32
  ## v1.3.1
4
33
 
5
34
  ### ✨ Feature
@@ -191,6 +191,7 @@ module.exports = function(req, res) {
191
191
  activeResponseId: interfaceData.activeResponseId !== undefined ? interfaceData.activeResponseId : interfacesData.interfaces[index].activeResponseId,
192
192
  targetUrl: interfaceData.targetUrl,
193
193
  customHeaders: interfaceData.customHeaders || {},
194
+ headers: interfaceData.headers || {},
194
195
  paramMatchers: interfaceData.paramMatchers || [],
195
196
  filePath: interfaceData.filePath,
196
197
  httpStatus: interfaceData.httpStatus || 200,
@@ -226,6 +227,7 @@ module.exports = function(req, res) {
226
227
  activeResponseId: interfaceData.activeResponseId || null,
227
228
  targetUrl: interfaceData.targetUrl || '',
228
229
  customHeaders: interfaceData.customHeaders || {},
230
+ headers: interfaceData.headers || {},
229
231
  paramMatchers: interfaceData.paramMatchers || [],
230
232
  filePath: interfaceData.filePath || '',
231
233
  httpStatus: interfaceData.httpStatus || 200,
@@ -4,6 +4,7 @@ const path = require('path');
4
4
  const DEFAULT_SETTINGS = {
5
5
  interfaceParamMatcherEnabled: true,
6
6
  responseParamMatcherEnabled: true,
7
+ webDavSyncEnabled: false,
7
8
  };
8
9
 
9
10
  module.exports = function(req, res) {
@@ -14,18 +14,30 @@ module.exports = (req, res) => {
14
14
  const type = req.query.type || 'overview';
15
15
 
16
16
  if (!fs.existsSync(LOGS_FILE)) {
17
- return res.json({ code: 0, data: { logs: [], stats: {} } });
17
+ return res.json({ code: 0, data: { logs: [], timeline: {}, topInterfaces: [], topFeatures: [] } });
18
18
  }
19
19
 
20
20
  const logsData = fs.readJsonSync(LOGS_FILE);
21
21
  const logs = logsData.logs || [];
22
22
 
23
- // 只取最近 7 天的日志做统计(避免数据量过大)
24
23
  const now = new Date();
25
- const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
24
+ const dayKey = value => {
25
+ const date = new Date(value);
26
+ return `${date.getFullYear()}-${String(date.getMonth()+1).padStart(2,'0')}-${String(date.getDate()).padStart(2,'0')}`;
27
+ };
28
+ const parseDay = value => {
29
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(value || '')) return null;
30
+ const date = new Date(`${value}T00:00:00`);
31
+ return Number.isFinite(date.getTime()) && dayKey(date) === value ? date : null;
32
+ };
33
+ const start = req.query.startDate ? parseDay(req.query.startDate) : new Date(now.getFullYear(),now.getMonth(),now.getDate()-6);
34
+ const end = req.query.endDate ? parseDay(req.query.endDate) : new Date(now.getFullYear(),now.getMonth(),now.getDate());
35
+ if (!start || !end || start > end || end-start > 366*86400000) return res.status(400).json({code:400,message:'请选择有效的日期范围(最多 366 天)'});
36
+ const endExclusive = new Date(end); endExclusive.setDate(endExclusive.getDate()+1);
37
+ const filterUrl = String(req.query.url || '').toLowerCase();
26
38
  const recentLogs = logs.filter(log => {
27
39
  const t = new Date(log.timestamp);
28
- return t >= sevenDaysAgo;
40
+ return t >= start && t < endExclusive && (!filterUrl || String(log.url || '').toLowerCase().includes(filterUrl));
29
41
  });
30
42
 
31
43
  // 分离命中和未命中
@@ -69,7 +81,7 @@ module.exports = (req, res) => {
69
81
  const id = log.interfaceId || log.interfaceName || 'unknown';
70
82
  const name = log.interfaceName || id;
71
83
  if (!interfaceHitMap[id]) {
72
- interfaceHitMap[id] = { id, name, count: 0 };
84
+ interfaceHitMap[id] = { id, name, featureId: log.featureId, count: 0 };
73
85
  }
74
86
  interfaceHitMap[id].count++;
75
87
  });
@@ -80,7 +92,7 @@ module.exports = (req, res) => {
80
92
  // Top 10 命中功能模块
81
93
  const featureHitMap = {};
82
94
  hitLogs.forEach(log => {
83
- const id = log.featureName || log.featureId || 'unknown';
95
+ const id = log.featureId || log.featureName || 'unknown';
84
96
  const name = log.featureName || '未分类';
85
97
  if (!featureHitMap[id]) {
86
98
  featureHitMap[id] = { id, name, count: 0 };
@@ -98,13 +110,11 @@ module.exports = (req, res) => {
98
110
  case 'timeline': {
99
111
  // 最近 7 天每天的命中数
100
112
  const timeline = {};
101
- for (let i = 6; i >= 0; i--) {
102
- const d = new Date(now.getTime() - i * 24 * 60 * 60 * 1000);
103
- const key = d.toISOString().slice(0, 10);
104
- timeline[key] = 0;
113
+ for (let date = new Date(start); date <= end; date.setDate(date.getDate()+1)) {
114
+ timeline[dayKey(date)] = 0;
105
115
  }
106
116
  hitLogs.forEach(log => {
107
- const key = new Date(log.timestamp).toISOString().slice(0, 10);
117
+ const key = dayKey(log.timestamp);
108
118
  if (timeline[key] !== undefined) {
109
119
  timeline[key]++;
110
120
  }
@@ -120,7 +130,7 @@ module.exports = (req, res) => {
120
130
 
121
131
  if (filterDate) {
122
132
  filteredLogs = filteredLogs.filter(log => {
123
- const logDate = new Date(log.timestamp).toISOString().slice(0, 10);
133
+ const logDate = dayKey(log.timestamp);
124
134
  return logDate === filterDate;
125
135
  });
126
136
  }
@@ -0,0 +1,56 @@
1
+ const { createWebDavSync } = require('../../lib/webdav-sync');
2
+
3
+ module.exports = async function(req, res) {
4
+ const webDavSync = createWebDavSync(this.dataDir);
5
+
6
+ try {
7
+ if (req.method == 'GET') {
8
+ return res.json({ code: 0, message: '成功', data: webDavSync.getStatus() });
9
+ }
10
+
11
+ if (req.method != 'POST') {
12
+ return res.status(405).json({ code: 405, message: 'Method Not Allowed', data: null });
13
+ }
14
+
15
+ const { action, force, featureIds, endpoint, username, password } = req.body || {};
16
+ const input = { endpoint, username, password };
17
+ let data;
18
+
19
+ switch (action) {
20
+ case 'test':
21
+ data = await webDavSync.test(input);
22
+ break;
23
+ case 'connect':
24
+ data = await webDavSync.connect(input);
25
+ break;
26
+ case 'refresh':
27
+ data = await webDavSync.refresh();
28
+ break;
29
+ case 'push':
30
+ data = await webDavSync.push({ force: force == true, featureIds });
31
+ break;
32
+ case 'pull':
33
+ data = await webDavSync.pull({ force: force == true, featureIds });
34
+ break;
35
+ case 'disconnect':
36
+ data = webDavSync.disconnect();
37
+ break;
38
+ default:
39
+ return res.status(400).json({ code: 400, message: '不支持的 WebDAV 操作', data: null });
40
+ }
41
+
42
+ return res.json({ code: 0, message: '成功', data });
43
+ } catch (error) {
44
+ const statusCode = error.statusCode || 500;
45
+ console.error(
46
+ `[webdav] ${req.body && req.body.action ? req.body.action : req.method} 失败:`,
47
+ error.message || error.code || error
48
+ );
49
+ return res.status(statusCode).json({
50
+ code: statusCode,
51
+ message: error.message || 'WebDAV 操作失败',
52
+ conflict: error.conflict || null,
53
+ data: null,
54
+ });
55
+ }
56
+ };
@@ -1 +1 @@
1
- <!doctype html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Whistle Mock Plugin</title><link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;700;900&family=Orbitron:wght@400;500;600;700;800;900&family=Share+Tech+Mono&display=swap" rel="stylesheet"><script defer="defer" src="/js/index.js"></script></head><body><div id="root"></div></body></html>
1
+ <!doctype html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Whistle Mock Plugin</title><script defer="defer" src="/js/index.js"></script></head><body><div id="root"></div></body></html>