@jachy/multiport-proxy 0.0.1 → 0.0.3

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/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  一个功能强大的多端口代理服务,支持动态配置和实时日志查看。
4
4
 
5
+ ![multiport-proxy-screenshot](./ScreenShot.png)
6
+
5
7
  ## Features
6
8
 
7
9
  - 🚀 **Web 配置界面** - 启动时自动打开 Web UI,轻松配置代理规则
package/dist/index.js CHANGED
@@ -54,8 +54,11 @@ async function main() {
54
54
  // 创建 Web 服务器
55
55
  const app = (0, express_1.default)();
56
56
  app.use(express_1.default.json());
57
- // 静态文件服务
58
- const uiDir = path_1.default.join(__dirname, 'web', 'ui');
57
+ // 静态文件服务 - 支持开发和构建环境
58
+ const isDev = process.env.NODE_ENV !== 'production';
59
+ const uiDir = isDev
60
+ ? path_1.default.join(__dirname, 'web', 'ui')
61
+ : path_1.default.join(__dirname, '..', 'web', 'ui');
59
62
  app.use(express_1.default.static(uiDir));
60
63
  // API 路由
61
64
  app.use('/api', (0, api_routes_1.createApiRouter)(configManager, logger, proxyServer));
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,sDAA8B;AAC9B,gDAAwB;AACxB,4DAAwD;AACxD,4CAAyC;AACzC,wDAAoD;AACpD,iDAAmD;AAEnD,MAAM,QAAQ,GAAG,IAAI,CAAC;AAEtB,KAAK,UAAU,IAAI;IACjB,OAAO,CAAC,GAAG,CAAC,kCAAkC,CAAC,CAAC;IAEhD,UAAU;IACV,MAAM,aAAa,GAAG,IAAI,8BAAa,EAAE,CAAC;IAC1C,MAAM,MAAM,GAAG,IAAI,eAAM,EAAE,CAAC;IAC5B,MAAM,WAAW,GAAG,IAAI,0BAAW,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;IAE3D,SAAS;IACT,WAAW,CAAC,YAAY,EAAE,CAAC;IAE3B,aAAa;IACb,MAAM,GAAG,GAAG,IAAA,iBAAO,GAAE,CAAC;IAEtB,GAAG,CAAC,GAAG,CAAC,iBAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IAExB,SAAS;IACT,MAAM,KAAK,GAAG,cAAI,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;IAChD,GAAG,CAAC,GAAG,CAAC,iBAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IAE/B,SAAS;IACT,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,IAAA,4BAAe,EAAC,aAAa,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC;IAErE,aAAa;IACb,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QACxB,GAAG,CAAC,QAAQ,CAAC,cAAI,CAAC,IAAI,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;IAEH,aAAa;IACb,GAAG,CAAC,MAAM,CAAC,QAAQ,EAAE,KAAK,IAAI,EAAE;QAC9B,OAAO,CAAC,GAAG,CAAC,wCAAwC,QAAQ,EAAE,CAAC,CAAC;QAChE,OAAO,CAAC,GAAG,CAAC,oBAAoB,WAAW,CAAC,eAAe,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,MAAM,IAAI,CAAC,CAAC;QAExF,UAAU;QACV,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,CAAC,wDAAa,MAAM,GAAC,CAAC,CAAC,OAAO,CAAC;YAC5C,MAAM,IAAI,CAAC,oBAAoB,QAAQ,EAAE,CAAC,CAAC;YAC3C,OAAO,CAAC,GAAG,CAAC,kCAAkC,CAAC,CAAC;QAClD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,GAAG,CAAC,gCAAgC,QAAQ,oBAAoB,CAAC,CAAC;QAC5E,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,OAAO;IACP,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;QACxB,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC;QACvC,WAAW,CAAC,cAAc,EAAE,CAAC;QAC7B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,sDAA8B;AAC9B,gDAAwB;AACxB,4DAAwD;AACxD,4CAAyC;AACzC,wDAAoD;AACpD,iDAAmD;AAEnD,MAAM,QAAQ,GAAG,IAAI,CAAC;AAEtB,KAAK,UAAU,IAAI;IACjB,OAAO,CAAC,GAAG,CAAC,kCAAkC,CAAC,CAAC;IAEhD,UAAU;IACV,MAAM,aAAa,GAAG,IAAI,8BAAa,EAAE,CAAC;IAC1C,MAAM,MAAM,GAAG,IAAI,eAAM,EAAE,CAAC;IAC5B,MAAM,WAAW,GAAG,IAAI,0BAAW,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;IAE3D,SAAS;IACT,WAAW,CAAC,YAAY,EAAE,CAAC;IAE3B,aAAa;IACb,MAAM,GAAG,GAAG,IAAA,iBAAO,GAAE,CAAC;IAEtB,GAAG,CAAC,GAAG,CAAC,iBAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IAExB,qBAAqB;IACrB,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY,CAAC;IACpD,MAAM,KAAK,GAAG,KAAK;QACjB,CAAC,CAAC,cAAI,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,EAAE,IAAI,CAAC;QACnC,CAAC,CAAC,cAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;IAC5C,GAAG,CAAC,GAAG,CAAC,iBAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IAE/B,SAAS;IACT,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,IAAA,4BAAe,EAAC,aAAa,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC;IAErE,aAAa;IACb,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QACxB,GAAG,CAAC,QAAQ,CAAC,cAAI,CAAC,IAAI,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;IAEH,aAAa;IACb,GAAG,CAAC,MAAM,CAAC,QAAQ,EAAE,KAAK,IAAI,EAAE;QAC9B,OAAO,CAAC,GAAG,CAAC,wCAAwC,QAAQ,EAAE,CAAC,CAAC;QAChE,OAAO,CAAC,GAAG,CAAC,oBAAoB,WAAW,CAAC,eAAe,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,MAAM,IAAI,CAAC,CAAC;QAExF,UAAU;QACV,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,CAAC,wDAAa,MAAM,GAAC,CAAC,CAAC,OAAO,CAAC;YAC5C,MAAM,IAAI,CAAC,oBAAoB,QAAQ,EAAE,CAAC,CAAC;YAC3C,OAAO,CAAC,GAAG,CAAC,kCAAkC,CAAC,CAAC;QAClD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,GAAG,CAAC,gCAAgC,QAAQ,oBAAoB,CAAC,CAAC;QAC5E,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,OAAO;IACP,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;QACxB,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC;QACvC,WAAW,CAAC,cAAc,EAAE,CAAC;QAC7B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"api-routes.d.ts","sourceRoot":"","sources":["../../src/web/api-routes.ts"],"names":[],"mappings":"AAAA,OAAgB,EAAE,MAAM,EAAqB,MAAM,SAAS,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAErD,wBAAgB,eAAe,CAC7B,aAAa,EAAE,aAAa,EAC5B,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,WAAW,GACvB,MAAM,CAkHR"}
1
+ {"version":3,"file":"api-routes.d.ts","sourceRoot":"","sources":["../../src/web/api-routes.ts"],"names":[],"mappings":"AAAA,OAAgB,EAAE,MAAM,EAAqB,MAAM,SAAS,CAAC;AAG7D,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAkDrD,wBAAgB,eAAe,CAC7B,aAAa,EAAE,aAAa,EAC5B,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,WAAW,GACvB,MAAM,CA0IR"}
@@ -5,6 +5,51 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.createApiRouter = createApiRouter;
7
7
  const express_1 = __importDefault(require("express"));
8
+ const child_process_1 = require("child_process");
9
+ // 检查端口是否被占用
10
+ function checkPortAvailable(port) {
11
+ const isWindows = process.platform === 'win32';
12
+ try {
13
+ let command;
14
+ let output;
15
+ if (isWindows) {
16
+ // Windows 使用 netstat
17
+ command = `netstat -ano | findstr :${port}`;
18
+ output = (0, child_process_1.execSync)(command, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).toString();
19
+ }
20
+ else {
21
+ // macOS 和 Linux 使用 lsof
22
+ command = `lsof -i :${port}`;
23
+ output = (0, child_process_1.execSync)(command, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).toString();
24
+ }
25
+ if (output.trim()) {
26
+ const lines = output.trim().split('\n');
27
+ const details = lines.slice(1).map(line => line.trim()).join('\n');
28
+ return {
29
+ available: false,
30
+ details: `端口 ${port} 已被占用\n\n程序信息:\n${details}`,
31
+ };
32
+ }
33
+ return {
34
+ available: true,
35
+ details: `端口 ${port} 可用`,
36
+ };
37
+ }
38
+ catch (error) {
39
+ // 命令执行失败可能表示端口未被占用
40
+ if (error.status === 1 || error.code === 1) {
41
+ return {
42
+ available: true,
43
+ details: `端口 ${port} 可用`,
44
+ };
45
+ }
46
+ // 其他错误
47
+ return {
48
+ available: false,
49
+ details: `端口 ${port} 检查失败: ${error.message}`,
50
+ };
51
+ }
52
+ }
8
53
  function createApiRouter(configManager, logger, proxyServer) {
9
54
  const router = express_1.default.Router();
10
55
  // 获取所有配置
@@ -27,13 +72,21 @@ function createApiRouter(configManager, logger, proxyServer) {
27
72
  }
28
73
  });
29
74
  // 添加规则
30
- router.post('/config/rules', (req, res) => {
75
+ router.post('/config/rules', async (req, res) => {
31
76
  try {
32
77
  const rule = req.body;
33
78
  if (!rule.id) {
34
79
  rule.id = Math.random().toString(36).slice(2);
35
80
  }
36
81
  rule.enabled = rule.enabled !== false;
82
+ // 检查端口是否被占用
83
+ const portCheck = checkPortAvailable(rule.localPort);
84
+ if (!portCheck.available) {
85
+ return res.status(400).json({
86
+ error: 'Port unavailable',
87
+ details: portCheck.details,
88
+ });
89
+ }
37
90
  configManager.addRule(rule);
38
91
  proxyServer.updateProxies();
39
92
  res.json({ success: true, rule });
@@ -43,10 +96,23 @@ function createApiRouter(configManager, logger, proxyServer) {
43
96
  }
44
97
  });
45
98
  // 更新规则
46
- router.put('/config/rules/:id', (req, res) => {
99
+ router.put('/config/rules/:id', async (req, res) => {
47
100
  try {
48
101
  const { id } = req.params;
49
102
  const updates = req.body;
103
+ // 获取旧规则
104
+ const oldRule = configManager.getConfig().rules.find(r => r.id === id);
105
+ const portChanged = oldRule && oldRule.localPort !== updates.localPort;
106
+ // 如果端口改变了,需要检查新端口是否可用
107
+ if (portChanged) {
108
+ const portCheck = checkPortAvailable(updates.localPort);
109
+ if (!portCheck.available) {
110
+ return res.status(400).json({
111
+ error: 'Port unavailable',
112
+ details: portCheck.details,
113
+ });
114
+ }
115
+ }
50
116
  configManager.updateRule(id, updates);
51
117
  proxyServer.updateProxies();
52
118
  res.json({ success: true, message: 'Rule updated' });
@@ -1 +1 @@
1
- {"version":3,"file":"api-routes.js","sourceRoot":"","sources":["../../src/web/api-routes.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"api-routes.js","sourceRoot":"","sources":["../../src/web/api-routes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,252 @@
1
+ let currentEditingId = null;
2
+
3
+ // 启用 CORS 复选框的事件监听
4
+ document.addEventListener('DOMContentLoaded', () => {
5
+ const corsCheckbox = document.getElementById('ruleCorsEnabled');
6
+ const corsOrigins = document.getElementById('corsOrigins');
7
+
8
+ corsCheckbox.addEventListener('change', (e) => {
9
+ corsOrigins.style.display = e.target.checked ? 'block' : 'none';
10
+ });
11
+
12
+ // 初始化
13
+ loadRules();
14
+ loadLogs();
15
+
16
+ // 定时刷新日志
17
+ setInterval(loadLogs, 2000);
18
+
19
+ // 日志筛选
20
+ document.getElementById('logFilter').addEventListener('input', filterLogs);
21
+ });
22
+
23
+ // 加载规则列表
24
+ async function loadRules() {
25
+ try {
26
+ const response = await fetch('/api/config');
27
+ const data = await response.json();
28
+ const rules = data.rules || [];
29
+
30
+ const rulesList = document.getElementById('rulesList');
31
+
32
+ if (rules.length === 0) {
33
+ rulesList.innerHTML = '<div class="loading">暂无规则,点击"新增规则"开始</div>';
34
+ return;
35
+ }
36
+
37
+ rulesList.innerHTML = rules.map(rule => `
38
+ <div class="rule-item" onclick="editRule('${rule.id}')">
39
+ <div class="rule-item-header">
40
+ <span class="rule-port">
41
+ :${rule.localPort}
42
+ <span class="rule-status ${rule.enabled ? 'enabled' : 'disabled'}"></span>
43
+ </span>
44
+ </div>
45
+ <div class="rule-target">${rule.targetUrl}</div>
46
+ </div>
47
+ `).join('');
48
+ } catch (error) {
49
+ console.error('Failed to load rules:', error);
50
+ }
51
+ }
52
+
53
+ // 打开新增规则表单
54
+ function showAddRuleForm() {
55
+ currentEditingId = null;
56
+ document.getElementById('modalTitle').textContent = '新增代理规则';
57
+ document.getElementById('deleteBtn').style.display = 'none';
58
+ document.getElementById('ruleForm').reset();
59
+ document.getElementById('ruleTimeout').value = '30000';
60
+ document.getElementById('ruleRetries').value = '0';
61
+ document.getElementById('ruleEnabled').checked = true;
62
+ document.getElementById('ruleCorsEnabled').checked = true;
63
+ const corsOrigins = document.getElementById('corsOrigins');
64
+ corsOrigins.style.display = 'block';
65
+ document.getElementById('ruleModal').classList.add('show');
66
+ }
67
+
68
+ // 编辑规则
69
+ async function editRule(id) {
70
+ try {
71
+ const response = await fetch('/api/config');
72
+ const data = await response.json();
73
+ const rule = data.rules.find(r => r.id === id);
74
+
75
+ if (!rule) return;
76
+
77
+ currentEditingId = id;
78
+ document.getElementById('modalTitle').textContent = '编辑代理规则';
79
+ document.getElementById('deleteBtn').style.display = 'inline-block';
80
+ document.getElementById('rulePort').value = rule.localPort;
81
+ document.getElementById('ruleTarget').value = rule.targetUrl;
82
+ document.getElementById('ruleTimeout').value = rule.timeout || 30000;
83
+ document.getElementById('ruleRetries').value = rule.retries || 0;
84
+ document.getElementById('ruleEnabled').checked = rule.enabled !== false;
85
+ document.getElementById('ruleCorsEnabled').checked = rule.cors?.enabled || false;
86
+ document.getElementById('ruleCorsOrigins').value = rule.cors?.origins?.join(', ') || '*';
87
+ document.getElementById('corsOrigins').style.display = rule.cors?.enabled ? 'block' : 'none';
88
+
89
+ document.getElementById('ruleModal').classList.add('show');
90
+ } catch (error) {
91
+ console.error('Failed to edit rule:', error);
92
+ }
93
+ }
94
+
95
+ // 保存规则
96
+ async function saveRule(e) {
97
+ e.preventDefault();
98
+
99
+ const rule = {
100
+ localPort: parseInt(document.getElementById('rulePort').value),
101
+ targetUrl: document.getElementById('ruleTarget').value,
102
+ timeout: parseInt(document.getElementById('ruleTimeout').value),
103
+ retries: parseInt(document.getElementById('ruleRetries').value),
104
+ enabled: document.getElementById('ruleEnabled').checked,
105
+ cors: {
106
+ enabled: document.getElementById('ruleCorsEnabled').checked,
107
+ origins: document.getElementById('ruleCorsOrigins').value
108
+ .split(',')
109
+ .map(o => o.trim())
110
+ .filter(o => o),
111
+ },
112
+ };
113
+
114
+ try {
115
+ if (currentEditingId) {
116
+ // 更新规则
117
+ const response = await fetch(`/api/config/rules/${currentEditingId}`, {
118
+ method: 'PUT',
119
+ headers: { 'Content-Type': 'application/json' },
120
+ body: JSON.stringify(rule),
121
+ });
122
+
123
+ if (!response.ok) {
124
+ const errorData = await response.json();
125
+ throw new Error(errorData.details || errorData.error || 'Failed to update rule');
126
+ }
127
+ } else {
128
+ // 新增规则
129
+ const response = await fetch('/api/config/rules', {
130
+ method: 'POST',
131
+ headers: { 'Content-Type': 'application/json' },
132
+ body: JSON.stringify(rule),
133
+ });
134
+
135
+ if (!response.ok) {
136
+ const errorData = await response.json();
137
+ throw new Error(errorData.details || errorData.error || 'Failed to add rule');
138
+ }
139
+ }
140
+
141
+ closeRuleForm();
142
+ loadRules();
143
+ alert('规则已保存!');
144
+ } catch (error) {
145
+ console.error('Error saving rule:', error);
146
+ alert('保存失败:' + error.message);
147
+ }
148
+ }
149
+
150
+ // 删除规则
151
+ async function deleteRule() {
152
+ if (!currentEditingId) return;
153
+
154
+ if (!confirm('确定删除此规则吗?')) return;
155
+
156
+ try {
157
+ const response = await fetch(`/api/config/rules/${currentEditingId}`, {
158
+ method: 'DELETE',
159
+ });
160
+
161
+ if (!response.ok) {
162
+ throw new Error('Failed to delete rule');
163
+ }
164
+
165
+ closeRuleForm();
166
+ loadRules();
167
+ alert('规则已删除!');
168
+ } catch (error) {
169
+ console.error('Error deleting rule:', error);
170
+ alert('删除失败:' + error.message);
171
+ }
172
+ }
173
+
174
+ // 关闭表单
175
+ function closeRuleForm() {
176
+ document.getElementById('ruleModal').classList.remove('show');
177
+ currentEditingId = null;
178
+ }
179
+
180
+ // 加载日志
181
+ async function loadLogs() {
182
+ try {
183
+ const response = await fetch('/api/logs');
184
+ const data = await response.json();
185
+ const logs = data.logs || [];
186
+
187
+ // 更新统计信息
188
+ if (data.stats) {
189
+ document.getElementById('totalLogs').textContent = data.stats.totalLogs;
190
+ document.getElementById('errorCount').textContent = data.stats.errorCount;
191
+ document.getElementById('avgDuration').textContent = data.stats.averageDuration + 'ms';
192
+ }
193
+
194
+ // 渲染日志
195
+ const logsContainer = document.getElementById('logsContainer');
196
+ if (logs.length === 0) {
197
+ logsContainer.innerHTML = '<div class="log-entry"><p style="text-align: center; color: #999;">暂无日志</p></div>';
198
+ return;
199
+ }
200
+
201
+ logsContainer.innerHTML = logs.map(log => {
202
+ const time = new Date(log.timestamp).toLocaleTimeString('zh-CN');
203
+ const statusClass = log.error ? 'error' : 'success';
204
+ const statusText = log.statusCode || (log.error ? 'ERROR' : '?');
205
+ const duration = log.duration.toFixed(0);
206
+
207
+ return `
208
+ <div class="log-entry">
209
+ <span class="log-time">${time}</span>
210
+ <span class="log-port">:${log.localPort}</span>
211
+ <span class="log-method ${log.method}">${log.method}</span>
212
+ <span>${log.path}</span>
213
+ <span class="log-status ${statusClass}">${statusText}</span>
214
+ <span class="log-duration">${duration}ms</span>
215
+ ${log.error ? `<span style="color: #ef4444; margin-left: 10px;">⚠️ ${log.error}</span>` : ''}
216
+ </div>
217
+ `;
218
+ }).join('');
219
+ } catch (error) {
220
+ console.error('Failed to load logs:', error);
221
+ }
222
+ }
223
+
224
+ // 筛选日志
225
+ function filterLogs() {
226
+ const filter = document.getElementById('logFilter').value.toLowerCase();
227
+ const entries = document.querySelectorAll('.log-entry');
228
+
229
+ entries.forEach(entry => {
230
+ const text = entry.textContent.toLowerCase();
231
+ entry.style.display = text.includes(filter) ? '' : 'none';
232
+ });
233
+ }
234
+
235
+ // 清空日志
236
+ async function clearLogs() {
237
+ if (!confirm('确定清空所有日志吗?')) return;
238
+
239
+ try {
240
+ const response = await fetch('/api/logs', {
241
+ method: 'DELETE',
242
+ });
243
+
244
+ if (!response.ok) {
245
+ throw new Error('Failed to clear logs');
246
+ }
247
+
248
+ loadLogs();
249
+ } catch (error) {
250
+ console.error('Error clearing logs:', error);
251
+ }
252
+ }
@@ -0,0 +1,124 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Multiport Proxy - 配置管理</title>
7
+ <link rel="stylesheet" href="/style.css">
8
+ </head>
9
+ <body>
10
+ <div class="container">
11
+ <header>
12
+ <h1>🚀 Multiport Proxy</h1>
13
+ <p class="subtitle">多端口代理配置管理</p>
14
+ </header>
15
+
16
+ <main>
17
+ <div class="layout">
18
+ <!-- 左侧:配置面板 -->
19
+ <section class="panel config-panel">
20
+ <div class="panel-header">
21
+ <h2>📋 代理规则</h2>
22
+ <button class="btn btn-primary" onclick="showAddRuleForm()">+ 新增规则</button>
23
+ </div>
24
+
25
+ <div class="rules-list" id="rulesList">
26
+ <div class="loading">加载中...</div>
27
+ </div>
28
+ </section>
29
+
30
+ <!-- 右侧:日志面板 -->
31
+ <section class="panel log-panel">
32
+ <div class="panel-header">
33
+ <h2>📊 实时日志</h2>
34
+ <div class="log-controls">
35
+ <input type="text" id="logFilter" placeholder="筛选日志..." class="filter-input">
36
+ <button class="btn btn-small" onclick="clearLogs()">清空</button>
37
+ </div>
38
+ </div>
39
+
40
+ <div class="stats" id="stats">
41
+ <div class="stat-item">
42
+ <span class="label">总请求数:</span>
43
+ <span class="value" id="totalLogs">0</span>
44
+ </div>
45
+ <div class="stat-item">
46
+ <span class="label">错误数:</span>
47
+ <span class="value" id="errorCount">0</span>
48
+ </div>
49
+ <div class="stat-item">
50
+ <span class="label">平均耗时:</span>
51
+ <span class="value" id="avgDuration">0ms</span>
52
+ </div>
53
+ </div>
54
+
55
+ <div class="logs-container" id="logsContainer">
56
+ <div class="log-entry">
57
+ <p style="text-align: center; color: #999;">暂无日志</p>
58
+ </div>
59
+ </div>
60
+ </section>
61
+ </div>
62
+ </main>
63
+ </div>
64
+
65
+ <!-- 新增/编辑规则 Modal -->
66
+ <div id="ruleModal" class="modal">
67
+ <div class="modal-content">
68
+ <div class="modal-header">
69
+ <h3 id="modalTitle">新增代理规则</h3>
70
+ <button class="close-btn" onclick="closeRuleForm()">&times;</button>
71
+ </div>
72
+
73
+ <form id="ruleForm" onsubmit="saveRule(event)">
74
+ <div class="form-group">
75
+ <label for="rulePort">本地监听端口 *</label>
76
+ <input type="number" id="rulePort" min="1" max="65535" required placeholder="3000">
77
+ </div>
78
+
79
+ <div class="form-group">
80
+ <label for="ruleTarget">目标服务地址 *</label>
81
+ <input type="url" id="ruleTarget" required placeholder="http://localhost:8000">
82
+ </div>
83
+
84
+ <div class="form-group">
85
+ <label for="ruleTimeout">超时时间 (ms)</label>
86
+ <input type="number" id="ruleTimeout" min="1000" value="30000" placeholder="30000">
87
+ </div>
88
+
89
+ <div class="form-group">
90
+ <label for="ruleRetries">重试次数</label>
91
+ <input type="number" id="ruleRetries" min="0" value="0" placeholder="0">
92
+ </div>
93
+
94
+ <div class="form-group">
95
+ <label class="checkbox">
96
+ <input type="checkbox" id="ruleCorsEnabled">
97
+ 启用 CORS
98
+ </label>
99
+ </div>
100
+
101
+ <div class="form-group" id="corsOrigins" style="display: none;">
102
+ <label for="ruleCorsOrigins">CORS 来源 (逗号分隔)</label>
103
+ <input type="text" id="ruleCorsOrigins" value="*" placeholder="* 或 http://localhost:3000, http://localhost:3001">
104
+ </div>
105
+
106
+ <div class="form-group">
107
+ <label class="checkbox">
108
+ <input type="checkbox" id="ruleEnabled" checked>
109
+ 启用此规则
110
+ </label>
111
+ </div>
112
+
113
+ <div class="form-actions">
114
+ <button type="submit" class="btn btn-primary">保存</button>
115
+ <button type="button" class="btn btn-secondary" onclick="closeRuleForm()">取消</button>
116
+ <button type="button" class="btn btn-danger" id="deleteBtn" style="display: none;" onclick="deleteRule()">删除</button>
117
+ </div>
118
+ </form>
119
+ </div>
120
+ </div>
121
+
122
+ <script src="/app.js"></script>
123
+ </body>
124
+ </html>
@@ -0,0 +1,449 @@
1
+ :root {
2
+ --primary-color: #3b82f6;
3
+ --danger-color: #ef4444;
4
+ --success-color: #10b981;
5
+ --warning-color: #f59e0b;
6
+ --secondary-color: #6b7280;
7
+ --bg-primary: #ffffff;
8
+ --bg-secondary: #f9fafb;
9
+ --border-color: #e5e7eb;
10
+ --text-primary: #111827;
11
+ --text-secondary: #6b7280;
12
+ --shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06);
13
+ --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
14
+ }
15
+
16
+ * {
17
+ margin: 0;
18
+ padding: 0;
19
+ box-sizing: border-box;
20
+ }
21
+
22
+ body {
23
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
24
+ background-color: var(--bg-secondary);
25
+ color: var(--text-primary);
26
+ }
27
+
28
+ .container {
29
+ max-width: 1400px;
30
+ margin: 0 auto;
31
+ padding: 15px;
32
+ }
33
+
34
+ header {
35
+ text-align: center;
36
+ margin-bottom: 15px;
37
+ padding: 15px 0;
38
+ border-bottom: 1px solid var(--border-color);
39
+ }
40
+
41
+ header h1 {
42
+ font-size: 1.5rem;
43
+ margin-bottom: 5px;
44
+ color: var(--primary-color);
45
+ }
46
+
47
+ .subtitle {
48
+ font-size: 0.85rem;
49
+ color: var(--text-secondary);
50
+ }
51
+
52
+ .layout {
53
+ display: grid;
54
+ grid-template-columns: 400px 1fr;
55
+ gap: 15px;
56
+ height: calc(100vh - 150px);
57
+ }
58
+
59
+ .panel {
60
+ background: var(--bg-primary);
61
+ border-radius: 8px;
62
+ box-shadow: var(--shadow);
63
+ display: flex;
64
+ flex-direction: column;
65
+ overflow: hidden;
66
+ }
67
+
68
+ .panel-header {
69
+ padding: 12px 15px;
70
+ border-bottom: 1px solid var(--border-color);
71
+ display: flex;
72
+ justify-content: space-between;
73
+ align-items: center;
74
+ flex-wrap: wrap;
75
+ gap: 10px;
76
+ }
77
+
78
+ .panel-header h2 {
79
+ font-size: 1rem;
80
+ margin: 0;
81
+ }
82
+
83
+ .config-panel .rules-list,
84
+ .log-panel .logs-container {
85
+ flex: 1;
86
+ overflow-y: auto;
87
+ padding: 0;
88
+ }
89
+
90
+ /* 规则列表 */
91
+ .rules-list {
92
+ padding: 0;
93
+ }
94
+
95
+ .rule-item {
96
+ padding: 15px 20px;
97
+ border-bottom: 1px solid var(--border-color);
98
+ cursor: pointer;
99
+ transition: background-color 0.2s;
100
+ }
101
+
102
+ .rule-item:hover {
103
+ background-color: var(--bg-secondary);
104
+ }
105
+
106
+ .rule-item-header {
107
+ display: flex;
108
+ justify-content: space-between;
109
+ align-items: center;
110
+ margin-bottom: 8px;
111
+ }
112
+
113
+ .rule-port {
114
+ font-weight: bold;
115
+ color: var(--primary-color);
116
+ font-size: 1.1rem;
117
+ }
118
+
119
+ .rule-status {
120
+ display: inline-block;
121
+ width: 8px;
122
+ height: 8px;
123
+ border-radius: 50%;
124
+ margin-left: 8px;
125
+ }
126
+
127
+ .rule-status.enabled {
128
+ background-color: var(--success-color);
129
+ }
130
+
131
+ .rule-status.disabled {
132
+ background-color: var(--danger-color);
133
+ }
134
+
135
+ .rule-target {
136
+ color: var(--text-secondary);
137
+ font-size: 0.9rem;
138
+ white-space: nowrap;
139
+ overflow: hidden;
140
+ text-overflow: ellipsis;
141
+ }
142
+
143
+ .loading {
144
+ padding: 20px;
145
+ text-align: center;
146
+ color: var(--text-secondary);
147
+ }
148
+
149
+ /* 日志面板 */
150
+ .stats {
151
+ display: grid;
152
+ grid-template-columns: repeat(3, 1fr);
153
+ gap: 10px;
154
+ padding: 15px 20px;
155
+ background-color: var(--bg-secondary);
156
+ border-bottom: 1px solid var(--border-color);
157
+ }
158
+
159
+ .stat-item .label {
160
+ font-size: 0.85rem;
161
+ color: var(--text-secondary);
162
+ }
163
+
164
+ .stat-item .value {
165
+ font-weight: bold;
166
+ color: var(--primary-color);
167
+ }
168
+
169
+ .log-controls {
170
+ display: flex;
171
+ gap: 10px;
172
+ }
173
+
174
+ .filter-input {
175
+ padding: 8px 12px;
176
+ border: 1px solid var(--border-color);
177
+ border-radius: 4px;
178
+ font-size: 0.9rem;
179
+ min-width: 200px;
180
+ }
181
+
182
+ .logs-container {
183
+ padding: 10px 20px;
184
+ background-color: #fafafa;
185
+ color: var(--text-primary);
186
+ }
187
+
188
+ .log-entry {
189
+ padding: 2px 0;
190
+ border-bottom: none;
191
+ font-family: 'Monaco', 'Menlo', 'Courier New', monospace;
192
+ font-size: 0.8rem;
193
+ line-height: 1.4;
194
+ }
195
+
196
+ .log-entry:hover {
197
+ background-color: #f0f0f0;
198
+ }
199
+
200
+ .log-time {
201
+ color: #6b7280;
202
+ margin-right: 10px;
203
+ font-size: 0.75rem;
204
+ }
205
+
206
+ .log-port {
207
+ color: #2563eb;
208
+ font-weight: normal;
209
+ margin-right: 10px;
210
+ }
211
+
212
+ .log-method {
213
+ color: #6b7280;
214
+ margin-right: 10px;
215
+ font-weight: 600;
216
+ min-width: 45px;
217
+ display: inline-block;
218
+ }
219
+
220
+ .log-method.GET {
221
+ color: #059669;
222
+ }
223
+
224
+ .log-method.POST {
225
+ color: #d97706;
226
+ }
227
+
228
+ .log-method.PUT {
229
+ color: #7c3aed;
230
+ }
231
+
232
+ .log-method.DELETE {
233
+ color: #dc2626;
234
+ }
235
+
236
+ .log-status {
237
+ font-weight: normal;
238
+ margin-right: 10px;
239
+ min-width: 35px;
240
+ display: inline-block;
241
+ }
242
+
243
+ .log-status.success {
244
+ color: #059669;
245
+ }
246
+
247
+ .log-status.error {
248
+ color: #dc2626;
249
+ }
250
+
251
+ .log-duration {
252
+ color: #6b7280;
253
+ margin-left: 10px;
254
+ font-size: 0.75rem;
255
+ }
256
+
257
+ /* 按钮 */
258
+ .btn {
259
+ padding: 10px 16px;
260
+ border: none;
261
+ border-radius: 4px;
262
+ font-size: 0.95rem;
263
+ font-weight: 500;
264
+ cursor: pointer;
265
+ transition: all 0.2s;
266
+ }
267
+
268
+ .btn-primary {
269
+ background-color: var(--primary-color);
270
+ color: white;
271
+ }
272
+
273
+ .btn-primary:hover {
274
+ background-color: #2563eb;
275
+ }
276
+
277
+ .btn-secondary {
278
+ background-color: var(--secondary-color);
279
+ color: white;
280
+ }
281
+
282
+ .btn-secondary:hover {
283
+ background-color: #4b5563;
284
+ }
285
+
286
+ .btn-danger {
287
+ background-color: var(--danger-color);
288
+ color: white;
289
+ }
290
+
291
+ .btn-danger:hover {
292
+ background-color: #dc2626;
293
+ }
294
+
295
+ .btn-small {
296
+ padding: 6px 12px;
297
+ font-size: 0.85rem;
298
+ }
299
+
300
+ /* Modal */
301
+ .modal {
302
+ display: none;
303
+ position: fixed;
304
+ top: 0;
305
+ left: 0;
306
+ width: 100%;
307
+ height: 100%;
308
+ background-color: rgba(0, 0, 0, 0.5);
309
+ z-index: 1000;
310
+ justify-content: center;
311
+ align-items: center;
312
+ }
313
+
314
+ .modal.show {
315
+ display: flex;
316
+ }
317
+
318
+ .modal-content {
319
+ background: white;
320
+ border-radius: 8px;
321
+ box-shadow: var(--shadow-lg);
322
+ max-width: 500px;
323
+ width: 90%;
324
+ max-height: 90vh;
325
+ overflow-y: auto;
326
+ }
327
+
328
+ .modal-header {
329
+ padding: 20px;
330
+ border-bottom: 1px solid var(--border-color);
331
+ display: flex;
332
+ justify-content: space-between;
333
+ align-items: center;
334
+ }
335
+
336
+ .modal-header h3 {
337
+ margin: 0;
338
+ }
339
+
340
+ .close-btn {
341
+ background: none;
342
+ border: none;
343
+ font-size: 1.5rem;
344
+ cursor: pointer;
345
+ color: var(--text-secondary);
346
+ }
347
+
348
+ .close-btn:hover {
349
+ color: var(--text-primary);
350
+ }
351
+
352
+ form {
353
+ padding: 20px;
354
+ }
355
+
356
+ .form-group {
357
+ margin-bottom: 16px;
358
+ }
359
+
360
+ .form-group label {
361
+ display: block;
362
+ margin-bottom: 6px;
363
+ font-weight: 500;
364
+ font-size: 0.95rem;
365
+ }
366
+
367
+ .form-group input[type="text"],
368
+ .form-group input[type="url"],
369
+ .form-group input[type="number"] {
370
+ width: 100%;
371
+ padding: 10px 12px;
372
+ border: 1px solid var(--border-color);
373
+ border-radius: 4px;
374
+ font-size: 0.95rem;
375
+ font-family: inherit;
376
+ }
377
+
378
+ .form-group input:focus {
379
+ outline: none;
380
+ border-color: var(--primary-color);
381
+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
382
+ }
383
+
384
+ .checkbox {
385
+ display: flex;
386
+ align-items: center;
387
+ cursor: pointer;
388
+ font-weight: 400;
389
+ }
390
+
391
+ .checkbox input[type="checkbox"] {
392
+ margin-right: 8px;
393
+ width: 16px;
394
+ height: 16px;
395
+ cursor: pointer;
396
+ }
397
+
398
+ .form-actions {
399
+ display: flex;
400
+ gap: 10px;
401
+ margin-top: 20px;
402
+ padding-top: 20px;
403
+ border-top: 1px solid var(--border-color);
404
+ }
405
+
406
+ .form-actions button {
407
+ flex: 1;
408
+ }
409
+
410
+ /* 响应式 */
411
+ @media (max-width: 1024px) {
412
+ .layout {
413
+ grid-template-columns: 1fr;
414
+ height: auto;
415
+ }
416
+
417
+ .panel {
418
+ min-height: 400px;
419
+ }
420
+
421
+ .stats {
422
+ grid-template-columns: 1fr;
423
+ }
424
+ }
425
+
426
+ @media (max-width: 768px) {
427
+ .container {
428
+ padding: 10px;
429
+ }
430
+
431
+ header h1 {
432
+ font-size: 1.8rem;
433
+ }
434
+
435
+ .panel-header {
436
+ flex-direction: column;
437
+ align-items: flex-start;
438
+ }
439
+
440
+ .log-controls {
441
+ width: 100%;
442
+ flex-direction: column;
443
+ }
444
+
445
+ .filter-input {
446
+ width: 100%;
447
+ min-width: auto;
448
+ }
449
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jachy/multiport-proxy",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "A multi-port proxy server with web UI configuration",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -13,7 +13,7 @@
13
13
  ],
14
14
  "scripts": {
15
15
  "dev": "ts-node src/cli.ts",
16
- "build": "tsc && chmod +x dist/cli.js",
16
+ "build": "tsc && cp -r src/web/ui dist/web/ && chmod +x dist/cli.js",
17
17
  "start": "node dist/cli.js",
18
18
  "prepublishOnly": "pnpm run build",
19
19
  "test": "echo \"Error: no test specified\" && exit 1"