whistle.mock-plugins 1.0.0

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 (68) hide show
  1. package/API.md +247 -0
  2. package/README.md +91 -0
  3. package/app/cgi-bin/features.js +285 -0
  4. package/app/cgi-bin/index.js +31 -0
  5. package/app/cgi-bin/interfaces.js +409 -0
  6. package/app/cgi-bin/logs.js +151 -0
  7. package/app/cgi-bin/test-interface.js +152 -0
  8. package/app/public/index.html +1 -0
  9. package/app/public/js/index.js +2 -0
  10. package/app/public/js/index.js.LICENSE.txt +150 -0
  11. package/app/util/index.js +24 -0
  12. package/index.html +52 -0
  13. package/initialize.js +32 -0
  14. package/lib/dataManager.js +500 -0
  15. package/lib/index.js +3 -0
  16. package/lib/models.js +64 -0
  17. package/lib/routes/features.js +213 -0
  18. package/lib/routes/interfaces.js +304 -0
  19. package/lib/routes/proxy.js +239 -0
  20. package/lib/ruleManager.js +551 -0
  21. package/lib/rules-server.js +267 -0
  22. package/lib/server.js +805 -0
  23. package/lib/storage.js +16 -0
  24. package/lib/ui-server.js +68 -0
  25. package/package.json +51 -0
  26. package/public/bundle.js +2 -0
  27. package/public/bundle.js.LICENSE.txt +38 -0
  28. package/public/index.html +1 -0
  29. package/public/plugin.mock-plugin.html +14 -0
  30. package/rules.txt +22 -0
  31. package/test/rulesServer.test.js +120 -0
  32. package/ui/src/App.css +173 -0
  33. package/ui/src/App.js +24 -0
  34. package/ui/src/components/AppLayout.js +149 -0
  35. package/ui/src/components/FeatureCard.js +53 -0
  36. package/ui/src/components/Layout.js +94 -0
  37. package/ui/src/components/MainNav.js +42 -0
  38. package/ui/src/components/PageContainer.js +16 -0
  39. package/ui/src/index.html +11 -0
  40. package/ui/src/index.js +43 -0
  41. package/ui/src/pages/FileProxy.js +235 -0
  42. package/ui/src/pages/FilesPage.js +292 -0
  43. package/ui/src/pages/Home.js +128 -0
  44. package/ui/src/pages/HomePage.js +195 -0
  45. package/ui/src/pages/InterfaceManagement.js +829 -0
  46. package/ui/src/pages/InterfacePage.js +342 -0
  47. package/ui/src/pages/MockData.js +472 -0
  48. package/ui/src/pages/Settings.js +192 -0
  49. package/ui/src/pages/SettingsPage.js +165 -0
  50. package/ui/src/pages/UrlRedirect.js +225 -0
  51. package/ui/src/pages/Welcome.js +155 -0
  52. package/ui/src/services/api.js +186 -0
  53. package/ui/src/styles/app-layout.css +337 -0
  54. package/ui/src/styles/feature-card.css +172 -0
  55. package/ui/src/styles/file-proxy.css +301 -0
  56. package/ui/src/styles/global.css +224 -0
  57. package/ui/src/styles/home.css +123 -0
  58. package/ui/src/styles/index.css +419 -0
  59. package/ui/src/styles/interface-management.css +694 -0
  60. package/ui/src/styles/layout.css +159 -0
  61. package/ui/src/styles/logs-page.css +427 -0
  62. package/ui/src/styles/main-nav.css +52 -0
  63. package/ui/src/styles/mock-data.css +850 -0
  64. package/ui/src/styles/page-container.css +11 -0
  65. package/ui/src/styles/settings.css +208 -0
  66. package/ui/src/styles/url-redirect.css +279 -0
  67. package/ui/src/styles/welcome.css +282 -0
  68. package/webpack.config.js +49 -0
@@ -0,0 +1,165 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import { Card, Form, Input, Button, Switch, message, Divider, Typography, Space, Select } from 'antd';
3
+ import { SaveOutlined, ReloadOutlined } from '@ant-design/icons';
4
+
5
+ const { Title, Paragraph, Text } = Typography;
6
+ const { Option } = Select;
7
+
8
+ // 模拟设置 API
9
+ const SettingsAPI = {
10
+ getSettings: () => Promise.resolve({
11
+ mockFolder: './mocks',
12
+ enableAutoSave: true,
13
+ responseDelay: 200,
14
+ logLevel: 'info',
15
+ }),
16
+ saveSettings: (settings) => Promise.resolve(settings)
17
+ };
18
+
19
+ const SettingsPage = () => {
20
+ const [form] = Form.useForm();
21
+ const [loading, setLoading] = useState(false);
22
+
23
+ useEffect(() => {
24
+ fetchSettings();
25
+ }, []);
26
+
27
+ const fetchSettings = async () => {
28
+ setLoading(true);
29
+ try {
30
+ const settings = await SettingsAPI.getSettings();
31
+ form.setFieldsValue(settings);
32
+ } catch (error) {
33
+ console.error("获取设置失败:", error);
34
+ message.error('获取设置失败');
35
+ } finally {
36
+ setLoading(false);
37
+ }
38
+ };
39
+
40
+ const handleSave = async () => {
41
+ try {
42
+ const values = await form.validateFields();
43
+ setLoading(true);
44
+ await SettingsAPI.saveSettings(values);
45
+ message.success('设置保存成功');
46
+ } catch (error) {
47
+ console.error("保存设置失败:", error);
48
+ message.error('保存设置失败');
49
+ } finally {
50
+ setLoading(false);
51
+ }
52
+ };
53
+
54
+ const handleReset = () => {
55
+ form.resetFields();
56
+ fetchSettings();
57
+ };
58
+
59
+ return (
60
+ <div className="settings-page">
61
+ <Card title="全局设置">
62
+ <Form
63
+ form={form}
64
+ layout="vertical"
65
+ initialValues={{
66
+ mockFolder: './mocks',
67
+ enableAutoSave: true,
68
+ responseDelay: 200,
69
+ logLevel: 'info',
70
+ }}
71
+ >
72
+ <Title level={4}>基本设置</Title>
73
+ <Form.Item
74
+ name="mockFolder"
75
+ label="Mock 数据文件夹路径"
76
+ rules={[{ required: true, message: '请输入 Mock 数据文件夹路径!' }]}
77
+ >
78
+ <Input placeholder="./mocks" />
79
+ </Form.Item>
80
+
81
+ <Form.Item
82
+ name="enableAutoSave"
83
+ label="启用自动保存"
84
+ valuePropName="checked"
85
+ >
86
+ <Switch />
87
+ </Form.Item>
88
+
89
+ <Form.Item
90
+ name="responseDelay"
91
+ label="默认响应延迟(ms)"
92
+ rules={[{ required: true, message: '请输入默认响应延迟!' }]}
93
+ >
94
+ <Input type="number" placeholder="200" />
95
+ </Form.Item>
96
+
97
+ <Form.Item
98
+ name="logLevel"
99
+ label="日志级别"
100
+ rules={[{ required: true, message: '请选择日志级别!' }]}
101
+ >
102
+ <Select placeholder="请选择日志级别">
103
+ <Option value="debug">Debug</Option>
104
+ <Option value="info">Info</Option>
105
+ <Option value="warn">Warning</Option>
106
+ <Option value="error">Error</Option>
107
+ </Select>
108
+ </Form.Item>
109
+
110
+ <Divider />
111
+
112
+ <Title level={4}>使用说明</Title>
113
+ <Paragraph>
114
+ Whistle Mock 插件用于快速创建 API 模拟数据,支持以下功能:
115
+ </Paragraph>
116
+
117
+ <Paragraph>
118
+ <ul>
119
+ <li>
120
+ <Text strong>接口管理</Text> - 创建和管理模拟接口,支持 GET、POST 等多种请求方法
121
+ </li>
122
+ <li>
123
+ <Text strong>文件管理</Text> - 编辑 JSON 文件作为模拟数据源
124
+ </li>
125
+ <li>
126
+ <Text strong>Mock 模板语法</Text> - 支持使用 Mock.js 模板语法生成随机数据
127
+ </li>
128
+ <li>
129
+ <Text strong>路径匹配规则</Text> - 支持通配符和正则表达式匹配请求路径
130
+ </li>
131
+ </ul>
132
+ </Paragraph>
133
+
134
+ <Paragraph>
135
+ 要启用 Whistle Mock 插件,请在 Whistle 界面添加以下规则:
136
+ <pre>
137
+ example.com mock://
138
+ </pre>
139
+ </Paragraph>
140
+
141
+ <Divider />
142
+
143
+ <Space>
144
+ <Button
145
+ type="primary"
146
+ icon={<SaveOutlined />}
147
+ onClick={handleSave}
148
+ loading={loading}
149
+ >
150
+ 保存设置
151
+ </Button>
152
+ <Button
153
+ icon={<ReloadOutlined />}
154
+ onClick={handleReset}
155
+ >
156
+ 重置
157
+ </Button>
158
+ </Space>
159
+ </Form>
160
+ </Card>
161
+ </div>
162
+ );
163
+ };
164
+
165
+ export default SettingsPage;
@@ -0,0 +1,225 @@
1
+ import React, { useState } from 'react';
2
+ import AppLayout from '../components/AppLayout';
3
+ import '../styles/url-redirect.css';
4
+
5
+ const UrlRedirect = () => {
6
+ const [urlRedirects, setUrlRedirects] = useState([
7
+ {
8
+ id: 1,
9
+ sourceUrl: 'https://api.example.com/users',
10
+ targetUrl: 'http://localhost:3000/mock/users',
11
+ description: '用户API重定向到本地',
12
+ active: true
13
+ },
14
+ {
15
+ id: 2,
16
+ sourceUrl: 'https://api.example.com/products',
17
+ targetUrl: 'http://localhost:3000/mock/products',
18
+ description: '产品API重定向到本地',
19
+ active: false
20
+ }
21
+ ]);
22
+
23
+ const [isModalOpen, setIsModalOpen] = useState(false);
24
+ const [currentRedirect, setCurrentRedirect] = useState(null);
25
+ const [sourceUrl, setSourceUrl] = useState('');
26
+ const [targetUrl, setTargetUrl] = useState('');
27
+ const [description, setDescription] = useState('');
28
+
29
+ const openModal = (redirect = null) => {
30
+ if (redirect) {
31
+ setCurrentRedirect(redirect);
32
+ setSourceUrl(redirect.sourceUrl);
33
+ setTargetUrl(redirect.targetUrl);
34
+ setDescription(redirect.description);
35
+ } else {
36
+ setCurrentRedirect(null);
37
+ setSourceUrl('');
38
+ setTargetUrl('');
39
+ setDescription('');
40
+ }
41
+ setIsModalOpen(true);
42
+ };
43
+
44
+ const closeModal = () => {
45
+ setIsModalOpen(false);
46
+ };
47
+
48
+ const handleSubmit = (e) => {
49
+ e.preventDefault();
50
+
51
+ if (!sourceUrl || !targetUrl) {
52
+ alert('请填写源URL和目标URL');
53
+ return;
54
+ }
55
+
56
+ if (currentRedirect) {
57
+ // 编辑现有配置
58
+ setUrlRedirects(urlRedirects.map(redirect =>
59
+ redirect.id === currentRedirect.id
60
+ ? { ...redirect, sourceUrl, targetUrl, description }
61
+ : redirect
62
+ ));
63
+ } else {
64
+ // 添加新配置
65
+ const newRedirect = {
66
+ id: urlRedirects.length > 0 ? Math.max(...urlRedirects.map(r => r.id)) + 1 : 1,
67
+ sourceUrl,
68
+ targetUrl,
69
+ description,
70
+ active: true
71
+ };
72
+ setUrlRedirects([...urlRedirects, newRedirect]);
73
+ }
74
+
75
+ closeModal();
76
+ };
77
+
78
+ const toggleActive = (id) => {
79
+ setUrlRedirects(urlRedirects.map(redirect =>
80
+ redirect.id === id ? { ...redirect, active: !redirect.active } : redirect
81
+ ));
82
+ };
83
+
84
+ const deleteRedirect = (id) => {
85
+ if (window.confirm('确定要删除此条重定向配置吗?')) {
86
+ setUrlRedirects(urlRedirects.filter(redirect => redirect.id !== id));
87
+ }
88
+ };
89
+
90
+ return (
91
+ <AppLayout>
92
+ <div className="url-redirect-container">
93
+ <div className="page-title-bar">
94
+ <div>
95
+ <h1 className="page-title">URL重定向配置</h1>
96
+ <div className="page-description">
97
+ 配置URL重定向规则,支持路径和查询参数匹配
98
+ </div>
99
+ </div>
100
+ <div className="page-actions">
101
+ <button className="add-button" onClick={() => openModal()}>
102
+ 添加URL重定向
103
+ </button>
104
+ </div>
105
+ </div>
106
+
107
+ <div className="redirect-list-container">
108
+ {urlRedirects.length === 0 ? (
109
+ <div className="empty-data">
110
+ <div className="empty-icon">🔄</div>
111
+ <div className="empty-text">暂无URL重定向配置</div>
112
+ <button className="add-button" onClick={() => openModal()}>
113
+ 添加URL重定向
114
+ </button>
115
+ </div>
116
+ ) : (
117
+ <table className="redirect-table">
118
+ <thead>
119
+ <tr>
120
+ <th>状态</th>
121
+ <th>源URL</th>
122
+ <th>目标URL</th>
123
+ <th>描述</th>
124
+ <th>操作</th>
125
+ </tr>
126
+ </thead>
127
+ <tbody>
128
+ {urlRedirects.map(redirect => (
129
+ <tr key={redirect.id} className={redirect.active ? '' : 'inactive'}>
130
+ <td>
131
+ <label className="switch">
132
+ <input
133
+ type="checkbox"
134
+ checked={redirect.active}
135
+ onChange={() => toggleActive(redirect.id)}
136
+ />
137
+ <span className="slider"></span>
138
+ </label>
139
+ </td>
140
+ <td className="url-text">{redirect.sourceUrl}</td>
141
+ <td className="url-text">{redirect.targetUrl}</td>
142
+ <td>{redirect.description || '-'}</td>
143
+ <td>
144
+ <div className="action-buttons">
145
+ <button
146
+ className="edit-button"
147
+ onClick={() => openModal(redirect)}
148
+ >
149
+ 编辑
150
+ </button>
151
+ <button
152
+ className="delete-button"
153
+ onClick={() => deleteRedirect(redirect.id)}
154
+ >
155
+ 删除
156
+ </button>
157
+ </div>
158
+ </td>
159
+ </tr>
160
+ ))}
161
+ </tbody>
162
+ </table>
163
+ )}
164
+ </div>
165
+
166
+ {isModalOpen && (
167
+ <div className="modal-overlay">
168
+ <div className="modal">
169
+ <div className="modal-header">
170
+ <h2>{currentRedirect ? '编辑URL重定向' : '添加URL重定向'}</h2>
171
+ <button className="close-button" onClick={closeModal}>×</button>
172
+ </div>
173
+ <form className="redirect-form" onSubmit={handleSubmit}>
174
+ <div className="form-group">
175
+ <label>源URL</label>
176
+ <input
177
+ type="text"
178
+ value={sourceUrl}
179
+ onChange={(e) => setSourceUrl(e.target.value)}
180
+ placeholder="https://api.example.com/endpoint"
181
+ required
182
+ />
183
+ <div className="help-text">需要重定向的原始URL</div>
184
+ </div>
185
+
186
+ <div className="form-group">
187
+ <label>目标URL</label>
188
+ <input
189
+ type="text"
190
+ value={targetUrl}
191
+ onChange={(e) => setTargetUrl(e.target.value)}
192
+ placeholder="http://localhost:3000/mock/endpoint"
193
+ required
194
+ />
195
+ <div className="help-text">重定向的目标地址</div>
196
+ </div>
197
+
198
+ <div className="form-group">
199
+ <label>描述(可选)</label>
200
+ <input
201
+ type="text"
202
+ value={description}
203
+ onChange={(e) => setDescription(e.target.value)}
204
+ placeholder="重定向描述"
205
+ />
206
+ </div>
207
+
208
+ <div className="form-actions">
209
+ <button type="button" className="cancel-button" onClick={closeModal}>
210
+ 取消
211
+ </button>
212
+ <button type="submit" className="submit-button">
213
+ {currentRedirect ? '保存修改' : '添加重定向'}
214
+ </button>
215
+ </div>
216
+ </form>
217
+ </div>
218
+ </div>
219
+ )}
220
+ </div>
221
+ </AppLayout>
222
+ );
223
+ };
224
+
225
+ export default UrlRedirect;
@@ -0,0 +1,155 @@
1
+ import React from 'react';
2
+ import { Link } from 'react-router-dom';
3
+ import AppLayout from '../components/AppLayout';
4
+ import '../styles/welcome.css';
5
+
6
+ const Welcome = () => {
7
+ // 模拟统计数据
8
+ const stats = {
9
+ featuresCount: 3,
10
+ interfacesCount: 15,
11
+ activeInterfacesCount: 12,
12
+ requestsHandledCount: 256
13
+ };
14
+
15
+ // 模拟最近活动数据
16
+ const recentActivities = [
17
+ { id: 1, type: '创建', target: '功能', name: '商品列表', time: '2023-11-05 14:30' },
18
+ { id: 2, type: '修改', target: '接口', name: '用户登录', time: '2023-11-05 14:15' },
19
+ { id: 3, type: '启用', target: '接口', name: '获取用户信息', time: '2023-11-05 13:45' },
20
+ { id: 4, type: '创建', target: '接口', name: '商品详情', time: '2023-11-05 13:30' },
21
+ { id: 5, type: '禁用', target: '功能', name: '购物车', time: '2023-11-05 11:20' }
22
+ ];
23
+
24
+ return (
25
+ <AppLayout>
26
+ <div className="welcome-container">
27
+ <div className="welcome-header">
28
+ <h1>欢迎使用 Whistle Mock 数据工厂</h1>
29
+ <p>快速创建和管理模拟数据,提高前端开发效率</p>
30
+ </div>
31
+
32
+ <div className="cards-container">
33
+ {/* 功能概览卡片 */}
34
+ <div className="card stats-card">
35
+ <div className="card-header">
36
+ <div className="card-icon">📊</div>
37
+ <h2>功能概览</h2>
38
+ </div>
39
+ <div className="card-content">
40
+ <div className="stat-item">
41
+ <div className="stat-value">{stats.featuresCount}</div>
42
+ <div className="stat-label">功能模块</div>
43
+ </div>
44
+ <div className="stat-item">
45
+ <div className="stat-value">{stats.interfacesCount}</div>
46
+ <div className="stat-label">接口配置</div>
47
+ </div>
48
+ <div className="stat-item">
49
+ <div className="stat-value">{stats.activeInterfacesCount}</div>
50
+ <div className="stat-label">启用接口</div>
51
+ </div>
52
+ <div className="stat-item">
53
+ <div className="stat-value">{stats.requestsHandledCount}</div>
54
+ <div className="stat-label">已处理请求</div>
55
+ </div>
56
+ </div>
57
+ </div>
58
+
59
+ {/* 快速开始卡片 */}
60
+ <div className="card quickstart-card">
61
+ <div className="card-header">
62
+ <div className="card-icon">🚀</div>
63
+ <h2>快速开始</h2>
64
+ </div>
65
+ <div className="card-content">
66
+ <div className="steps">
67
+ <div className="step">
68
+ <div className="step-number">1</div>
69
+ <div className="step-content">
70
+ <div className="step-title">创建功能模块</div>
71
+ <div className="step-desc">创建一个功能模块,用于组织相关接口</div>
72
+ </div>
73
+ </div>
74
+ <div className="step">
75
+ <div className="step-number">2</div>
76
+ <div className="step-content">
77
+ <div className="step-title">添加接口配置</div>
78
+ <div className="step-desc">配置API接口的URL匹配规则和响应数据</div>
79
+ </div>
80
+ </div>
81
+ <div className="step">
82
+ <div className="step-number">3</div>
83
+ <div className="step-content">
84
+ <div className="step-title">启用接口使用</div>
85
+ <div className="step-desc">启用功能模块和接口,即可在Whistle中使用</div>
86
+ </div>
87
+ </div>
88
+ </div>
89
+ <div className="quickstart-actions">
90
+ <Link to="/mock-data">
91
+ <button className="quickstart-button">开始创建功能</button>
92
+ </Link>
93
+ </div>
94
+ </div>
95
+ </div>
96
+
97
+ {/* 使用说明卡片 */}
98
+ <div className="card usage-card">
99
+ <div className="card-header">
100
+ <div className="card-icon">📘</div>
101
+ <h2>使用说明</h2>
102
+ </div>
103
+ <div className="card-content">
104
+ <div className="usage-item">
105
+ <div className="usage-title">添加Whistle规则</div>
106
+ <div className="usage-desc">
107
+ 在Whistle中添加如下规则:
108
+ <pre className="code-block">example.com mock://</pre>
109
+ </div>
110
+ </div>
111
+ <div className="usage-item">
112
+ <div className="usage-title">匹配规则说明</div>
113
+ <div className="usage-desc">
114
+ 支持多种匹配方式:
115
+ <ul className="usage-list">
116
+ <li>精确匹配: <code>/api/users</code></li>
117
+ <li>通配符匹配: <code>/api/users/*</code></li>
118
+ <li>正则匹配: <code>/\/api\/products\/\d+/</code></li>
119
+ </ul>
120
+ </div>
121
+ </div>
122
+ </div>
123
+ </div>
124
+ </div>
125
+
126
+ {/* 最近活动 */}
127
+ <div className="recent-activities">
128
+ <div className="section-header">
129
+ <h2>最近活动</h2>
130
+ </div>
131
+ <div className="activities-list">
132
+ {recentActivities.map(activity => (
133
+ <div key={activity.id} className="activity-item">
134
+ <div className="activity-icon">
135
+ {activity.type === '创建' ? '➕' :
136
+ activity.type === '修改' ? '✏️' :
137
+ activity.type === '启用' ? '✅' :
138
+ activity.type === '禁用' ? '❌' : '🔄'}
139
+ </div>
140
+ <div className="activity-content">
141
+ <div className="activity-title">
142
+ {activity.type}了{activity.target}「{activity.name}」
143
+ </div>
144
+ <div className="activity-time">{activity.time}</div>
145
+ </div>
146
+ </div>
147
+ ))}
148
+ </div>
149
+ </div>
150
+ </div>
151
+ </AppLayout>
152
+ );
153
+ };
154
+
155
+ export default Welcome;