@chenpu17/tcpmapping 2.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.
@@ -0,0 +1,668 @@
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>TCP转发服务器 - 管理面板</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
16
+ background: #f5f5f5;
17
+ min-height: 100vh;
18
+ }
19
+
20
+ .header {
21
+ background: white;
22
+ padding: 1rem 2rem;
23
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
24
+ display: flex;
25
+ justify-content: space-between;
26
+ align-items: center;
27
+ }
28
+
29
+ .header h1 {
30
+ color: #333;
31
+ font-size: 24px;
32
+ }
33
+
34
+ .header-actions {
35
+ display: flex;
36
+ gap: 1rem;
37
+ align-items: center;
38
+ }
39
+
40
+ .btn {
41
+ padding: 0.5rem 1rem;
42
+ border: none;
43
+ border-radius: 5px;
44
+ cursor: pointer;
45
+ font-size: 14px;
46
+ transition: all 0.2s;
47
+ }
48
+
49
+ .btn-primary {
50
+ background: #007bff;
51
+ color: white;
52
+ }
53
+
54
+ .btn-primary:hover {
55
+ background: #0056b3;
56
+ }
57
+
58
+ .btn-danger {
59
+ background: #dc3545;
60
+ color: white;
61
+ }
62
+
63
+ .btn-danger:hover {
64
+ background: #c82333;
65
+ }
66
+
67
+ .btn-success {
68
+ background: #28a745;
69
+ color: white;
70
+ }
71
+
72
+ .btn-success:hover {
73
+ background: #218838;
74
+ }
75
+
76
+ .container {
77
+ max-width: 1200px;
78
+ margin: 0 auto;
79
+ padding: 2rem;
80
+ }
81
+
82
+ .card {
83
+ background: white;
84
+ border-radius: 8px;
85
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
86
+ margin-bottom: 2rem;
87
+ }
88
+
89
+ .card-header {
90
+ padding: 1.5rem;
91
+ border-bottom: 1px solid #eee;
92
+ display: flex;
93
+ justify-content: space-between;
94
+ align-items: center;
95
+ }
96
+
97
+ .card-header h2 {
98
+ color: #333;
99
+ font-size: 20px;
100
+ }
101
+
102
+ .card-body {
103
+ padding: 1.5rem;
104
+ }
105
+
106
+ .stats-grid {
107
+ display: grid;
108
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
109
+ gap: 1rem;
110
+ margin-bottom: 2rem;
111
+ }
112
+
113
+ .stat-card {
114
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
115
+ color: white;
116
+ padding: 1.5rem;
117
+ border-radius: 8px;
118
+ text-align: center;
119
+ }
120
+
121
+ .stat-card h3 {
122
+ font-size: 32px;
123
+ margin-bottom: 0.5rem;
124
+ }
125
+
126
+ .stat-card p {
127
+ font-size: 14px;
128
+ opacity: 0.9;
129
+ }
130
+
131
+ .table {
132
+ width: 100%;
133
+ border-collapse: collapse;
134
+ }
135
+
136
+ .table th,
137
+ .table td {
138
+ padding: 0.75rem;
139
+ text-align: left;
140
+ border-bottom: 1px solid #eee;
141
+ }
142
+
143
+ .table th {
144
+ background: #f8f9fa;
145
+ font-weight: 600;
146
+ color: #333;
147
+ }
148
+
149
+ .status-badge {
150
+ padding: 0.25rem 0.5rem;
151
+ border-radius: 12px;
152
+ font-size: 12px;
153
+ font-weight: 500;
154
+ }
155
+
156
+ .status-running {
157
+ background: #d4edda;
158
+ color: #155724;
159
+ }
160
+
161
+ .status-stopped {
162
+ background: #f8d7da;
163
+ color: #721c24;
164
+ }
165
+
166
+ .status-connected {
167
+ background: #d1ecf1;
168
+ color: #0c5460;
169
+ }
170
+
171
+ .modal {
172
+ display: none;
173
+ position: fixed;
174
+ top: 0;
175
+ left: 0;
176
+ width: 100%;
177
+ height: 100%;
178
+ background: rgba(0, 0, 0, 0.5);
179
+ z-index: 1000;
180
+ }
181
+
182
+ .modal-content {
183
+ background: white;
184
+ margin: 10% auto;
185
+ padding: 2rem;
186
+ border-radius: 8px;
187
+ max-width: 500px;
188
+ width: 90%;
189
+ }
190
+
191
+ .modal-header {
192
+ display: flex;
193
+ justify-content: space-between;
194
+ align-items: center;
195
+ margin-bottom: 1rem;
196
+ }
197
+
198
+ .modal-header h3 {
199
+ color: #333;
200
+ }
201
+
202
+ .close {
203
+ font-size: 24px;
204
+ cursor: pointer;
205
+ color: #666;
206
+ }
207
+
208
+ .close:hover {
209
+ color: #333;
210
+ }
211
+
212
+ .form-group {
213
+ margin-bottom: 1rem;
214
+ }
215
+
216
+ .form-group label {
217
+ display: block;
218
+ margin-bottom: 0.5rem;
219
+ color: #333;
220
+ font-weight: 500;
221
+ }
222
+
223
+ .form-group input {
224
+ width: 100%;
225
+ padding: 0.5rem;
226
+ border: 1px solid #ddd;
227
+ border-radius: 4px;
228
+ font-size: 14px;
229
+ }
230
+
231
+ .form-group input:focus {
232
+ outline: none;
233
+ border-color: #007bff;
234
+ }
235
+
236
+ .error-message {
237
+ color: #dc3545;
238
+ font-size: 14px;
239
+ margin-top: 0.5rem;
240
+ display: none;
241
+ }
242
+
243
+ .success-message {
244
+ color: #28a745;
245
+ font-size: 14px;
246
+ margin-top: 0.5rem;
247
+ display: none;
248
+ }
249
+
250
+ .connection-details {
251
+ font-size: 12px;
252
+ color: #666;
253
+ margin-top: 0.5rem;
254
+ }
255
+
256
+ .refresh-btn {
257
+ font-size: 12px;
258
+ padding: 0.25rem 0.5rem;
259
+ }
260
+ </style>
261
+ </head>
262
+ <body>
263
+ <div class="header">
264
+ <h1>TCP转发服务器管理面板</h1>
265
+ <div class="header-actions">
266
+ <span>欢迎,管理员</span>
267
+ <button class="btn btn-danger" onclick="logout()">退出登录</button>
268
+ </div>
269
+ </div>
270
+
271
+ <div class="container">
272
+ <!-- 系统状态 -->
273
+ <div class="card">
274
+ <div class="card-header">
275
+ <h2>系统状态</h2>
276
+ <button class="btn btn-primary refresh-btn" onclick="refreshStatus()">刷新</button>
277
+ </div>
278
+ <div class="card-body">
279
+ <div class="stats-grid" id="statsGrid">
280
+ <!-- 状态卡片将通过JavaScript动态生成 -->
281
+ </div>
282
+ </div>
283
+ </div>
284
+
285
+ <!-- 端口映射管理 -->
286
+ <div class="card">
287
+ <div class="card-header">
288
+ <h2>端口映射管理</h2>
289
+ <button class="btn btn-success" onclick="showAddMappingModal()">添加映射</button>
290
+ </div>
291
+ <div class="card-body">
292
+ <table class="table">
293
+ <thead>
294
+ <tr>
295
+ <th>名称</th>
296
+ <th>监听端口</th>
297
+ <th>目标地址</th>
298
+ <th>连接数</th>
299
+ <th>流量统计</th>
300
+ <th>状态</th>
301
+ <th>操作</th>
302
+ </tr>
303
+ </thead>
304
+ <tbody id="mappingsTable">
305
+ <!-- 端口映射列表将通过JavaScript动态生成 -->
306
+ </tbody>
307
+ </table>
308
+ </div>
309
+ </div>
310
+
311
+ <!-- 连接状态监控 -->
312
+ <div class="card">
313
+ <div class="card-header">
314
+ <h2>连接状态监控</h2>
315
+ <button class="btn btn-primary refresh-btn" onclick="refreshConnections()">刷新</button>
316
+ </div>
317
+ <div class="card-body">
318
+ <div id="connectionsDisplay">
319
+ <!-- 连接状态将通过JavaScript动态生成 -->
320
+ </div>
321
+ </div>
322
+ </div>
323
+ </div>
324
+
325
+ <!-- 添加映射模态框 -->
326
+ <div id="addMappingModal" class="modal">
327
+ <div class="modal-content">
328
+ <div class="modal-header">
329
+ <h3>添加端口映射</h3>
330
+ <span class="close" onclick="closeModal()">&times;</span>
331
+ </div>
332
+ <form id="addMappingForm">
333
+ <div class="form-group">
334
+ <label for="mappingName">映射名称</label>
335
+ <input type="text" id="mappingName" name="name" required>
336
+ </div>
337
+ <div class="form-group">
338
+ <label for="listenPort">监听端口</label>
339
+ <input type="number" id="listenPort" name="listenPort" min="1" max="65535" required>
340
+ </div>
341
+ <div class="form-group">
342
+ <label for="targetHost">目标主机</label>
343
+ <input type="text" id="targetHost" name="targetHost" required>
344
+ </div>
345
+ <div class="form-group">
346
+ <label for="targetPort">目标端口</label>
347
+ <input type="number" id="targetPort" name="targetPort" min="1" max="65535" required>
348
+ </div>
349
+ <button type="submit" class="btn btn-success">添加映射</button>
350
+ <div class="error-message" id="modalErrorMessage"></div>
351
+ <div class="success-message" id="modalSuccessMessage"></div>
352
+ </form>
353
+ </div>
354
+ </div>
355
+
356
+ <script>
357
+ let mappings = [];
358
+ let connections = {};
359
+ let status = {};
360
+
361
+ // 页面加载时初始化数据
362
+ document.addEventListener('DOMContentLoaded', function() {
363
+ loadMappings();
364
+ loadConnections();
365
+ loadStatus();
366
+
367
+ // 每5秒自动刷新连接状态
368
+ setInterval(() => {
369
+ loadConnections();
370
+ loadStatus();
371
+ }, 5000);
372
+ });
373
+
374
+ // 加载端口映射
375
+ async function loadMappings() {
376
+ try {
377
+ const response = await fetch('/api/mappings');
378
+ const data = await response.json();
379
+
380
+ if (data.success) {
381
+ mappings = data.data;
382
+ renderMappings();
383
+ }
384
+ } catch (error) {
385
+ console.error('加载端口映射失败:', error);
386
+ }
387
+ }
388
+
389
+ // 加载连接状态
390
+ async function loadConnections() {
391
+ try {
392
+ const response = await fetch('/api/connections');
393
+ const data = await response.json();
394
+
395
+ if (data.success) {
396
+ connections = data.data;
397
+ renderConnections();
398
+ }
399
+ } catch (error) {
400
+ console.error('加载连接状态失败:', error);
401
+ }
402
+ }
403
+
404
+ // 加载系统状态
405
+ async function loadStatus() {
406
+ try {
407
+ const response = await fetch('/api/status');
408
+ const data = await response.json();
409
+
410
+ if (data.success) {
411
+ status = data.data;
412
+ renderStatus();
413
+ }
414
+ } catch (error) {
415
+ console.error('加载系统状态失败:', error);
416
+ }
417
+ }
418
+
419
+ // 格式化字节数
420
+ function formatBytes(bytes) {
421
+ if (bytes === 0) return '0 B';
422
+ const k = 1024;
423
+ const sizes = ['B', 'KB', 'MB', 'GB'];
424
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
425
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
426
+ }
427
+
428
+ // 渲染端口映射列表
429
+ function renderMappings() {
430
+ const tbody = document.getElementById('mappingsTable');
431
+ tbody.innerHTML = '';
432
+
433
+ mappings.forEach((mapping, index) => {
434
+ const row = document.createElement('tr');
435
+ const identifier = mapping.id || index;
436
+ row.innerHTML = `
437
+ <td>${mapping.name}</td>
438
+ <td>${mapping.listen.host}:${mapping.listen.port}</td>
439
+ <td>${mapping.target.host}:${mapping.target.port}</td>
440
+ <td><span class="status-badge status-connected">${mapping.connectionCount || 0}</span></td>
441
+ <td>
442
+ <small>↑ ${formatBytes(mapping.bytesReceived || 0)}</small><br>
443
+ <small>↓ ${formatBytes(mapping.bytesSent || 0)}</small>
444
+ </td>
445
+ <td>
446
+ <span class="status-badge ${mapping.status === 'running' ? 'status-running' : 'status-stopped'}">
447
+ ${mapping.status === 'running' ? '运行中' : '已停止'}
448
+ </span>
449
+ </td>
450
+ <td>
451
+ ${mapping.status === 'running'
452
+ ? `<button class="btn btn-primary" style="margin-right:5px" onclick="stopMapping('${identifier}')">停止</button>`
453
+ : `<button class="btn btn-success" style="margin-right:5px" onclick="startMapping('${identifier}')">启动</button>`
454
+ }
455
+ <button class="btn btn-danger" onclick="deleteMapping('${identifier}')">删除</button>
456
+ </td>
457
+ `;
458
+ tbody.appendChild(row);
459
+ });
460
+ }
461
+
462
+ // 渲染连接状态
463
+ function renderConnections() {
464
+ const container = document.getElementById('connectionsDisplay');
465
+ container.innerHTML = '';
466
+
467
+ if (connections.byPort && connections.byPort.length > 0) {
468
+ connections.byPort.forEach(portStat => {
469
+ const portDiv = document.createElement('div');
470
+ portDiv.style.marginBottom = '1rem';
471
+ portDiv.style.padding = '1rem';
472
+ portDiv.style.border = '1px solid #eee';
473
+ portDiv.style.borderRadius = '5px';
474
+
475
+ portDiv.innerHTML = `
476
+ <h4>端口 ${portStat.port} (${portStat.mappingName})</h4>
477
+ <p>总连接数: ${portStat.totalConnections}</p>
478
+ <div class="connection-details">
479
+ ${portStat.connections.map(conn => `
480
+ <div>
481
+ <span class="status-badge status-connected">${conn.status}</span>
482
+ ${conn.clientAddress} → ${conn.targetAddress}
483
+ (上行: ${(conn.bytesReceived / 1024).toFixed(2)} KB, 下行: ${(conn.bytesSent / 1024).toFixed(2)} KB)
484
+ </div>
485
+ `).join('')}
486
+ </div>
487
+ `;
488
+
489
+ container.appendChild(portDiv);
490
+ });
491
+ } else {
492
+ container.innerHTML = '<p>当前没有活跃连接</p>';
493
+ }
494
+ }
495
+
496
+ // 渲染系统状态
497
+ function renderStatus() {
498
+ const container = document.getElementById('statsGrid');
499
+ container.innerHTML = `
500
+ <div class="stat-card">
501
+ <h3>${status.totalMappings || 0}</h3>
502
+ <p>总映射数</p>
503
+ </div>
504
+ <div class="stat-card">
505
+ <h3>${status.activeMappings || 0}</h3>
506
+ <p>活跃映射</p>
507
+ </div>
508
+ <div class="stat-card">
509
+ <h3>${status.totalConnections || 0}</h3>
510
+ <p>总连接数</p>
511
+ </div>
512
+ <div class="stat-card">
513
+ <h3>${Math.floor((status.uptime || 0) / 60)}m</h3>
514
+ <p>运行时间</p>
515
+ </div>
516
+ `;
517
+ }
518
+
519
+ // 显示添加映射模态框
520
+ function showAddMappingModal() {
521
+ document.getElementById('addMappingModal').style.display = 'block';
522
+ }
523
+
524
+ // 关闭模态框
525
+ function closeModal() {
526
+ document.getElementById('addMappingModal').style.display = 'none';
527
+ document.getElementById('addMappingForm').reset();
528
+ document.getElementById('modalErrorMessage').style.display = 'none';
529
+ document.getElementById('modalSuccessMessage').style.display = 'none';
530
+ }
531
+
532
+ // 添加端口映射
533
+ document.getElementById('addMappingForm').addEventListener('submit', async function(e) {
534
+ e.preventDefault();
535
+
536
+ const formData = new FormData(e.target);
537
+ const mappingData = {
538
+ name: formData.get('name'),
539
+ listen: {
540
+ host: '0.0.0.0',
541
+ port: parseInt(formData.get('listenPort'))
542
+ },
543
+ target: {
544
+ host: formData.get('targetHost'),
545
+ port: parseInt(formData.get('targetPort'))
546
+ }
547
+ };
548
+
549
+ try {
550
+ const response = await fetch('/api/mappings', {
551
+ method: 'POST',
552
+ headers: {
553
+ 'Content-Type': 'application/json',
554
+ },
555
+ body: JSON.stringify(mappingData)
556
+ });
557
+
558
+ const data = await response.json();
559
+
560
+ if (data.success) {
561
+ document.getElementById('modalSuccessMessage').textContent = data.message;
562
+ document.getElementById('modalSuccessMessage').style.display = 'block';
563
+
564
+ setTimeout(() => {
565
+ closeModal();
566
+ loadMappings();
567
+ }, 1000);
568
+ } else {
569
+ document.getElementById('modalErrorMessage').textContent = data.message;
570
+ document.getElementById('modalErrorMessage').style.display = 'block';
571
+ }
572
+ } catch (error) {
573
+ document.getElementById('modalErrorMessage').textContent = '添加失败,请检查网络连接';
574
+ document.getElementById('modalErrorMessage').style.display = 'block';
575
+ }
576
+ });
577
+
578
+ // 删除端口映射
579
+ async function deleteMapping(identifier) {
580
+ if (!confirm('确定要删除这个端口映射吗?')) {
581
+ return;
582
+ }
583
+
584
+ try {
585
+ const response = await fetch(`/api/mappings/${identifier}`, {
586
+ method: 'DELETE'
587
+ });
588
+
589
+ const data = await response.json();
590
+
591
+ if (data.success) {
592
+ loadMappings();
593
+ } else {
594
+ alert('删除失败: ' + data.message);
595
+ }
596
+ } catch (error) {
597
+ alert('删除失败,请检查网络连接');
598
+ }
599
+ }
600
+
601
+ // 启动端口映射
602
+ async function startMapping(identifier) {
603
+ try {
604
+ const response = await fetch(`/api/mappings/${identifier}/start`, {
605
+ method: 'POST'
606
+ });
607
+
608
+ const data = await response.json();
609
+
610
+ if (data.success) {
611
+ loadMappings();
612
+ } else {
613
+ alert('启动失败: ' + data.message);
614
+ }
615
+ } catch (error) {
616
+ alert('启动失败,请检查网络连接');
617
+ }
618
+ }
619
+
620
+ // 停止端口映射
621
+ async function stopMapping(identifier) {
622
+ try {
623
+ const response = await fetch(`/api/mappings/${identifier}/stop`, {
624
+ method: 'POST'
625
+ });
626
+
627
+ const data = await response.json();
628
+
629
+ if (data.success) {
630
+ loadMappings();
631
+ } else {
632
+ alert('停止失败: ' + data.message);
633
+ }
634
+ } catch (error) {
635
+ alert('停止失败,请检查网络连接');
636
+ }
637
+ }
638
+
639
+ // 刷新状态
640
+ function refreshStatus() {
641
+ loadStatus();
642
+ }
643
+
644
+ // 刷新连接
645
+ function refreshConnections() {
646
+ loadConnections();
647
+ }
648
+
649
+ // 退出登录
650
+ async function logout() {
651
+ try {
652
+ await fetch('/api/logout', { method: 'POST' });
653
+ window.location.href = '/login';
654
+ } catch (error) {
655
+ console.error('退出登录失败:', error);
656
+ }
657
+ }
658
+
659
+ // 点击模态框外部关闭
660
+ window.onclick = function(event) {
661
+ const modal = document.getElementById('addMappingModal');
662
+ if (event.target === modal) {
663
+ closeModal();
664
+ }
665
+ }
666
+ </script>
667
+ </body>
668
+ </html>