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
@@ -1,288 +0,0 @@
1
- import React, { useState, useEffect, useCallback } from 'react';
2
- import AppLayout from '../components/AppLayout';
3
- import {
4
- Card, Row, Col, Statistic, Table, Tag, Spin, Empty, Badge,
5
- DatePicker, Input, Space
6
- } from 'antd';
7
- import {
8
- ApiOutlined, ThunderboltOutlined,
9
- BarChartOutlined, RiseOutlined, SearchOutlined
10
- } from '@ant-design/icons';
11
- import axios from 'axios';
12
- import dayjs from 'dayjs';
13
-
14
- const Stats = () => {
15
- const [loading, setLoading] = useState(true);
16
- const [overview, setOverview] = useState({});
17
- const [topData, setTopData] = useState({ topInterfaces: [], topFeatures: [] });
18
- const [timeline, setTimeline] = useState({});
19
- const [recentLogs, setRecentLogs] = useState([]);
20
- const [recentLoading, setRecentLoading] = useState(false);
21
- const [filterDate, setFilterDate] = useState(null);
22
- const [filterUrl, setFilterUrl] = useState('');
23
-
24
- const fetchOverview = useCallback(async () => {
25
- const [oRes, tRes, tlRes] = await Promise.all([
26
- axios.get('/cgi-bin/stats?type=overview'),
27
- axios.get('/cgi-bin/stats?type=top'),
28
- axios.get('/cgi-bin/stats?type=timeline'),
29
- ]);
30
- setOverview(oRes.data.data || {});
31
- setTopData(tRes.data.data || { topInterfaces: [], topFeatures: [] });
32
- setTimeline(tlRes.data.data?.timeline || {});
33
- }, []);
34
-
35
- const fetchRecent = useCallback(async (date, url) => {
36
- setRecentLoading(true);
37
- try {
38
- const params = new URLSearchParams({ type: 'recent' });
39
- if (date) params.append('date', date);
40
- if (url) params.append('url', url);
41
- const res = await axios.get(`/cgi-bin/stats?${params.toString()}`);
42
- setRecentLogs(res.data.data?.logs || []);
43
- } catch (e) {
44
- console.error('加载最近记录失败:', e);
45
- } finally {
46
- setRecentLoading(false);
47
- }
48
- }, []);
49
-
50
- const fetchAll = async () => {
51
- setLoading(true);
52
- try {
53
- await fetchOverview();
54
- const dateStr = filterDate ? dayjs(filterDate).format('YYYY-MM-DD') : '';
55
- await fetchRecent(dateStr, filterUrl);
56
- } catch (e) {
57
- console.error('加载统计数据失败:', e);
58
- } finally {
59
- setLoading(false);
60
- }
61
- };
62
-
63
- useEffect(() => {
64
- fetchAll();
65
- // eslint-disable-next-line react-hooks/exhaustive-deps
66
- }, []);
67
-
68
- const handleDateChange = (date) => {
69
- setFilterDate(date);
70
- const dateStr = date ? dayjs(date).format('YYYY-MM-DD') : '';
71
- fetchRecent(dateStr, filterUrl);
72
- };
73
-
74
- const handleUrlChange = (e) => {
75
- const value = e.target.value;
76
- setFilterUrl(value);
77
- const dateStr = filterDate ? dayjs(filterDate).format('YYYY-MM-DD') : '';
78
- fetchRecent(dateStr, value);
79
- };
80
-
81
- const hitColumns = [
82
- { title: '接口名称', dataIndex: 'name', key: 'name' },
83
- { title: '命中次数', dataIndex: 'count', key: 'count', width: 120 },
84
- {
85
- title: '占比',
86
- key: 'percent',
87
- width: 120,
88
- render: (_, record) => {
89
- const total = overview.totalHits || 1;
90
- const pct = ((record.count / total) * 100).toFixed(1);
91
- return <Badge count={`${pct}%`} style={{ backgroundColor: '#52c41a' }} />;
92
- },
93
- },
94
- ];
95
-
96
- const featureColumns = [
97
- { title: '模块名称', dataIndex: 'name', key: 'name' },
98
- { title: '命中次数', dataIndex: 'count', key: 'count', width: 120 },
99
- ];
100
-
101
- const recentColumns = [
102
- {
103
- title: '时间',
104
- dataIndex: 'timestamp',
105
- key: 'timestamp',
106
- width: 180,
107
- render: (t) => new Date(t).toLocaleString(),
108
- },
109
- { title: '方法', dataIndex: 'method', key: 'method', width: 80 },
110
- { title: 'URL', dataIndex: 'url', key: 'url', ellipsis: true },
111
- {
112
- title: '状态',
113
- dataIndex: 'status',
114
- key: 'status',
115
- width: 80,
116
- render: (s) => <Tag color={s >= 200 && s < 300 ? 'success' : 'default'}>{s}</Tag>,
117
- },
118
- { title: '模块', dataIndex: 'featureName', key: 'featureName', width: 140 },
119
- { title: '接口', dataIndex: 'interfaceName', key: 'interfaceName', width: 160 },
120
- ];
121
-
122
- const timelineEntries = Object.entries(timeline).sort();
123
- const maxTimeline = Math.max(...Object.values(timeline), 1);
124
-
125
- return (
126
- <AppLayout>
127
- <div className="page-container">
128
- <div className="page-title-bar">
129
- <div>
130
- <h1 className="page-title">📊 请求统计</h1>
131
- <div className="page-description">
132
- 查看 Mock 接口的命中情况、流量趋势和未覆盖接口
133
- </div>
134
- </div>
135
- </div>
136
-
137
- {loading ? (
138
- <Spin size="large" style={{ display: 'block', margin: '80px auto' }} />
139
- ) : (
140
- <>
141
- {/* 概览卡片 */}
142
- <Row gutter={16} style={{ marginBottom: 24 }}>
143
- <Col span={6}>
144
- <Card>
145
- <Statistic
146
- title="今日命中"
147
- value={overview.todayHits || 0}
148
- prefix={<ThunderboltOutlined />}
149
- valueStyle={{ color: '#52c41a' }}
150
- />
151
- </Card>
152
- </Col>
153
- <Col span={6}>
154
- <Card>
155
- <Statistic
156
- title="7日总请求"
157
- value={overview.totalRequests || 0}
158
- prefix={<BarChartOutlined />}
159
- />
160
- </Card>
161
- </Col>
162
- <Col span={6}>
163
- <Card>
164
- <Statistic
165
- title="命中率"
166
- value={overview.hitRate || 0}
167
- suffix="%"
168
- prefix={<RiseOutlined />}
169
- valueStyle={{ color: '#1890ff' }}
170
- />
171
- </Card>
172
- </Col>
173
- <Col span={6}>
174
- <Card>
175
- <Statistic
176
- title="活跃接口 / 总数"
177
- value={`${overview.activeInterfaces || 0} / ${overview.totalInterfaces || 0}`}
178
- prefix={<ApiOutlined />}
179
- />
180
- </Card>
181
- </Col>
182
- </Row>
183
-
184
- {/* 趋势图(简单柱状图) */}
185
- <Card title="📈 近7天命中趋势" style={{ marginBottom: 24 }}>
186
- {timelineEntries.length === 0 ? (
187
- <Empty description="暂无数据" />
188
- ) : (
189
- <div style={{ display: 'flex', alignItems: 'flex-end', height: 160, gap: 12, padding: '0 8px' }}>
190
- {timelineEntries.map(([date, count]) => (
191
- <div key={date} style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
192
- <div style={{ fontSize: 12, fontWeight: 600, marginBottom: 4 }}>{count}</div>
193
- <div
194
- style={{
195
- width: '100%',
196
- height: `${(count / maxTimeline) * 120}px`,
197
- background: '#1890ff',
198
- borderRadius: '4px 4px 0 0',
199
- minHeight: count > 0 ? 4 : 0,
200
- transition: 'height 0.3s',
201
- }}
202
- />
203
- <div style={{ fontSize: 11, color: '#999', marginTop: 4 }}>{date.slice(5)}</div>
204
- </div>
205
- ))}
206
- </div>
207
- )}
208
- </Card>
209
-
210
- {/* Top 命中接口 & 功能模块 */}
211
- <Row gutter={16} style={{ marginBottom: 24 }}>
212
- <Col span={12}>
213
- <Card title="🏆 Top 10 命中接口">
214
- {topData.topInterfaces.length === 0 ? (
215
- <Empty description="暂无命中记录" />
216
- ) : (
217
- <Table
218
- dataSource={topData.topInterfaces}
219
- columns={hitColumns}
220
- rowKey="id"
221
- pagination={false}
222
- size="small"
223
- />
224
- )}
225
- </Card>
226
- </Col>
227
- <Col span={12}>
228
- <Card title="📦 Top 10 功能模块">
229
- {topData.topFeatures.length === 0 ? (
230
- <Empty description="暂无命中记录" />
231
- ) : (
232
- <Table
233
- dataSource={topData.topFeatures}
234
- columns={featureColumns}
235
- rowKey="id"
236
- pagination={false}
237
- size="small"
238
- />
239
- )}
240
- </Card>
241
- </Col>
242
- </Row>
243
-
244
- {/* 最近命中 */}
245
- <Card
246
- title="🕐 最近命中记录"
247
- extra={
248
- <Space>
249
- <DatePicker
250
- placeholder="筛选日期"
251
- value={filterDate}
252
- onChange={handleDateChange}
253
- allowClear
254
- style={{ width: 140 }}
255
- />
256
- <Input
257
- placeholder="筛选 URL"
258
- value={filterUrl}
259
- onChange={handleUrlChange}
260
- allowClear
261
- prefix={<SearchOutlined />}
262
- style={{ width: 200 }}
263
- />
264
- </Space>
265
- }
266
- >
267
- {recentLoading ? (
268
- <Spin size="small" style={{ display: 'block', margin: '40px auto' }} />
269
- ) : recentLogs.length === 0 ? (
270
- <Empty description="暂无记录" />
271
- ) : (
272
- <Table
273
- dataSource={recentLogs}
274
- columns={recentColumns}
275
- rowKey="id"
276
- pagination={false}
277
- size="small"
278
- />
279
- )}
280
- </Card>
281
- </>
282
- )}
283
- </div>
284
- </AppLayout>
285
- );
286
- };
287
-
288
- export default Stats;
@@ -1,192 +0,0 @@
1
- /**
2
- * API 服务模块
3
- * 提供功能和接口相关的 API 调用
4
- */
5
- const API_BASE_URL = '/api';
6
-
7
- /**
8
- * 通用 API 请求方法
9
- * @param {string} url - 请求 URL
10
- * @param {object} options - 请求选项
11
- * @returns {Promise<any>} - 请求结果
12
- */
13
- const request = async (url, options = {}) => {
14
- const response = await fetch(url, {
15
- ...options,
16
- headers: {
17
- 'Content-Type': 'application/json',
18
- ...options.headers
19
- }
20
- });
21
-
22
- const data = await response.json();
23
-
24
- if (!response.ok) {
25
- throw new Error(data.message || '请求失败');
26
- }
27
-
28
- return data;
29
- };
30
-
31
- /**
32
- * 功能相关 API
33
- */
34
- export const FeatureAPI = {
35
- /**
36
- * 获取所有功能
37
- * @returns {Promise<Array>} 功能列表
38
- */
39
- getAllFeatures: () => request(`${API_BASE_URL}/features`),
40
-
41
- /**
42
- * 获取单个功能
43
- * @param {string} id - 功能 ID
44
- * @returns {Promise<object>} 功能对象
45
- */
46
- getFeature: (id) => request(`${API_BASE_URL}/features/${id}`),
47
-
48
- /**
49
- * 创建功能
50
- * @param {object} feature - 功能对象
51
- * @returns {Promise<object>} 创建的功能对象
52
- */
53
- createFeature: (feature) => request(`${API_BASE_URL}/features`, {
54
- method: 'POST',
55
- body: JSON.stringify(feature)
56
- }),
57
-
58
- /**
59
- * 更新功能
60
- * @param {string} id - 功能 ID
61
- * @param {object} feature - 功能对象
62
- * @returns {Promise<object>} 更新的功能对象
63
- */
64
- updateFeature: (id, feature) => request(`${API_BASE_URL}/features/${id}`, {
65
- method: 'PUT',
66
- body: JSON.stringify(feature)
67
- }),
68
-
69
- /**
70
- * 删除功能
71
- * @param {string} id - 功能 ID
72
- * @returns {Promise<object>} 删除结果
73
- */
74
- deleteFeature: (id) => request(`${API_BASE_URL}/features/${id}`, {
75
- method: 'DELETE'
76
- }),
77
-
78
- /**
79
- * 获取功能下的所有接口
80
- * @param {string} id - 功能 ID
81
- * @returns {Promise<Array>} 接口列表
82
- */
83
- getFeatureInterfaces: (id) => request(`${API_BASE_URL}/features/${id}/interfaces`)
84
- };
85
-
86
- /**
87
- * 接口相关 API
88
- */
89
- export const InterfaceAPI = {
90
- /**
91
- * 获取单个接口
92
- * @param {string} id - 接口 ID
93
- * @returns {Promise<object>} 接口对象
94
- */
95
- getInterface: (id) => request(`${API_BASE_URL}/interfaces/${id}`),
96
-
97
- /**
98
- * 创建接口
99
- * @param {object} interfaceObj - 接口对象
100
- * @returns {Promise<object>} 创建的接口对象
101
- */
102
- createInterface: (interfaceObj) => request(`${API_BASE_URL}/interfaces`, {
103
- method: 'POST',
104
- body: JSON.stringify(interfaceObj)
105
- }),
106
-
107
- /**
108
- * 更新接口
109
- * @param {string} id - 接口 ID
110
- * @param {object} interfaceObj - 接口对象
111
- * @returns {Promise<object>} 更新的接口对象
112
- */
113
- updateInterface: (id, interfaceObj) => request(`${API_BASE_URL}/interfaces/${id}`, {
114
- method: 'PUT',
115
- body: JSON.stringify(interfaceObj)
116
- }),
117
-
118
- /**
119
- * 删除接口
120
- * @param {string} id - 接口 ID
121
- * @returns {Promise<object>} 删除结果
122
- */
123
- deleteInterface: (id) => request(`${API_BASE_URL}/interfaces/${id}`, {
124
- method: 'DELETE'
125
- }),
126
-
127
- /**
128
- * 测试接口
129
- * @param {string} id - 接口 ID
130
- * @param {object} testData - 测试数据
131
- * @returns {Promise<object>} 测试结果
132
- */
133
- testInterface: (id, testData) => request(`${API_BASE_URL}/interfaces/${id}/test`, {
134
- method: 'POST',
135
- body: JSON.stringify(testData)
136
- })
137
- };
138
-
139
- /**
140
- * 代理相关 API
141
- */
142
- export const ProxyAPI = {
143
- /**
144
- * 获取文件内容
145
- * @param {string} path - 文件路径
146
- * @returns {Promise<object>} 文件内容
147
- */
148
- getFile: (path) => request(`${API_BASE_URL}/proxy/file?path=${encodeURIComponent(path)}`),
149
-
150
- /**
151
- * 保存文件内容
152
- * @param {string} path - 文件路径
153
- * @param {string} content - 文件内容
154
- * @returns {Promise<object>} 保存结果
155
- */
156
- saveFile: (path, content) => request(`${API_BASE_URL}/proxy/file`, {
157
- method: 'POST',
158
- body: JSON.stringify({ path, content })
159
- }),
160
-
161
- /**
162
- * 列出文件
163
- * @param {string} path - 目录路径
164
- * @returns {Promise<Array>} 文件列表
165
- */
166
- listFiles: (path = '') => request(`${API_BASE_URL}/proxy/files?path=${encodeURIComponent(path)}`),
167
-
168
- /**
169
- * 删除文件
170
- * @param {string} path - 文件路径
171
- * @returns {Promise<object>} 删除结果
172
- */
173
- deleteFile: (path) => request(`${API_BASE_URL}/proxy/file?path=${encodeURIComponent(path)}`, {
174
- method: 'DELETE'
175
- }),
176
-
177
- /**
178
- * 测试 mock 数据模板
179
- * @param {string} template - 模板字符串
180
- * @returns {Promise<object>} 生成的数据
181
- */
182
- testMockTemplate: (template) => request(`${API_BASE_URL}/proxy/test`, {
183
- method: 'POST',
184
- body: JSON.stringify({ template })
185
- }),
186
-
187
- /**
188
- * 获取版本信息
189
- * @returns {Promise<object>} 版本信息文件内容
190
- */
191
- getVersionInfo: () => request('/cgi-bin/version')
192
- };