@chenpu17/tcpmapping 2.0.0 → 2.0.1
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 +178 -54
- package/package.json +1 -1
- package/public/dashboard.html +388 -125
- package/public/login.html +157 -67
package/public/dashboard.html
CHANGED
|
@@ -13,22 +13,24 @@
|
|
|
13
13
|
|
|
14
14
|
body {
|
|
15
15
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
16
|
-
background: #
|
|
16
|
+
background: linear-gradient(135deg, #f5f7fa 0%, #e4e8ec 100%);
|
|
17
17
|
min-height: 100vh;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
.header {
|
|
21
|
-
background:
|
|
21
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
22
22
|
padding: 1rem 2rem;
|
|
23
|
-
box-shadow: 0
|
|
23
|
+
box-shadow: 0 4px 20px rgba(102, 126, 234, 0.3);
|
|
24
24
|
display: flex;
|
|
25
25
|
justify-content: space-between;
|
|
26
26
|
align-items: center;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
.header h1 {
|
|
30
|
-
color:
|
|
31
|
-
font-size:
|
|
30
|
+
color: white;
|
|
31
|
+
font-size: 22px;
|
|
32
|
+
font-weight: 600;
|
|
33
|
+
text-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
.header-actions {
|
|
@@ -37,95 +39,152 @@
|
|
|
37
39
|
align-items: center;
|
|
38
40
|
}
|
|
39
41
|
|
|
42
|
+
.header-actions span {
|
|
43
|
+
color: rgba(255,255,255,0.9);
|
|
44
|
+
font-size: 14px;
|
|
45
|
+
}
|
|
46
|
+
|
|
40
47
|
.btn {
|
|
41
|
-
padding: 0.
|
|
48
|
+
padding: 0.6rem 1.2rem;
|
|
42
49
|
border: none;
|
|
43
|
-
border-radius:
|
|
50
|
+
border-radius: 6px;
|
|
44
51
|
cursor: pointer;
|
|
45
52
|
font-size: 14px;
|
|
46
|
-
|
|
53
|
+
font-weight: 500;
|
|
54
|
+
transition: all 0.3s ease;
|
|
55
|
+
display: inline-flex;
|
|
56
|
+
align-items: center;
|
|
57
|
+
gap: 6px;
|
|
47
58
|
}
|
|
48
59
|
|
|
49
60
|
.btn-primary {
|
|
50
|
-
background: #
|
|
61
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
51
62
|
color: white;
|
|
63
|
+
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
|
|
52
64
|
}
|
|
53
65
|
|
|
54
66
|
.btn-primary:hover {
|
|
55
|
-
|
|
67
|
+
transform: translateY(-2px);
|
|
68
|
+
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.5);
|
|
56
69
|
}
|
|
57
70
|
|
|
58
71
|
.btn-danger {
|
|
59
|
-
background: #
|
|
72
|
+
background: linear-gradient(135deg, #ff6b6b 0%, #ee5a5a 100%);
|
|
60
73
|
color: white;
|
|
74
|
+
box-shadow: 0 4px 15px rgba(255, 107, 107, 0.3);
|
|
61
75
|
}
|
|
62
76
|
|
|
63
77
|
.btn-danger:hover {
|
|
64
|
-
|
|
78
|
+
transform: translateY(-2px);
|
|
79
|
+
box-shadow: 0 6px 20px rgba(255, 107, 107, 0.4);
|
|
65
80
|
}
|
|
66
81
|
|
|
67
82
|
.btn-success {
|
|
68
|
-
background: #
|
|
83
|
+
background: linear-gradient(135deg, #2ed573 0%, #26de81 100%);
|
|
69
84
|
color: white;
|
|
85
|
+
box-shadow: 0 4px 15px rgba(46, 213, 115, 0.3);
|
|
70
86
|
}
|
|
71
87
|
|
|
72
88
|
.btn-success:hover {
|
|
73
|
-
|
|
89
|
+
transform: translateY(-2px);
|
|
90
|
+
box-shadow: 0 6px 20px rgba(46, 213, 115, 0.4);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.btn-warning {
|
|
94
|
+
background: linear-gradient(135deg, #ffa502 0%, #ff9f43 100%);
|
|
95
|
+
color: white;
|
|
96
|
+
box-shadow: 0 4px 15px rgba(255, 165, 2, 0.3);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.btn-warning:hover {
|
|
100
|
+
transform: translateY(-2px);
|
|
101
|
+
box-shadow: 0 6px 20px rgba(255, 165, 2, 0.4);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.btn-sm {
|
|
105
|
+
padding: 0.4rem 0.8rem;
|
|
106
|
+
font-size: 12px;
|
|
74
107
|
}
|
|
75
108
|
|
|
76
109
|
.container {
|
|
77
|
-
max-width:
|
|
110
|
+
max-width: 1300px;
|
|
78
111
|
margin: 0 auto;
|
|
79
112
|
padding: 2rem;
|
|
80
113
|
}
|
|
81
114
|
|
|
82
115
|
.card {
|
|
83
116
|
background: white;
|
|
84
|
-
border-radius:
|
|
85
|
-
box-shadow: 0
|
|
117
|
+
border-radius: 16px;
|
|
118
|
+
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.08);
|
|
86
119
|
margin-bottom: 2rem;
|
|
120
|
+
overflow: hidden;
|
|
87
121
|
}
|
|
88
122
|
|
|
89
123
|
.card-header {
|
|
90
|
-
padding: 1.5rem;
|
|
91
|
-
border-bottom: 1px solid #
|
|
124
|
+
padding: 1.5rem 2rem;
|
|
125
|
+
border-bottom: 1px solid #f0f0f0;
|
|
92
126
|
display: flex;
|
|
93
127
|
justify-content: space-between;
|
|
94
128
|
align-items: center;
|
|
129
|
+
background: linear-gradient(135deg, #fafbfc 0%, #f8f9fa 100%);
|
|
95
130
|
}
|
|
96
131
|
|
|
97
132
|
.card-header h2 {
|
|
98
|
-
color: #
|
|
99
|
-
font-size:
|
|
133
|
+
color: #2d3436;
|
|
134
|
+
font-size: 18px;
|
|
135
|
+
font-weight: 600;
|
|
100
136
|
}
|
|
101
137
|
|
|
102
138
|
.card-body {
|
|
103
|
-
padding: 1.5rem;
|
|
139
|
+
padding: 1.5rem 2rem;
|
|
104
140
|
}
|
|
105
141
|
|
|
106
142
|
.stats-grid {
|
|
107
143
|
display: grid;
|
|
108
|
-
grid-template-columns: repeat(auto-fit, minmax(
|
|
109
|
-
gap:
|
|
110
|
-
margin-bottom:
|
|
144
|
+
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
|
145
|
+
gap: 1.5rem;
|
|
146
|
+
margin-bottom: 0;
|
|
111
147
|
}
|
|
112
148
|
|
|
113
149
|
.stat-card {
|
|
114
150
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
115
151
|
color: white;
|
|
116
|
-
padding: 1.
|
|
117
|
-
border-radius:
|
|
152
|
+
padding: 1.8rem;
|
|
153
|
+
border-radius: 16px;
|
|
118
154
|
text-align: center;
|
|
155
|
+
box-shadow: 0 10px 30px rgba(102, 126, 234, 0.3);
|
|
156
|
+
transition: transform 0.3s ease;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.stat-card:hover {
|
|
160
|
+
transform: translateY(-5px);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.stat-card:nth-child(2) {
|
|
164
|
+
background: linear-gradient(135deg, #2ed573 0%, #26de81 100%);
|
|
165
|
+
box-shadow: 0 10px 30px rgba(46, 213, 115, 0.3);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.stat-card:nth-child(3) {
|
|
169
|
+
background: linear-gradient(135deg, #ffa502 0%, #ff9f43 100%);
|
|
170
|
+
box-shadow: 0 10px 30px rgba(255, 165, 2, 0.3);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.stat-card:nth-child(4) {
|
|
174
|
+
background: linear-gradient(135deg, #45aaf2 0%, #2d98da 100%);
|
|
175
|
+
box-shadow: 0 10px 30px rgba(69, 170, 242, 0.3);
|
|
119
176
|
}
|
|
120
177
|
|
|
121
178
|
.stat-card h3 {
|
|
122
|
-
font-size:
|
|
179
|
+
font-size: 36px;
|
|
123
180
|
margin-bottom: 0.5rem;
|
|
181
|
+
font-weight: 700;
|
|
124
182
|
}
|
|
125
183
|
|
|
126
184
|
.stat-card p {
|
|
127
185
|
font-size: 14px;
|
|
128
186
|
opacity: 0.9;
|
|
187
|
+
font-weight: 500;
|
|
129
188
|
}
|
|
130
189
|
|
|
131
190
|
.table {
|
|
@@ -135,39 +194,67 @@
|
|
|
135
194
|
|
|
136
195
|
.table th,
|
|
137
196
|
.table td {
|
|
138
|
-
padding:
|
|
197
|
+
padding: 1rem;
|
|
139
198
|
text-align: left;
|
|
140
|
-
border-bottom: 1px solid #
|
|
199
|
+
border-bottom: 1px solid #f0f0f0;
|
|
141
200
|
}
|
|
142
201
|
|
|
143
202
|
.table th {
|
|
144
|
-
background: #f8f9fa;
|
|
203
|
+
background: linear-gradient(135deg, #f8f9fa 0%, #f1f3f4 100%);
|
|
145
204
|
font-weight: 600;
|
|
146
|
-
color: #
|
|
205
|
+
color: #2d3436;
|
|
206
|
+
font-size: 13px;
|
|
207
|
+
text-transform: uppercase;
|
|
208
|
+
letter-spacing: 0.5px;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.table tr:hover {
|
|
212
|
+
background-color: #fafbfc;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.table td {
|
|
216
|
+
color: #636e72;
|
|
217
|
+
font-size: 14px;
|
|
147
218
|
}
|
|
148
219
|
|
|
149
220
|
.status-badge {
|
|
150
|
-
padding: 0.
|
|
151
|
-
border-radius:
|
|
221
|
+
padding: 0.35rem 0.75rem;
|
|
222
|
+
border-radius: 20px;
|
|
152
223
|
font-size: 12px;
|
|
153
|
-
font-weight:
|
|
224
|
+
font-weight: 600;
|
|
225
|
+
display: inline-block;
|
|
154
226
|
}
|
|
155
227
|
|
|
156
228
|
.status-running {
|
|
157
|
-
background: #d4edda;
|
|
229
|
+
background: linear-gradient(135deg, #d4edda 0%, #c3e6cb 100%);
|
|
158
230
|
color: #155724;
|
|
159
231
|
}
|
|
160
232
|
|
|
161
233
|
.status-stopped {
|
|
162
|
-
background: #f8d7da;
|
|
234
|
+
background: linear-gradient(135deg, #f8d7da 0%, #f5c6cb 100%);
|
|
163
235
|
color: #721c24;
|
|
164
236
|
}
|
|
165
237
|
|
|
166
238
|
.status-connected {
|
|
167
|
-
background: #d1ecf1;
|
|
239
|
+
background: linear-gradient(135deg, #d1ecf1 0%, #bee5eb 100%);
|
|
168
240
|
color: #0c5460;
|
|
169
241
|
}
|
|
170
242
|
|
|
243
|
+
.traffic-info {
|
|
244
|
+
font-size: 12px;
|
|
245
|
+
color: #636e72;
|
|
246
|
+
line-height: 1.6;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.traffic-info .up {
|
|
250
|
+
color: #2ed573;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.traffic-info .down {
|
|
254
|
+
color: #45aaf2;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/* Modal Styles */
|
|
171
258
|
.modal {
|
|
172
259
|
display: none;
|
|
173
260
|
position: fixed;
|
|
@@ -175,87 +262,232 @@
|
|
|
175
262
|
left: 0;
|
|
176
263
|
width: 100%;
|
|
177
264
|
height: 100%;
|
|
178
|
-
background: rgba(0, 0, 0, 0.
|
|
265
|
+
background: rgba(0, 0, 0, 0.6);
|
|
266
|
+
backdrop-filter: blur(4px);
|
|
179
267
|
z-index: 1000;
|
|
268
|
+
animation: fadeIn 0.3s ease;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
@keyframes fadeIn {
|
|
272
|
+
from { opacity: 0; }
|
|
273
|
+
to { opacity: 1; }
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
@keyframes slideIn {
|
|
277
|
+
from {
|
|
278
|
+
opacity: 0;
|
|
279
|
+
transform: translateY(-30px) scale(0.95);
|
|
280
|
+
}
|
|
281
|
+
to {
|
|
282
|
+
opacity: 1;
|
|
283
|
+
transform: translateY(0) scale(1);
|
|
284
|
+
}
|
|
180
285
|
}
|
|
181
286
|
|
|
182
287
|
.modal-content {
|
|
183
288
|
background: white;
|
|
184
|
-
margin:
|
|
185
|
-
padding:
|
|
186
|
-
border-radius:
|
|
187
|
-
max-width:
|
|
289
|
+
margin: 8% auto;
|
|
290
|
+
padding: 0;
|
|
291
|
+
border-radius: 20px;
|
|
292
|
+
max-width: 480px;
|
|
188
293
|
width: 90%;
|
|
294
|
+
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.25);
|
|
295
|
+
animation: slideIn 0.3s ease;
|
|
296
|
+
overflow: hidden;
|
|
189
297
|
}
|
|
190
298
|
|
|
191
299
|
.modal-header {
|
|
192
300
|
display: flex;
|
|
193
301
|
justify-content: space-between;
|
|
194
302
|
align-items: center;
|
|
195
|
-
|
|
303
|
+
padding: 1.5rem 2rem;
|
|
304
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
305
|
+
color: white;
|
|
196
306
|
}
|
|
197
307
|
|
|
198
308
|
.modal-header h3 {
|
|
199
|
-
|
|
309
|
+
font-size: 18px;
|
|
310
|
+
font-weight: 600;
|
|
200
311
|
}
|
|
201
312
|
|
|
202
313
|
.close {
|
|
203
|
-
font-size:
|
|
314
|
+
font-size: 28px;
|
|
204
315
|
cursor: pointer;
|
|
205
|
-
color:
|
|
316
|
+
color: rgba(255,255,255,0.8);
|
|
317
|
+
transition: color 0.2s;
|
|
318
|
+
line-height: 1;
|
|
206
319
|
}
|
|
207
320
|
|
|
208
321
|
.close:hover {
|
|
209
|
-
color:
|
|
322
|
+
color: white;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.modal-body {
|
|
326
|
+
padding: 2rem;
|
|
210
327
|
}
|
|
211
328
|
|
|
212
329
|
.form-group {
|
|
213
|
-
margin-bottom:
|
|
330
|
+
margin-bottom: 1.5rem;
|
|
214
331
|
}
|
|
215
332
|
|
|
216
333
|
.form-group label {
|
|
217
334
|
display: block;
|
|
218
335
|
margin-bottom: 0.5rem;
|
|
219
|
-
color: #
|
|
220
|
-
font-weight:
|
|
336
|
+
color: #2d3436;
|
|
337
|
+
font-weight: 600;
|
|
338
|
+
font-size: 14px;
|
|
221
339
|
}
|
|
222
340
|
|
|
223
341
|
.form-group input {
|
|
224
342
|
width: 100%;
|
|
225
|
-
padding: 0.
|
|
226
|
-
border:
|
|
227
|
-
border-radius:
|
|
343
|
+
padding: 0.85rem 1rem;
|
|
344
|
+
border: 2px solid #e9ecef;
|
|
345
|
+
border-radius: 10px;
|
|
228
346
|
font-size: 14px;
|
|
347
|
+
transition: all 0.3s ease;
|
|
229
348
|
}
|
|
230
349
|
|
|
231
350
|
.form-group input:focus {
|
|
232
351
|
outline: none;
|
|
233
|
-
border-color: #
|
|
352
|
+
border-color: #667eea;
|
|
353
|
+
box-shadow: 0 0 0 4px rgba(102, 126, 234, 0.1);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
.form-group input::placeholder {
|
|
357
|
+
color: #b2bec3;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
.modal-footer {
|
|
361
|
+
padding: 0 2rem 2rem 2rem;
|
|
362
|
+
display: flex;
|
|
363
|
+
gap: 1rem;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
.modal-footer .btn {
|
|
367
|
+
flex: 1;
|
|
368
|
+
justify-content: center;
|
|
369
|
+
padding: 0.85rem 1.5rem;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
.btn-cancel {
|
|
373
|
+
background: #f1f3f4;
|
|
374
|
+
color: #636e72;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
.btn-cancel:hover {
|
|
378
|
+
background: #e9ecef;
|
|
234
379
|
}
|
|
235
380
|
|
|
236
381
|
.error-message {
|
|
237
|
-
color: #
|
|
382
|
+
color: #ee5a5a;
|
|
238
383
|
font-size: 14px;
|
|
239
|
-
margin-top:
|
|
384
|
+
margin-top: 1rem;
|
|
385
|
+
padding: 0.75rem 1rem;
|
|
386
|
+
background: #fff5f5;
|
|
387
|
+
border-radius: 8px;
|
|
240
388
|
display: none;
|
|
241
389
|
}
|
|
242
390
|
|
|
243
391
|
.success-message {
|
|
244
|
-
color: #
|
|
392
|
+
color: #2ed573;
|
|
245
393
|
font-size: 14px;
|
|
246
|
-
margin-top:
|
|
394
|
+
margin-top: 1rem;
|
|
395
|
+
padding: 0.75rem 1rem;
|
|
396
|
+
background: #f0fff4;
|
|
397
|
+
border-radius: 8px;
|
|
247
398
|
display: none;
|
|
248
399
|
}
|
|
249
400
|
|
|
250
|
-
.connection-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
401
|
+
.connection-card {
|
|
402
|
+
margin-bottom: 1rem;
|
|
403
|
+
padding: 1.25rem;
|
|
404
|
+
border: 1px solid #e9ecef;
|
|
405
|
+
border-radius: 12px;
|
|
406
|
+
background: linear-gradient(135deg, #fafbfc 0%, #f8f9fa 100%);
|
|
407
|
+
transition: all 0.3s ease;
|
|
254
408
|
}
|
|
255
409
|
|
|
256
|
-
.
|
|
410
|
+
.connection-card:hover {
|
|
411
|
+
border-color: #667eea;
|
|
412
|
+
box-shadow: 0 5px 20px rgba(102, 126, 234, 0.1);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
.connection-card h4 {
|
|
416
|
+
color: #2d3436;
|
|
417
|
+
font-size: 15px;
|
|
418
|
+
margin-bottom: 0.75rem;
|
|
419
|
+
display: flex;
|
|
420
|
+
align-items: center;
|
|
421
|
+
gap: 8px;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
.connection-card h4 .port-badge {
|
|
425
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
426
|
+
color: white;
|
|
427
|
+
padding: 0.25rem 0.6rem;
|
|
428
|
+
border-radius: 6px;
|
|
257
429
|
font-size: 12px;
|
|
258
|
-
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
.connection-details {
|
|
433
|
+
font-size: 13px;
|
|
434
|
+
color: #636e72;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
.connection-item {
|
|
438
|
+
padding: 0.5rem 0;
|
|
439
|
+
border-bottom: 1px dashed #e9ecef;
|
|
440
|
+
display: flex;
|
|
441
|
+
align-items: center;
|
|
442
|
+
gap: 8px;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
.connection-item:last-child {
|
|
446
|
+
border-bottom: none;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
.no-connections {
|
|
450
|
+
text-align: center;
|
|
451
|
+
padding: 3rem;
|
|
452
|
+
color: #b2bec3;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
.no-connections svg {
|
|
456
|
+
width: 60px;
|
|
457
|
+
height: 60px;
|
|
458
|
+
margin-bottom: 1rem;
|
|
459
|
+
opacity: 0.5;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
.action-buttons {
|
|
463
|
+
display: flex;
|
|
464
|
+
gap: 8px;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
@media (max-width: 768px) {
|
|
468
|
+
.header {
|
|
469
|
+
flex-direction: column;
|
|
470
|
+
gap: 1rem;
|
|
471
|
+
text-align: center;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
.container {
|
|
475
|
+
padding: 1rem;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
.card-header {
|
|
479
|
+
flex-direction: column;
|
|
480
|
+
gap: 1rem;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
.table {
|
|
484
|
+
display: block;
|
|
485
|
+
overflow-x: auto;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
.stats-grid {
|
|
489
|
+
grid-template-columns: repeat(2, 1fr);
|
|
490
|
+
}
|
|
259
491
|
}
|
|
260
492
|
</style>
|
|
261
493
|
</head>
|
|
@@ -264,7 +496,7 @@
|
|
|
264
496
|
<h1>TCP转发服务器管理面板</h1>
|
|
265
497
|
<div class="header-actions">
|
|
266
498
|
<span>欢迎,管理员</span>
|
|
267
|
-
<button class="btn btn-danger" onclick="logout()">退出登录</button>
|
|
499
|
+
<button class="btn btn-danger btn-sm" onclick="logout()">退出登录</button>
|
|
268
500
|
</div>
|
|
269
501
|
</div>
|
|
270
502
|
|
|
@@ -273,7 +505,7 @@
|
|
|
273
505
|
<div class="card">
|
|
274
506
|
<div class="card-header">
|
|
275
507
|
<h2>系统状态</h2>
|
|
276
|
-
<button class="btn btn-primary
|
|
508
|
+
<button class="btn btn-primary btn-sm" onclick="refreshStatus()">刷新</button>
|
|
277
509
|
</div>
|
|
278
510
|
<div class="card-body">
|
|
279
511
|
<div class="stats-grid" id="statsGrid">
|
|
@@ -286,7 +518,7 @@
|
|
|
286
518
|
<div class="card">
|
|
287
519
|
<div class="card-header">
|
|
288
520
|
<h2>端口映射管理</h2>
|
|
289
|
-
<button class="btn btn-success" onclick="showAddMappingModal()"
|
|
521
|
+
<button class="btn btn-success" onclick="showAddMappingModal()">+ 添加映射</button>
|
|
290
522
|
</div>
|
|
291
523
|
<div class="card-body">
|
|
292
524
|
<table class="table">
|
|
@@ -312,7 +544,7 @@
|
|
|
312
544
|
<div class="card">
|
|
313
545
|
<div class="card-header">
|
|
314
546
|
<h2>连接状态监控</h2>
|
|
315
|
-
<button class="btn btn-primary
|
|
547
|
+
<button class="btn btn-primary btn-sm" onclick="refreshConnections()">刷新</button>
|
|
316
548
|
</div>
|
|
317
549
|
<div class="card-body">
|
|
318
550
|
<div id="connectionsDisplay">
|
|
@@ -330,25 +562,30 @@
|
|
|
330
562
|
<span class="close" onclick="closeModal()">×</span>
|
|
331
563
|
</div>
|
|
332
564
|
<form id="addMappingForm">
|
|
333
|
-
<div class="
|
|
334
|
-
<
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
<
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
<
|
|
343
|
-
|
|
565
|
+
<div class="modal-body">
|
|
566
|
+
<div class="form-group">
|
|
567
|
+
<label for="mappingName">映射名称</label>
|
|
568
|
+
<input type="text" id="mappingName" name="name" placeholder="例如:Web服务" required>
|
|
569
|
+
</div>
|
|
570
|
+
<div class="form-group">
|
|
571
|
+
<label for="listenPort">监听端口</label>
|
|
572
|
+
<input type="number" id="listenPort" name="listenPort" min="1" max="65535" placeholder="1-65535" required>
|
|
573
|
+
</div>
|
|
574
|
+
<div class="form-group">
|
|
575
|
+
<label for="targetHost">目标主机</label>
|
|
576
|
+
<input type="text" id="targetHost" name="targetHost" placeholder="IP地址或域名" required>
|
|
577
|
+
</div>
|
|
578
|
+
<div class="form-group">
|
|
579
|
+
<label for="targetPort">目标端口</label>
|
|
580
|
+
<input type="number" id="targetPort" name="targetPort" min="1" max="65535" placeholder="1-65535" required>
|
|
581
|
+
</div>
|
|
582
|
+
<div class="error-message" id="modalErrorMessage"></div>
|
|
583
|
+
<div class="success-message" id="modalSuccessMessage"></div>
|
|
344
584
|
</div>
|
|
345
|
-
<div class="
|
|
346
|
-
<
|
|
347
|
-
<
|
|
585
|
+
<div class="modal-footer">
|
|
586
|
+
<button type="button" class="btn btn-cancel" onclick="closeModal()">取消</button>
|
|
587
|
+
<button type="submit" class="btn btn-success">确认添加</button>
|
|
348
588
|
</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
589
|
</form>
|
|
353
590
|
</div>
|
|
354
591
|
</div>
|
|
@@ -363,7 +600,7 @@
|
|
|
363
600
|
loadMappings();
|
|
364
601
|
loadConnections();
|
|
365
602
|
loadStatus();
|
|
366
|
-
|
|
603
|
+
|
|
367
604
|
// 每5秒自动刷新连接状态
|
|
368
605
|
setInterval(() => {
|
|
369
606
|
loadConnections();
|
|
@@ -376,7 +613,7 @@
|
|
|
376
613
|
try {
|
|
377
614
|
const response = await fetch('/api/mappings');
|
|
378
615
|
const data = await response.json();
|
|
379
|
-
|
|
616
|
+
|
|
380
617
|
if (data.success) {
|
|
381
618
|
mappings = data.data;
|
|
382
619
|
renderMappings();
|
|
@@ -391,7 +628,7 @@
|
|
|
391
628
|
try {
|
|
392
629
|
const response = await fetch('/api/connections');
|
|
393
630
|
const data = await response.json();
|
|
394
|
-
|
|
631
|
+
|
|
395
632
|
if (data.success) {
|
|
396
633
|
connections = data.data;
|
|
397
634
|
renderConnections();
|
|
@@ -406,7 +643,7 @@
|
|
|
406
643
|
try {
|
|
407
644
|
const response = await fetch('/api/status');
|
|
408
645
|
const data = await response.json();
|
|
409
|
-
|
|
646
|
+
|
|
410
647
|
if (data.success) {
|
|
411
648
|
status = data.data;
|
|
412
649
|
renderStatus();
|
|
@@ -430,17 +667,24 @@
|
|
|
430
667
|
const tbody = document.getElementById('mappingsTable');
|
|
431
668
|
tbody.innerHTML = '';
|
|
432
669
|
|
|
670
|
+
if (mappings.length === 0) {
|
|
671
|
+
tbody.innerHTML = '<tr><td colspan="7" style="text-align:center;color:#b2bec3;padding:3rem;">暂无端口映射,点击上方按钮添加</td></tr>';
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
|
|
433
675
|
mappings.forEach((mapping, index) => {
|
|
434
676
|
const row = document.createElement('tr');
|
|
435
677
|
const identifier = mapping.id || index;
|
|
436
678
|
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>
|
|
679
|
+
<td><strong>${mapping.name}</strong></td>
|
|
680
|
+
<td><code>${mapping.listen.host}:${mapping.listen.port}</code></td>
|
|
681
|
+
<td><code>${mapping.target.host}:${mapping.target.port}</code></td>
|
|
440
682
|
<td><span class="status-badge status-connected">${mapping.connectionCount || 0}</span></td>
|
|
441
683
|
<td>
|
|
442
|
-
<
|
|
443
|
-
|
|
684
|
+
<div class="traffic-info">
|
|
685
|
+
<span class="up">↑ ${formatBytes(mapping.bytesReceived || 0)}</span><br>
|
|
686
|
+
<span class="down">↓ ${formatBytes(mapping.bytesSent || 0)}</span>
|
|
687
|
+
</div>
|
|
444
688
|
</td>
|
|
445
689
|
<td>
|
|
446
690
|
<span class="status-badge ${mapping.status === 'running' ? 'status-running' : 'status-stopped'}">
|
|
@@ -448,11 +692,13 @@
|
|
|
448
692
|
</span>
|
|
449
693
|
</td>
|
|
450
694
|
<td>
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
695
|
+
<div class="action-buttons">
|
|
696
|
+
${mapping.status === 'running'
|
|
697
|
+
? `<button class="btn btn-warning btn-sm" onclick="stopMapping('${identifier}')">停止</button>`
|
|
698
|
+
: `<button class="btn btn-success btn-sm" onclick="startMapping('${identifier}')">启动</button>`
|
|
699
|
+
}
|
|
700
|
+
<button class="btn btn-danger btn-sm" onclick="deleteMapping('${identifier}')">删除</button>
|
|
701
|
+
</div>
|
|
456
702
|
</td>
|
|
457
703
|
`;
|
|
458
704
|
tbody.appendChild(row);
|
|
@@ -463,39 +709,53 @@
|
|
|
463
709
|
function renderConnections() {
|
|
464
710
|
const container = document.getElementById('connectionsDisplay');
|
|
465
711
|
container.innerHTML = '';
|
|
466
|
-
|
|
712
|
+
|
|
467
713
|
if (connections.byPort && connections.byPort.length > 0) {
|
|
468
714
|
connections.byPort.forEach(portStat => {
|
|
469
715
|
const portDiv = document.createElement('div');
|
|
470
|
-
portDiv.
|
|
471
|
-
|
|
472
|
-
portDiv.style.border = '1px solid #eee';
|
|
473
|
-
portDiv.style.borderRadius = '5px';
|
|
474
|
-
|
|
716
|
+
portDiv.className = 'connection-card';
|
|
717
|
+
|
|
475
718
|
portDiv.innerHTML = `
|
|
476
|
-
<h4>端口 ${portStat.port}
|
|
477
|
-
<p
|
|
719
|
+
<h4><span class="port-badge">端口 ${portStat.port}</span> ${portStat.mappingName}</h4>
|
|
720
|
+
<p style="margin-bottom:0.75rem;color:#636e72;">活跃连接: <strong>${portStat.totalConnections}</strong></p>
|
|
478
721
|
<div class="connection-details">
|
|
479
722
|
${portStat.connections.map(conn => `
|
|
480
|
-
<div>
|
|
723
|
+
<div class="connection-item">
|
|
481
724
|
<span class="status-badge status-connected">${conn.status}</span>
|
|
482
|
-
|
|
483
|
-
|
|
725
|
+
<span>${conn.clientAddress} → ${conn.targetAddress}</span>
|
|
726
|
+
<span style="margin-left:auto;color:#b2bec3;">↑${formatBytes(conn.bytesReceived)} ↓${formatBytes(conn.bytesSent)}</span>
|
|
484
727
|
</div>
|
|
485
728
|
`).join('')}
|
|
486
729
|
</div>
|
|
487
730
|
`;
|
|
488
|
-
|
|
731
|
+
|
|
489
732
|
container.appendChild(portDiv);
|
|
490
733
|
});
|
|
491
734
|
} else {
|
|
492
|
-
container.innerHTML =
|
|
735
|
+
container.innerHTML = `
|
|
736
|
+
<div class="no-connections">
|
|
737
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
738
|
+
<path d="M8.5 14.5A2.5 2.5 0 0011 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 11-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 002.5 2.5z"/>
|
|
739
|
+
</svg>
|
|
740
|
+
<p>当前没有活跃连接</p>
|
|
741
|
+
</div>
|
|
742
|
+
`;
|
|
493
743
|
}
|
|
494
744
|
}
|
|
495
745
|
|
|
496
746
|
// 渲染系统状态
|
|
497
747
|
function renderStatus() {
|
|
498
748
|
const container = document.getElementById('statsGrid');
|
|
749
|
+
const uptime = status.uptime || 0;
|
|
750
|
+
let uptimeStr;
|
|
751
|
+
if (uptime < 60) {
|
|
752
|
+
uptimeStr = Math.floor(uptime) + 's';
|
|
753
|
+
} else if (uptime < 3600) {
|
|
754
|
+
uptimeStr = Math.floor(uptime / 60) + 'm';
|
|
755
|
+
} else {
|
|
756
|
+
uptimeStr = Math.floor(uptime / 3600) + 'h ' + Math.floor((uptime % 3600) / 60) + 'm';
|
|
757
|
+
}
|
|
758
|
+
|
|
499
759
|
container.innerHTML = `
|
|
500
760
|
<div class="stat-card">
|
|
501
761
|
<h3>${status.totalMappings || 0}</h3>
|
|
@@ -510,7 +770,7 @@
|
|
|
510
770
|
<p>总连接数</p>
|
|
511
771
|
</div>
|
|
512
772
|
<div class="stat-card">
|
|
513
|
-
<h3>${
|
|
773
|
+
<h3>${uptimeStr}</h3>
|
|
514
774
|
<p>运行时间</p>
|
|
515
775
|
</div>
|
|
516
776
|
`;
|
|
@@ -519,6 +779,7 @@
|
|
|
519
779
|
// 显示添加映射模态框
|
|
520
780
|
function showAddMappingModal() {
|
|
521
781
|
document.getElementById('addMappingModal').style.display = 'block';
|
|
782
|
+
document.getElementById('mappingName').focus();
|
|
522
783
|
}
|
|
523
784
|
|
|
524
785
|
// 关闭模态框
|
|
@@ -532,7 +793,7 @@
|
|
|
532
793
|
// 添加端口映射
|
|
533
794
|
document.getElementById('addMappingForm').addEventListener('submit', async function(e) {
|
|
534
795
|
e.preventDefault();
|
|
535
|
-
|
|
796
|
+
|
|
536
797
|
const formData = new FormData(e.target);
|
|
537
798
|
const mappingData = {
|
|
538
799
|
name: formData.get('name'),
|
|
@@ -545,7 +806,7 @@
|
|
|
545
806
|
port: parseInt(formData.get('targetPort'))
|
|
546
807
|
}
|
|
547
808
|
};
|
|
548
|
-
|
|
809
|
+
|
|
549
810
|
try {
|
|
550
811
|
const response = await fetch('/api/mappings', {
|
|
551
812
|
method: 'POST',
|
|
@@ -554,13 +815,14 @@
|
|
|
554
815
|
},
|
|
555
816
|
body: JSON.stringify(mappingData)
|
|
556
817
|
});
|
|
557
|
-
|
|
818
|
+
|
|
558
819
|
const data = await response.json();
|
|
559
|
-
|
|
820
|
+
|
|
560
821
|
if (data.success) {
|
|
561
822
|
document.getElementById('modalSuccessMessage').textContent = data.message;
|
|
562
823
|
document.getElementById('modalSuccessMessage').style.display = 'block';
|
|
563
|
-
|
|
824
|
+
document.getElementById('modalErrorMessage').style.display = 'none';
|
|
825
|
+
|
|
564
826
|
setTimeout(() => {
|
|
565
827
|
closeModal();
|
|
566
828
|
loadMappings();
|
|
@@ -568,6 +830,7 @@
|
|
|
568
830
|
} else {
|
|
569
831
|
document.getElementById('modalErrorMessage').textContent = data.message;
|
|
570
832
|
document.getElementById('modalErrorMessage').style.display = 'block';
|
|
833
|
+
document.getElementById('modalSuccessMessage').style.display = 'none';
|
|
571
834
|
}
|
|
572
835
|
} catch (error) {
|
|
573
836
|
document.getElementById('modalErrorMessage').textContent = '添加失败,请检查网络连接';
|
|
@@ -639,6 +902,7 @@
|
|
|
639
902
|
// 刷新状态
|
|
640
903
|
function refreshStatus() {
|
|
641
904
|
loadStatus();
|
|
905
|
+
loadMappings();
|
|
642
906
|
}
|
|
643
907
|
|
|
644
908
|
// 刷新连接
|
|
@@ -656,13 +920,12 @@
|
|
|
656
920
|
}
|
|
657
921
|
}
|
|
658
922
|
|
|
659
|
-
//
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
if (event.target === modal) {
|
|
923
|
+
// ESC键关闭模态框
|
|
924
|
+
document.addEventListener('keydown', function(e) {
|
|
925
|
+
if (e.key === 'Escape') {
|
|
663
926
|
closeModal();
|
|
664
927
|
}
|
|
665
|
-
}
|
|
928
|
+
});
|
|
666
929
|
</script>
|
|
667
930
|
</body>
|
|
668
|
-
</html>
|
|
931
|
+
</html>
|