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.
- package/VERSION.md +29 -0
- package/app/cgi-bin/interfaces.js +2 -0
- package/app/cgi-bin/settings.js +1 -0
- package/app/cgi-bin/stats.js +22 -12
- package/app/cgi-bin/webdav.js +56 -0
- package/app/public/index.html +1 -1
- package/app/public/js/11.index.js +2 -0
- package/app/public/js/11.index.js.LICENSE.txt +5 -0
- package/app/public/js/704.index.js +2 -0
- package/app/public/js/704.index.js.LICENSE.txt +1 -0
- package/app/public/js/798.index.js +2 -0
- package/app/public/js/798.index.js.LICENSE.txt +53 -0
- package/app/public/js/807.index.js +2 -0
- package/app/public/js/807.index.js.LICENSE.txt +1 -0
- package/app/public/js/968.index.js +2 -0
- package/app/public/js/968.index.js.LICENSE.txt +12 -0
- package/app/public/js/index.js +1 -1
- package/app/public/js/index.js.LICENSE.txt +0 -173
- package/lib/routes/settings.js +1 -0
- package/lib/webdav-sync.js +806 -0
- package/package.json +9 -2
- package/API.md +0 -247
- package/COLUMN_RESIZE_IMPLEMENTATION.md +0 -162
- package/FEATURE_ENHANCEMENT.md +0 -157
- package/TEST_GUIDE.md +0 -156
- package/data/features.json +0 -1
- package/data/interfaces.json +0 -1
- package/index.html +0 -52
- package/initialize.js +0 -32
- package/public/bundle.js +0 -2
- package/public/bundle.js.LICENSE.txt +0 -38
- package/public/index.html +0 -1
- package/public/plugin.mock-plugin.html +0 -14
- package/rules.txt +0 -22
- package/ui/src/App.css +0 -173
- package/ui/src/components/AppLayout.js +0 -407
- package/ui/src/components/FeatureCard.js +0 -58
- package/ui/src/components/KeyboardShortcutsHelp.js +0 -58
- package/ui/src/components/Layout.js +0 -94
- package/ui/src/components/MainNav.js +0 -42
- package/ui/src/components/PageContainer.js +0 -16
- package/ui/src/components/VersionModal.js +0 -99
- package/ui/src/components/interface-management/DataManagementDrawer.js +0 -115
- package/ui/src/components/interface-management/DataManagementModal.js +0 -110
- package/ui/src/components/interface-management/InterfaceTestModal.js +0 -313
- package/ui/src/components/interface-management/PreviewModal.js +0 -48
- package/ui/src/components/interface-management/README.md +0 -184
- package/ui/src/components/interface-management/RealResponseRecorder.js +0 -165
- package/ui/src/components/interface-management/ResponseContentEditor.js +0 -645
- package/ui/src/components/interface-management/constants.js +0 -36
- package/ui/src/components/interface-management/index.js +0 -7
- package/ui/src/components/interface-management/utils.js +0 -103
- package/ui/src/hooks/useKeyboardShortcuts.js +0 -99
- package/ui/src/index.html +0 -14
- package/ui/src/index.js +0 -50
- package/ui/src/pages/InterfaceManagementV2.js +0 -2351
- package/ui/src/pages/MockDataV2.js +0 -1089
- package/ui/src/pages/Settings.js +0 -299
- package/ui/src/pages/Sniffer.js +0 -521
- package/ui/src/pages/Stats.js +0 -288
- package/ui/src/services/del_api.js +0 -192
- package/ui/src/styles/app-layout.css +0 -373
- package/ui/src/styles/cyber-theme.css +0 -1234
- package/ui/src/styles/earth-theme.css +0 -618
- package/ui/src/styles/global.css +0 -224
- package/ui/src/styles/interface-management.css +0 -1321
- package/ui/src/styles/mock-data.css +0 -1185
- package/ui/src/styles/settings.css +0 -252
- package/ui/src/styles/version-modal.css +0 -116
- 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,
|
package/app/cgi-bin/settings.js
CHANGED
package/app/cgi-bin/stats.js
CHANGED
|
@@ -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: [],
|
|
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
|
|
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 >=
|
|
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.
|
|
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
|
|
102
|
-
|
|
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 =
|
|
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 =
|
|
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
|
+
};
|
package/app/public/index.html
CHANGED
|
@@ -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><
|
|
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>
|