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,58 +0,0 @@
1
- import React from 'react';
2
- import '../styles/feature-card.css';
3
-
4
- const FeatureCard = ({ feature }) => {
5
- const { name, description, active, interfaceCount, createdAt } = feature;
6
-
7
- // 生成随机的主题色(根据当前主题选择色系)
8
- const getRandomColor = () => {
9
- const isEarth = document.body.classList.contains('earth-theme');
10
- if (isEarth) {
11
- const colors = ['#b87333', '#6b8e23', '#d4a017', '#8d6e63', '#c0392b'];
12
- return colors[Math.floor(Math.random() * colors.length)];
13
- }
14
- const colors = ['#00f0ff', '#00ff41', '#f0e800', '#ff00a0', '#bd00ff'];
15
- return colors[Math.floor(Math.random() * colors.length)];
16
- };
17
-
18
- const themeColor = getRandomColor();
19
-
20
- return (
21
- <div className="feature-card">
22
- <div className="feature-card-color-bar" style={{ backgroundColor: themeColor }}></div>
23
- <div className="feature-card-header">
24
- <h3 className="feature-card-title">{name}</h3>
25
- <span className={`feature-card-badge ${active ? 'active' : 'inactive'}`}>
26
- {active ? '已启用' : '未启用'}
27
- </span>
28
- </div>
29
- <div className="feature-card-content">
30
- <p className="feature-card-description">{description || '没有描述'}</p>
31
- <div className="feature-card-stats">
32
- <div className="feature-stat">
33
- <span className="feature-stat-value">{interfaceCount}</span>
34
- <span className="feature-stat-label">接口数量</span>
35
- </div>
36
- {createdAt && (
37
- <div className="feature-stat">
38
- <span className="feature-stat-label">创建于</span>
39
- <span className="feature-stat-value feature-date">{createdAt}</span>
40
- </div>
41
- )}
42
- </div>
43
- </div>
44
- <div className="feature-card-footer">
45
- <button className="card-btn edit-btn">
46
- <i className="icon-edit"></i>
47
- 编辑
48
- </button>
49
- <button className="card-btn primary-btn" style={{ borderColor: themeColor, color: themeColor }}>
50
- <i className="icon-manage"></i>
51
- 管理接口
52
- </button>
53
- </div>
54
- </div>
55
- );
56
- };
57
-
58
- export default FeatureCard;
@@ -1,58 +0,0 @@
1
- import React from 'react';
2
- import { Modal, Table } from 'antd';
3
- import { getShortcutsList } from '../hooks/useKeyboardShortcuts';
4
-
5
- const KeyboardShortcutsHelp = ({ visible, onClose }) => {
6
- const shortcuts = getShortcutsList();
7
-
8
- const columns = [
9
- {
10
- title: '快捷键',
11
- dataIndex: 'key',
12
- key: 'key',
13
- width: 180,
14
- render: (text) => (
15
- <kbd style={{
16
- display: 'inline-block',
17
- padding: '4px 8px',
18
- fontSize: '12px',
19
- fontFamily: 'monospace',
20
- lineHeight: 1,
21
- color: '#333',
22
- background: '#f5f5f5',
23
- border: '1px solid #d9d9d9',
24
- borderRadius: '4px',
25
- boxShadow: '0 1px 0 rgba(0,0,0,0.1)',
26
- }}>
27
- {text}
28
- </kbd>
29
- ),
30
- },
31
- {
32
- title: '功能',
33
- dataIndex: 'desc',
34
- key: 'desc',
35
- },
36
- ];
37
-
38
- return (
39
- <Modal
40
- title="⌨️ 键盘快捷键"
41
- open={visible}
42
- onCancel={onClose}
43
- footer={null}
44
- width={480}
45
- >
46
- <Table
47
- dataSource={shortcuts.map((s, i) => ({ ...s, id: i }))}
48
- columns={columns}
49
- rowKey="id"
50
- pagination={false}
51
- size="small"
52
- showHeader={false}
53
- />
54
- </Modal>
55
- );
56
- };
57
-
58
- export default KeyboardShortcutsHelp;
@@ -1,94 +0,0 @@
1
- import React, { useState } from 'react';
2
- import '../styles/layout.css';
3
-
4
- const Layout = ({ children }) => {
5
- const [activeFeature, setActiveFeature] = useState(null);
6
- const [features, setFeatures] = useState([
7
- { id: 1, name: '用户数据', active: true, count: 3 },
8
- { id: 2, name: '订单信息', active: false, count: 2 },
9
- { id: 3, name: '商品列表', active: false, count: 5 }
10
- ]);
11
-
12
- const handleFeatureClick = (id) => {
13
- setActiveFeature(id);
14
- // 更新功能状态
15
- setFeatures(features.map(feature => ({
16
- ...feature,
17
- active: feature.id === id ? !feature.active : feature.active
18
- })));
19
- };
20
-
21
- const handleCreateFeature = () => {
22
- // 模拟创建新功能
23
- const newId = features.length > 0 ? Math.max(...features.map(f => f.id)) + 1 : 1;
24
- const newFeature = {
25
- id: newId,
26
- name: `新功能 ${newId}`,
27
- active: false,
28
- count: 0
29
- };
30
- setFeatures([...features, newFeature]);
31
- };
32
-
33
- return (
34
- <div className="layout">
35
- {/* 顶部导航栏 */}
36
- <div className="header">
37
- <div className="logo">
38
- <div className="logo-icon">W</div>
39
- Whistle 接口代理插件
40
- </div>
41
- <div className="search">
42
- <input type="text" placeholder="搜索..." />
43
- </div>
44
- <div className="header-actions">
45
- <button>
46
- <i>🔄</i> 刷新
47
- </button>
48
- <button>
49
- <i>⚙️</i> 设置
50
- </button>
51
- </div>
52
- </div>
53
-
54
- {/* 主内容区域 */}
55
- <div className="main-container">
56
- {/* 左侧导航 */}
57
- <div className="sidebar">
58
- <div className="create-feature">
59
- <button onClick={handleCreateFeature}>+ 创建新功能</button>
60
- </div>
61
-
62
- <div className="feature-list">
63
- {features.map(feature => (
64
- <div
65
- key={feature.id}
66
- className={`feature-item ${activeFeature === feature.id ? 'active' : ''}`}
67
- onClick={() => handleFeatureClick(feature.id)}
68
- >
69
- <div className="feature-item-name">
70
- <div className={`status-icon ${feature.active ? 'active' : ''}`}>
71
- {feature.active ? '✓' : ''}
72
- </div>
73
- {feature.name}
74
- </div>
75
- <div className="feature-item-count">{feature.count}</div>
76
- </div>
77
- ))}
78
- </div>
79
-
80
- <div className="sidebar-footer">
81
- Whistle Mock Plugin v0.2.0
82
- </div>
83
- </div>
84
-
85
- {/* 内容区 */}
86
- <div className="content">
87
- {children}
88
- </div>
89
- </div>
90
- </div>
91
- );
92
- };
93
-
94
- export default Layout;
@@ -1,42 +0,0 @@
1
- import React from 'react';
2
- import { Link, useLocation } from 'react-router-dom';
3
- import '../styles/main-nav.css';
4
-
5
- const MainNav = () => {
6
- const location = useLocation();
7
- const pathname = location.pathname;
8
-
9
- // 检查当前路径是否匹配导航项
10
- const isActive = (path) => {
11
- if (path === '/' && pathname === '/') return true;
12
- if (path !== '/' && pathname.startsWith(path)) return true;
13
- return false;
14
- };
15
-
16
- return (
17
- <div className="main-nav">
18
- <div className="main-header">
19
- <h1>Whistle Mock 数据工厂</h1>
20
- </div>
21
- <nav className="nav-tabs">
22
- <Link to="/" className={`nav-tab ${isActive('/') ? 'active' : ''}`}>
23
- 首页
24
- </Link>
25
- <Link to="/mock" className={`nav-tab ${isActive('/mock') ? 'active' : ''}`}>
26
- Mock数据
27
- </Link>
28
- <Link to="/file-proxy" className={`nav-tab ${isActive('/file-proxy') ? 'active' : ''}`}>
29
- 接口配置
30
- </Link>
31
- <Link to="/url-redirect" className={`nav-tab ${isActive('/url-redirect') ? 'active' : ''}`}>
32
- 规则管理
33
- </Link>
34
- <Link to="/settings" className={`nav-tab ${isActive('/settings') ? 'active' : ''}`}>
35
- 设置
36
- </Link>
37
- </nav>
38
- </div>
39
- );
40
- };
41
-
42
- export default MainNav;
@@ -1,16 +0,0 @@
1
- import React from 'react';
2
- import MainNav from './MainNav';
3
- import '../styles/page-container.css';
4
-
5
- const PageContainer = ({ children }) => {
6
- return (
7
- <div className="page-container">
8
- <MainNav />
9
- <div className="page-content">
10
- {children}
11
- </div>
12
- </div>
13
- );
14
- };
15
-
16
- export default PageContainer;
@@ -1,99 +0,0 @@
1
- import React, { useState, useEffect } from 'react';
2
- import { Modal, Spin, Alert } from 'antd';
3
- import ReactMarkdown from 'react-markdown';
4
- import '../styles/version-modal.css';
5
-
6
- /**
7
- * 获取版本信息的请求函数
8
- * @returns {Promise<Object>} 版本信息
9
- */
10
- const getVersionInfo = async () => {
11
- const response = await fetch('/cgi-bin/version', {
12
- headers: {
13
- 'Content-Type': 'application/json'
14
- }
15
- });
16
-
17
- const data = await response.json();
18
-
19
- if (!response.ok) {
20
- throw new Error(data.message || '请求失败');
21
- }
22
-
23
- return data;
24
- };
25
-
26
- /**
27
- * 版本信息模态框组件
28
- * @param {Object} props - 组件属性
29
- * @param {boolean} props.visible - 是否显示模态框
30
- * @param {Function} props.onCancel - 取消回调函数
31
- */
32
- const VersionModal = ({ visible, onCancel }) => {
33
- const [versionContent, setVersionContent] = useState('');
34
- const [loading, setLoading] = useState(false);
35
- const [error, setError] = useState(null);
36
-
37
- useEffect(() => {
38
- if (visible) {
39
- fetchVersionInfo();
40
- }
41
- }, [visible]);
42
-
43
- const fetchVersionInfo = async () => {
44
- setLoading(true);
45
- setError(null);
46
-
47
- try {
48
- const response = await getVersionInfo();
49
- if (response.code === 0) {
50
- setVersionContent(response.data.content);
51
- } else {
52
- setError(response.message);
53
- }
54
- } catch (err) {
55
- setError(`获取版本信息失败: ${err.message}`);
56
- } finally {
57
- setLoading(false);
58
- }
59
- };
60
-
61
- return (
62
- <Modal
63
- title="版本更新记录"
64
- open={visible}
65
- onCancel={onCancel}
66
- footer={null}
67
- width={800}
68
- className="version-modal"
69
- >
70
- <div className="version-modal-content">
71
- {loading && (
72
- <div className="version-modal-loading">
73
- <Spin size="large" />
74
- <p>正在加载版本信息...</p>
75
- </div>
76
- )}
77
-
78
- {error && (
79
- <Alert
80
- message="加载失败"
81
- description={error}
82
- type="error"
83
- showIcon
84
- />
85
- )}
86
-
87
- {!loading && !error && versionContent && (
88
- <div className="version-markdown-content">
89
- <ReactMarkdown>
90
- {versionContent.replace(/^#\s*🔄?\s*版本更新记录\s*\n+/, '')}
91
- </ReactMarkdown>
92
- </div>
93
- )}
94
- </div>
95
- </Modal>
96
- );
97
- };
98
-
99
- export default VersionModal;
@@ -1,115 +0,0 @@
1
- import React, { useEffect, useState } from 'react';
2
- import { Drawer, Form, Input, message, Button, Space } from 'antd';
3
- import axios from 'axios';
4
- import { ResponseContentEditor, PreviewModal, formatResponseContent, refreshCacheAfterUpdate } from '.';
5
-
6
- const DataManagementDrawer = ({ visible, onCancel, interfaceItem, onSaved }) => {
7
- const [form] = Form.useForm();
8
- const [previewVisible, setPreviewVisible] = useState(false);
9
- const [previewContent, setPreviewContent] = useState(null);
10
-
11
- useEffect(() => {
12
- if (visible && interfaceItem) {
13
- let responses = Array.isArray(interfaceItem.responses) ? interfaceItem.responses : [];
14
- let activeResponseId = interfaceItem.activeResponseId || (responses[0] && responses[0].id) || '';
15
- form.setFieldsValue({ responses, activeResponseId });
16
- }
17
- }, [visible, interfaceItem]);
18
-
19
- const handlePreview = () => {
20
- const values = form.getFieldsValue(true);
21
- const responses = Array.isArray(values.responses) ? values.responses : [];
22
- const activeResponse = responses.find(r => r.id === values.activeResponseId) || responses[0];
23
- if (!activeResponse) {
24
- message.error('未找到有效的响应内容');
25
- return;
26
- }
27
- const contentType = 'application/json; charset=utf-8';
28
- const formatted = formatResponseContent(activeResponse.content || '', contentType);
29
- setPreviewContent({ title: `预览: ${activeResponse.name || '未命名响应'}`, content: formatted, contentType, description: '' });
30
- setPreviewVisible(true);
31
- };
32
-
33
- const handleOk = async () => {
34
- try {
35
- await form.validateFields();
36
- const values = form.getFieldsValue(true);
37
- const payload = {
38
- responses: Array.isArray(values.responses) ? values.responses : [],
39
- activeResponseId: values.activeResponseId || (values.responses && values.responses[0] && values.responses[0].id) || ''
40
- };
41
- if (!interfaceItem || !interfaceItem.id) {
42
- message.error('缺少接口ID,无法保存');
43
- return;
44
- }
45
- const res = await axios.patch(`/cgi-bin/interfaces/${interfaceItem.id}`, payload);
46
- if (res.data && res.data.code === 0) {
47
- message.success('保存成功');
48
- await refreshCacheAfterUpdate();
49
- if (onSaved) onSaved();
50
- if (onCancel) onCancel();
51
- } else {
52
- throw new Error(res.data?.message || '保存失败');
53
- }
54
- } catch (err) {
55
- if (err && err.errorFields) return; // 表单校验错误
56
- console.error('保存失败', err);
57
- message.error(err.response?.data?.message || err.message || '保存失败');
58
- }
59
- };
60
-
61
- return (
62
- <>
63
- <Drawer
64
- title="数据管理"
65
- placement="right"
66
- width={1040}
67
- open={visible}
68
- onClose={onCancel}
69
- destroyOnClose
70
- bodyStyle={{ paddingBottom: 80 }}
71
- footer={
72
- <div style={{ textAlign: 'right' }}>
73
- <Space>
74
- <Button onClick={onCancel}>取消</Button>
75
- <Button type="primary" onClick={handleOk}>保存</Button>
76
- </Space>
77
- </div>
78
- }
79
- >
80
- <Form form={form} layout="vertical" initialValues={{ responses: [], activeResponseId: '' }}>
81
- <Form.Item name="responses" hidden>
82
- <Input />
83
- </Form.Item>
84
- <Form.Item name="activeResponseId" hidden>
85
- <Input />
86
- </Form.Item>
87
- <Form.Item noStyle shouldUpdate>
88
- {({ getFieldValue }) => {
89
- const responses = getFieldValue('responses') || [];
90
- const activeResponseId = getFieldValue('activeResponseId') || (responses[0] && responses[0].id) || '';
91
- return (
92
- <ResponseContentEditor
93
- form={form}
94
- responses={responses}
95
- activeResponseId={activeResponseId}
96
- onPreview={handlePreview}
97
- enableSorting={true}
98
- />
99
- );
100
- }}
101
- </Form.Item>
102
- </Form>
103
- </Drawer>
104
-
105
- <PreviewModal
106
- visible={previewVisible}
107
- onCancel={() => setPreviewVisible(false)}
108
- previewContent={previewContent}
109
- />
110
- </>
111
- );
112
- };
113
-
114
- export default DataManagementDrawer;
115
-
@@ -1,110 +0,0 @@
1
- import React, { useEffect, useState } from 'react';
2
- import { Modal, Form, Input, message } from 'antd';
3
- import axios from 'axios';
4
- import { ResponseContentEditor, PreviewModal, formatResponseContent, refreshCacheAfterUpdate } from '.';
5
-
6
- const DataManagementModal = ({ visible, onCancel, interfaceItem, onSaved }) => {
7
- const [form] = Form.useForm();
8
- const [previewVisible, setPreviewVisible] = useState(false);
9
- const [previewContent, setPreviewContent] = useState(null);
10
-
11
- useEffect(() => {
12
- if (visible && interfaceItem) {
13
- let responses = Array.isArray(interfaceItem.responses) ? interfaceItem.responses : [];
14
- let activeResponseId = interfaceItem.activeResponseId || (responses[0] && responses[0].id) || '';
15
- form.setFieldsValue({ responses, activeResponseId });
16
- }
17
- }, [visible, interfaceItem]);
18
-
19
- const handlePreview = () => {
20
- const values = form.getFieldsValue(true);
21
- const responses = Array.isArray(values.responses) ? values.responses : [];
22
- const activeResponse = responses.find(r => r.id === values.activeResponseId) || responses[0];
23
- if (!activeResponse) {
24
- message.error('未找到有效的响应内容');
25
- return;
26
- }
27
- const contentType = 'application/json; charset=utf-8';
28
- const formatted = formatResponseContent(activeResponse.content || '', contentType);
29
- setPreviewContent({ title: `预览: ${activeResponse.name || '未命名响应'}`, content: formatted, contentType, description: '' });
30
- setPreviewVisible(true);
31
- };
32
-
33
- const handleOk = async () => {
34
- try {
35
- await form.validateFields();
36
- const values = form.getFieldsValue(true);
37
- const payload = {
38
- responses: Array.isArray(values.responses) ? values.responses : [],
39
- activeResponseId: values.activeResponseId || (values.responses && values.responses[0] && values.responses[0].id) || ''
40
- };
41
- if (!interfaceItem || !interfaceItem.id) {
42
- message.error('缺少接口ID,无法保存');
43
- return;
44
- }
45
- const res = await axios.patch(`/cgi-bin/interfaces/${interfaceItem.id}`, payload);
46
- if (res.data && res.data.code === 0) {
47
- message.success('保存成功');
48
- await refreshCacheAfterUpdate();
49
- if (onSaved) onSaved();
50
- if (onCancel) onCancel();
51
- } else {
52
- throw new Error(res.data?.message || '保存失败');
53
- }
54
- } catch (err) {
55
- if (err && err.errorFields) return; // 表单校验错误
56
- console.error('保存失败', err);
57
- message.error(err.response?.data?.message || err.message || '保存失败');
58
- }
59
- };
60
-
61
- return (
62
- <>
63
- <Modal
64
- title="数据管理"
65
- open={visible}
66
- onOk={handleOk}
67
- onCancel={onCancel}
68
- width={1040}
69
- destroyOnClose
70
- okText="保存"
71
- cancelText="取消"
72
- bodyStyle={{ maxHeight: '80vh', overflow: 'auto', padding: '24px' }}
73
- >
74
- <Form form={form} layout="vertical" initialValues={{ responses: [], activeResponseId: '' }}>
75
- <Form.Item name="responses" hidden>
76
- <Input />
77
- </Form.Item>
78
- <Form.Item name="activeResponseId" hidden>
79
- <Input />
80
- </Form.Item>
81
- <Form.Item noStyle shouldUpdate>
82
- {({ getFieldValue }) => {
83
- const responses = getFieldValue('responses') || [];
84
- const activeResponseId = getFieldValue('activeResponseId') || (responses[0] && responses[0].id) || '';
85
- return (
86
- <ResponseContentEditor
87
- form={form}
88
- responses={responses}
89
- activeResponseId={activeResponseId}
90
- onPreview={handlePreview}
91
- enableSorting={true}
92
- />
93
- );
94
- }}
95
- </Form.Item>
96
- </Form>
97
- </Modal>
98
-
99
- <PreviewModal
100
- visible={previewVisible}
101
- onCancel={() => setPreviewVisible(false)}
102
- previewContent={previewContent}
103
- />
104
- </>
105
- );
106
- };
107
-
108
- export default DataManagementModal;
109
-
110
-