@chenpu17/tcpmapping 2.1.1 → 2.4.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/CHANGELOG.md +108 -0
- package/README.md +113 -10
- package/bin/tcpmapping.js +476 -176
- package/docs/API.md +532 -0
- package/docs/screenshots/dashboard-en-dark.webp +0 -0
- package/docs/screenshots/dashboard-mobile-zh.webp +0 -0
- package/docs/screenshots/dashboard-zh-light.webp +0 -0
- package/docs/screenshots/login-zh-light.webp +0 -0
- package/docs//345/212/237/350/203/275/346/200/273/347/273/223.md +200 -0
- package/docs//345/256/211/345/205/250/351/205/215/347/275/256/350/257/264/346/230/216.md +154 -0
- package/package.json +6 -4
- package/public/common.js +411 -0
- package/public/dashboard.html +1624 -437
- package/public/login.html +430 -107
- package/server.js +1219 -464
- package/setup-password.js +137 -57
package/public/login.html
CHANGED
|
@@ -4,7 +4,51 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>TCP转发服务器 - 登录</title>
|
|
7
|
+
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Crect width='24' height='24' rx='6' fill='%233b82f6'/%3E%3Cpath d='M6 12h3l2-5 3 10 2-5h2' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E">
|
|
8
|
+
<script src="/common.js"></script>
|
|
7
9
|
<style>
|
|
10
|
+
:root {
|
|
11
|
+
color-scheme: light;
|
|
12
|
+
--bg-1: #eef2ff;
|
|
13
|
+
--bg-2: #e0f2fe;
|
|
14
|
+
--surface: #ffffff;
|
|
15
|
+
--surface-muted: #f8fafc;
|
|
16
|
+
--text: #0f172a;
|
|
17
|
+
--text-muted: #64748b;
|
|
18
|
+
--text-faint: #94a3b8;
|
|
19
|
+
--border: #e2e8f0;
|
|
20
|
+
--border-strong: #cbd5e1;
|
|
21
|
+
--primary: #6366f1;
|
|
22
|
+
--primary-soft: rgba(99, 102, 241, 0.14);
|
|
23
|
+
--green: #10b981;
|
|
24
|
+
--green-soft: rgba(16, 185, 129, 0.12);
|
|
25
|
+
--red: #ef4444;
|
|
26
|
+
--red-soft: rgba(239, 68, 68, 0.12);
|
|
27
|
+
--gradient: linear-gradient(135deg, #6366f1, #8b5cf6);
|
|
28
|
+
--shadow-lg: 0 30px 80px -24px rgba(15, 23, 42, 0.35);
|
|
29
|
+
--mono: 'SFMono-Regular', ui-monospace, 'Cascadia Code', Consolas, monospace;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
[data-theme="dark"] {
|
|
33
|
+
color-scheme: dark;
|
|
34
|
+
--bg-1: #0b1120;
|
|
35
|
+
--bg-2: #111827;
|
|
36
|
+
--surface: #111827;
|
|
37
|
+
--surface-muted: #0f172a;
|
|
38
|
+
--text: #e2e8f0;
|
|
39
|
+
--text-muted: #94a3b8;
|
|
40
|
+
--text-faint: #64748b;
|
|
41
|
+
--border: #1e293b;
|
|
42
|
+
--border-strong: #334155;
|
|
43
|
+
--primary: #818cf8;
|
|
44
|
+
--primary-soft: rgba(129, 140, 248, 0.2);
|
|
45
|
+
--green: #34d399;
|
|
46
|
+
--green-soft: rgba(52, 211, 153, 0.15);
|
|
47
|
+
--red: #f87171;
|
|
48
|
+
--red-soft: rgba(248, 113, 113, 0.15);
|
|
49
|
+
--shadow-lg: 0 40px 90px -24px rgba(0, 0, 0, 0.75);
|
|
50
|
+
}
|
|
51
|
+
|
|
8
52
|
* {
|
|
9
53
|
margin: 0;
|
|
10
54
|
padding: 0;
|
|
@@ -12,29 +56,48 @@
|
|
|
12
56
|
}
|
|
13
57
|
|
|
14
58
|
body {
|
|
15
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
16
|
-
background:
|
|
59
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'PingFang SC', 'Microsoft YaHei', sans-serif;
|
|
60
|
+
background:
|
|
61
|
+
radial-gradient(700px 500px at 12% 12%, rgba(99, 102, 241, 0.28), transparent 62%),
|
|
62
|
+
radial-gradient(700px 500px at 88% 82%, rgba(14, 165, 233, 0.24), transparent 60%),
|
|
63
|
+
linear-gradient(135deg, var(--bg-1), var(--bg-2));
|
|
17
64
|
min-height: 100vh;
|
|
18
65
|
display: flex;
|
|
66
|
+
flex-direction: column;
|
|
19
67
|
align-items: center;
|
|
20
68
|
justify-content: center;
|
|
21
|
-
padding: 1rem;
|
|
69
|
+
padding: 2rem 1rem;
|
|
70
|
+
color: var(--text);
|
|
71
|
+
transition: background-color 0.25s ease, color 0.25s ease;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
svg {
|
|
75
|
+
display: block;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.page {
|
|
79
|
+
width: 100%;
|
|
80
|
+
max-width: 430px;
|
|
81
|
+
display: flex;
|
|
82
|
+
flex-direction: column;
|
|
83
|
+
align-items: center;
|
|
22
84
|
}
|
|
23
85
|
|
|
24
86
|
.login-container {
|
|
25
|
-
|
|
26
|
-
padding: 2.5rem;
|
|
27
|
-
border-radius: 20px;
|
|
28
|
-
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.25);
|
|
87
|
+
position: relative;
|
|
29
88
|
width: 100%;
|
|
30
|
-
|
|
31
|
-
|
|
89
|
+
background: var(--surface);
|
|
90
|
+
padding: 2.25rem 2rem 2rem;
|
|
91
|
+
border: 1px solid var(--border);
|
|
92
|
+
border-radius: 22px;
|
|
93
|
+
box-shadow: var(--shadow-lg);
|
|
94
|
+
animation: slideUp 0.45s ease;
|
|
32
95
|
}
|
|
33
96
|
|
|
34
97
|
@keyframes slideUp {
|
|
35
98
|
from {
|
|
36
99
|
opacity: 0;
|
|
37
|
-
transform: translateY(
|
|
100
|
+
transform: translateY(24px);
|
|
38
101
|
}
|
|
39
102
|
to {
|
|
40
103
|
opacity: 1;
|
|
@@ -42,202 +105,462 @@
|
|
|
42
105
|
}
|
|
43
106
|
}
|
|
44
107
|
|
|
108
|
+
.top-controls {
|
|
109
|
+
position: absolute;
|
|
110
|
+
top: 1rem;
|
|
111
|
+
right: 1rem;
|
|
112
|
+
display: flex;
|
|
113
|
+
align-items: center;
|
|
114
|
+
gap: 0.4rem;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.icon-btn {
|
|
118
|
+
width: 34px;
|
|
119
|
+
height: 34px;
|
|
120
|
+
display: grid;
|
|
121
|
+
place-items: center;
|
|
122
|
+
border: 1px solid var(--border);
|
|
123
|
+
border-radius: 9px;
|
|
124
|
+
background: var(--surface-muted);
|
|
125
|
+
color: var(--text-muted);
|
|
126
|
+
cursor: pointer;
|
|
127
|
+
transition: background 0.2s ease, color 0.2s ease, border-color 0.2s ease;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.icon-btn:hover {
|
|
131
|
+
background: var(--surface);
|
|
132
|
+
color: var(--text);
|
|
133
|
+
border-color: var(--border-strong);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.icon-btn:focus-visible {
|
|
137
|
+
outline: 2px solid var(--primary);
|
|
138
|
+
outline-offset: 2px;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.icon-btn svg {
|
|
142
|
+
width: 16px;
|
|
143
|
+
height: 16px;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.icon-btn .icon-sun {
|
|
147
|
+
display: none;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
[data-theme="dark"] .icon-btn .icon-moon {
|
|
151
|
+
display: none;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
[data-theme="dark"] .icon-btn .icon-sun {
|
|
155
|
+
display: block;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.select-wrap {
|
|
159
|
+
position: relative;
|
|
160
|
+
display: flex;
|
|
161
|
+
align-items: center;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.select-wrap select {
|
|
165
|
+
appearance: none;
|
|
166
|
+
-webkit-appearance: none;
|
|
167
|
+
padding: 0.45rem 1.6rem 0.45rem 0.65rem;
|
|
168
|
+
border: 1px solid var(--border);
|
|
169
|
+
border-radius: 9px;
|
|
170
|
+
background: var(--surface-muted);
|
|
171
|
+
color: var(--text);
|
|
172
|
+
font-size: 0.75rem;
|
|
173
|
+
font-weight: 500;
|
|
174
|
+
font-family: inherit;
|
|
175
|
+
cursor: pointer;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.select-wrap select:hover {
|
|
179
|
+
border-color: var(--border-strong);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.select-wrap select:focus-visible {
|
|
183
|
+
outline: 2px solid var(--primary);
|
|
184
|
+
outline-offset: 2px;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.select-wrap::after {
|
|
188
|
+
content: '';
|
|
189
|
+
position: absolute;
|
|
190
|
+
right: 0.55rem;
|
|
191
|
+
width: 5px;
|
|
192
|
+
height: 5px;
|
|
193
|
+
border-right: 1.5px solid var(--text-muted);
|
|
194
|
+
border-bottom: 1.5px solid var(--text-muted);
|
|
195
|
+
transform: rotate(45deg) translateY(-1px);
|
|
196
|
+
pointer-events: none;
|
|
197
|
+
}
|
|
198
|
+
|
|
45
199
|
.login-header {
|
|
46
200
|
text-align: center;
|
|
47
|
-
margin
|
|
201
|
+
margin: 1.5rem 0 1.75rem;
|
|
48
202
|
}
|
|
49
203
|
|
|
50
204
|
.login-header .logo {
|
|
51
|
-
width:
|
|
52
|
-
height:
|
|
53
|
-
background:
|
|
54
|
-
border-radius:
|
|
205
|
+
width: 64px;
|
|
206
|
+
height: 64px;
|
|
207
|
+
background: var(--gradient);
|
|
208
|
+
border-radius: 18px;
|
|
55
209
|
display: flex;
|
|
56
210
|
align-items: center;
|
|
57
211
|
justify-content: center;
|
|
58
|
-
margin: 0 auto 1.
|
|
59
|
-
box-shadow: 0
|
|
212
|
+
margin: 0 auto 1.1rem;
|
|
213
|
+
box-shadow: 0 16px 36px -12px rgba(99, 102, 241, 0.8);
|
|
60
214
|
}
|
|
61
215
|
|
|
62
216
|
.login-header .logo svg {
|
|
63
|
-
width:
|
|
64
|
-
height:
|
|
65
|
-
color:
|
|
217
|
+
width: 32px;
|
|
218
|
+
height: 32px;
|
|
219
|
+
color: #ffffff;
|
|
66
220
|
}
|
|
67
221
|
|
|
68
222
|
.login-header h1 {
|
|
69
|
-
color:
|
|
70
|
-
font-size:
|
|
223
|
+
color: var(--text);
|
|
224
|
+
font-size: 1.4rem;
|
|
71
225
|
font-weight: 700;
|
|
72
|
-
|
|
226
|
+
letter-spacing: -0.02em;
|
|
227
|
+
margin-bottom: 0.35rem;
|
|
73
228
|
}
|
|
74
229
|
|
|
75
230
|
.login-header p {
|
|
76
|
-
color:
|
|
77
|
-
font-size:
|
|
231
|
+
color: var(--text-muted);
|
|
232
|
+
font-size: 0.85rem;
|
|
78
233
|
}
|
|
79
234
|
|
|
80
235
|
.form-group {
|
|
81
|
-
margin-bottom: 1.
|
|
236
|
+
margin-bottom: 1.15rem;
|
|
82
237
|
}
|
|
83
238
|
|
|
84
239
|
.form-group label {
|
|
85
240
|
display: block;
|
|
86
|
-
margin-bottom: 0.
|
|
87
|
-
color:
|
|
241
|
+
margin-bottom: 0.4rem;
|
|
242
|
+
color: var(--text);
|
|
88
243
|
font-weight: 600;
|
|
89
|
-
font-size:
|
|
244
|
+
font-size: 0.8rem;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.input-wrap {
|
|
248
|
+
position: relative;
|
|
249
|
+
display: flex;
|
|
250
|
+
align-items: center;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.input-wrap > svg {
|
|
254
|
+
position: absolute;
|
|
255
|
+
left: 0.9rem;
|
|
256
|
+
width: 17px;
|
|
257
|
+
height: 17px;
|
|
258
|
+
color: var(--text-faint);
|
|
259
|
+
pointer-events: none;
|
|
90
260
|
}
|
|
91
261
|
|
|
92
262
|
.form-group input {
|
|
93
263
|
width: 100%;
|
|
94
|
-
padding: 0.
|
|
95
|
-
border:
|
|
264
|
+
padding: 0.8rem 2.6rem 0.8rem 2.6rem;
|
|
265
|
+
border: 1.5px solid var(--border);
|
|
96
266
|
border-radius: 12px;
|
|
97
|
-
font-size:
|
|
98
|
-
|
|
267
|
+
font-size: 0.9rem;
|
|
268
|
+
font-family: inherit;
|
|
269
|
+
background: var(--surface-muted);
|
|
270
|
+
color: var(--text);
|
|
271
|
+
transition: border-color 0.2s ease, box-shadow 0.2s ease, background 0.2s ease;
|
|
99
272
|
}
|
|
100
273
|
|
|
101
274
|
.form-group input:focus {
|
|
102
275
|
outline: none;
|
|
103
|
-
border-color:
|
|
104
|
-
|
|
276
|
+
border-color: var(--primary);
|
|
277
|
+
background: var(--surface);
|
|
278
|
+
box-shadow: 0 0 0 4px var(--primary-soft);
|
|
105
279
|
}
|
|
106
280
|
|
|
107
281
|
.form-group input::placeholder {
|
|
108
|
-
color:
|
|
282
|
+
color: var(--text-muted);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
.toggle-password {
|
|
286
|
+
position: absolute;
|
|
287
|
+
right: 0.55rem;
|
|
288
|
+
width: 30px;
|
|
289
|
+
height: 30px;
|
|
290
|
+
display: grid;
|
|
291
|
+
place-items: center;
|
|
292
|
+
border: none;
|
|
293
|
+
border-radius: 7px;
|
|
294
|
+
background: transparent;
|
|
295
|
+
color: var(--text-faint);
|
|
296
|
+
cursor: pointer;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.toggle-password:hover {
|
|
300
|
+
background: var(--surface-muted);
|
|
301
|
+
color: var(--text);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
.toggle-password:focus-visible {
|
|
305
|
+
outline: 2px solid var(--primary);
|
|
306
|
+
outline-offset: 1px;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
.toggle-password svg {
|
|
310
|
+
width: 16px;
|
|
311
|
+
height: 16px;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.toggle-password .icon-eye-off {
|
|
315
|
+
display: none;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.toggle-password.is-visible .icon-eye {
|
|
319
|
+
display: none;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
.toggle-password.is-visible .icon-eye-off {
|
|
323
|
+
display: block;
|
|
109
324
|
}
|
|
110
325
|
|
|
111
326
|
.login-btn {
|
|
112
327
|
width: 100%;
|
|
113
|
-
padding: 0.
|
|
114
|
-
|
|
115
|
-
|
|
328
|
+
padding: 0.85rem;
|
|
329
|
+
margin-top: 0.35rem;
|
|
330
|
+
background: var(--gradient);
|
|
331
|
+
color: #ffffff;
|
|
116
332
|
border: none;
|
|
117
333
|
border-radius: 12px;
|
|
118
|
-
font-size:
|
|
119
|
-
font-weight:
|
|
334
|
+
font-size: 0.92rem;
|
|
335
|
+
font-weight: 650;
|
|
336
|
+
font-family: inherit;
|
|
120
337
|
cursor: pointer;
|
|
121
|
-
transition:
|
|
122
|
-
box-shadow: 0
|
|
338
|
+
transition: transform 0.15s ease, box-shadow 0.2s ease, filter 0.2s ease;
|
|
339
|
+
box-shadow: 0 14px 30px -12px rgba(99, 102, 241, 0.85);
|
|
123
340
|
}
|
|
124
341
|
|
|
125
342
|
.login-btn:hover {
|
|
126
|
-
|
|
127
|
-
box-shadow: 0 15px 35px rgba(102, 126, 234, 0.5);
|
|
343
|
+
filter: brightness(1.07);
|
|
128
344
|
}
|
|
129
345
|
|
|
130
346
|
.login-btn:active {
|
|
131
|
-
transform: translateY(
|
|
347
|
+
transform: translateY(1px);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
.login-btn:focus-visible {
|
|
351
|
+
outline: 2px solid var(--primary);
|
|
352
|
+
outline-offset: 2px;
|
|
132
353
|
}
|
|
133
354
|
|
|
134
355
|
.login-btn:disabled {
|
|
135
|
-
opacity: 0.
|
|
356
|
+
opacity: 0.65;
|
|
136
357
|
cursor: not-allowed;
|
|
137
|
-
|
|
358
|
+
filter: none;
|
|
138
359
|
box-shadow: none;
|
|
139
360
|
}
|
|
140
361
|
|
|
141
|
-
.error-message
|
|
142
|
-
|
|
143
|
-
font-size:
|
|
362
|
+
.error-message,
|
|
363
|
+
.success-message {
|
|
364
|
+
font-size: 0.82rem;
|
|
144
365
|
margin-top: 1rem;
|
|
145
|
-
padding: 0.
|
|
146
|
-
|
|
147
|
-
border-radius: 10px;
|
|
366
|
+
padding: 0.7rem 0.9rem;
|
|
367
|
+
border-radius: 9px;
|
|
148
368
|
display: none;
|
|
149
369
|
text-align: center;
|
|
150
370
|
}
|
|
151
371
|
|
|
372
|
+
.error-message {
|
|
373
|
+
color: var(--red);
|
|
374
|
+
background: var(--red-soft);
|
|
375
|
+
}
|
|
376
|
+
|
|
152
377
|
.success-message {
|
|
153
|
-
color:
|
|
154
|
-
|
|
155
|
-
margin-top: 1rem;
|
|
156
|
-
padding: 0.85rem 1rem;
|
|
157
|
-
background: #f0fff4;
|
|
158
|
-
border-radius: 10px;
|
|
159
|
-
display: none;
|
|
160
|
-
text-align: center;
|
|
378
|
+
color: var(--green);
|
|
379
|
+
background: var(--green-soft);
|
|
161
380
|
}
|
|
162
381
|
|
|
163
382
|
.help-info {
|
|
164
|
-
background:
|
|
383
|
+
background: var(--surface-muted);
|
|
384
|
+
border: 1px solid var(--border);
|
|
165
385
|
border-radius: 12px;
|
|
166
|
-
padding: 1rem
|
|
167
|
-
margin-top: 1.
|
|
168
|
-
font-size:
|
|
169
|
-
color:
|
|
170
|
-
line-height: 1.
|
|
386
|
+
padding: 0.9rem 1rem;
|
|
387
|
+
margin-top: 1.4rem;
|
|
388
|
+
font-size: 0.76rem;
|
|
389
|
+
color: var(--text-muted);
|
|
390
|
+
line-height: 1.7;
|
|
171
391
|
}
|
|
172
392
|
|
|
173
393
|
.help-info code {
|
|
174
|
-
background:
|
|
175
|
-
|
|
394
|
+
background: var(--surface);
|
|
395
|
+
border: 1px solid var(--border);
|
|
396
|
+
padding: 0.1rem 0.4rem;
|
|
176
397
|
border-radius: 5px;
|
|
177
|
-
font-family:
|
|
178
|
-
font-size:
|
|
179
|
-
color:
|
|
398
|
+
font-family: var(--mono);
|
|
399
|
+
font-size: 0.72rem;
|
|
400
|
+
color: var(--primary);
|
|
401
|
+
overflow-wrap: anywhere;
|
|
180
402
|
}
|
|
181
403
|
|
|
182
404
|
.footer {
|
|
183
405
|
text-align: center;
|
|
184
|
-
margin-top:
|
|
185
|
-
color:
|
|
186
|
-
font-size:
|
|
406
|
+
margin-top: 1.5rem;
|
|
407
|
+
color: var(--text-muted);
|
|
408
|
+
font-size: 0.78rem;
|
|
187
409
|
}
|
|
188
410
|
|
|
189
411
|
.footer a {
|
|
190
|
-
color:
|
|
412
|
+
color: var(--primary);
|
|
191
413
|
text-decoration: none;
|
|
414
|
+
font-weight: 600;
|
|
192
415
|
}
|
|
193
416
|
|
|
194
417
|
.footer a:hover {
|
|
195
418
|
text-decoration: underline;
|
|
196
419
|
}
|
|
420
|
+
|
|
421
|
+
@media (prefers-reduced-motion: reduce) {
|
|
422
|
+
*,
|
|
423
|
+
*::before,
|
|
424
|
+
*::after {
|
|
425
|
+
animation-duration: 0.01ms !important;
|
|
426
|
+
transition-duration: 0.01ms !important;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
@media (max-width: 480px) {
|
|
431
|
+
.login-container {
|
|
432
|
+
padding: 1.75rem 1.25rem 1.5rem;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
.login-header {
|
|
436
|
+
margin-top: 2rem;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
197
439
|
</style>
|
|
198
440
|
</head>
|
|
199
441
|
<body>
|
|
200
|
-
<div>
|
|
442
|
+
<div class="page">
|
|
201
443
|
<div class="login-container">
|
|
444
|
+
<div class="top-controls">
|
|
445
|
+
<div class="select-wrap">
|
|
446
|
+
<select id="languageSelect" data-i18n-aria-label="common.language">
|
|
447
|
+
<option value="zh-CN">中文</option>
|
|
448
|
+
<option value="en-US">English</option>
|
|
449
|
+
</select>
|
|
450
|
+
</div>
|
|
451
|
+
<button type="button" class="icon-btn" data-theme-toggle data-i18n-title="common.theme" data-i18n-aria-label="common.theme">
|
|
452
|
+
<svg class="icon-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
453
|
+
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
|
|
454
|
+
</svg>
|
|
455
|
+
<svg class="icon-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
456
|
+
<circle cx="12" cy="12" r="4"/>
|
|
457
|
+
<path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"/>
|
|
458
|
+
</svg>
|
|
459
|
+
</button>
|
|
460
|
+
</div>
|
|
461
|
+
|
|
202
462
|
<div class="login-header">
|
|
203
463
|
<div class="logo">
|
|
204
464
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
205
465
|
<path d="M22 12h-4l-3 9L9 3l-3 9H2"/>
|
|
206
466
|
</svg>
|
|
207
467
|
</div>
|
|
208
|
-
<h1>TCP转发服务器</h1>
|
|
209
|
-
<p>管理界面登录</p>
|
|
468
|
+
<h1 data-i18n="brand.name">TCP 转发服务器</h1>
|
|
469
|
+
<p data-i18n="login.subtitle">管理界面登录</p>
|
|
210
470
|
</div>
|
|
211
471
|
|
|
212
472
|
<form id="loginForm">
|
|
213
473
|
<div class="form-group">
|
|
214
|
-
<label for="username">用户名</label>
|
|
215
|
-
<
|
|
474
|
+
<label for="username" data-i18n="login.username">用户名</label>
|
|
475
|
+
<div class="input-wrap">
|
|
476
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
477
|
+
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/>
|
|
478
|
+
<circle cx="12" cy="7" r="4"/>
|
|
479
|
+
</svg>
|
|
480
|
+
<input type="text" id="username" name="username" data-i18n-placeholder="login.usernamePlaceholder" placeholder="请输入用户名" required autocomplete="username" autofocus>
|
|
481
|
+
</div>
|
|
216
482
|
</div>
|
|
217
483
|
|
|
218
484
|
<div class="form-group">
|
|
219
|
-
<label for="password">密码</label>
|
|
220
|
-
<
|
|
485
|
+
<label for="password" data-i18n="login.password">密码</label>
|
|
486
|
+
<div class="input-wrap">
|
|
487
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
488
|
+
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"/>
|
|
489
|
+
<path d="M7 11V7a5 5 0 0 1 10 0v4"/>
|
|
490
|
+
</svg>
|
|
491
|
+
<input type="password" id="password" name="password" data-i18n-placeholder="login.passwordPlaceholder" placeholder="请输入密码" required autocomplete="current-password">
|
|
492
|
+
<button type="button" class="toggle-password" id="togglePassword" data-i18n-title="login.showPassword" data-i18n-aria-label="login.showPassword">
|
|
493
|
+
<svg class="icon-eye" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
494
|
+
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/>
|
|
495
|
+
<circle cx="12" cy="12" r="3"/>
|
|
496
|
+
</svg>
|
|
497
|
+
<svg class="icon-eye-off" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
498
|
+
<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/>
|
|
499
|
+
<line x1="1" y1="1" x2="23" y2="23"/>
|
|
500
|
+
</svg>
|
|
501
|
+
</button>
|
|
502
|
+
</div>
|
|
221
503
|
</div>
|
|
222
504
|
|
|
223
|
-
<button type="submit" class="login-btn" id="loginBtn">登录</button>
|
|
505
|
+
<button type="submit" class="login-btn" id="loginBtn" data-i18n="login.submit">登录</button>
|
|
224
506
|
|
|
225
507
|
<div class="error-message" id="errorMessage"></div>
|
|
226
508
|
<div class="success-message" id="successMessage"></div>
|
|
227
509
|
</form>
|
|
228
510
|
|
|
229
|
-
<div class="help-info">
|
|
511
|
+
<div class="help-info" data-i18n-html="login.forgot">
|
|
230
512
|
忘记密码?运行 <code>tcpmapping setup-password</code> 或 <code>npm run reset-password</code> 重置
|
|
231
513
|
</div>
|
|
232
514
|
</div>
|
|
233
515
|
|
|
234
516
|
<div class="footer">
|
|
235
|
-
<a href="https://github.com/chenpu17/TcpMapping" target="_blank">TcpMapping</a>
|
|
517
|
+
<a href="https://github.com/chenpu17/TcpMapping" target="_blank" rel="noopener noreferrer">TcpMapping</a> ·
|
|
518
|
+
<span data-i18n="login.footer">TCP 端口转发服务器</span>
|
|
236
519
|
</div>
|
|
237
520
|
</div>
|
|
238
521
|
|
|
239
522
|
<script>
|
|
240
|
-
|
|
523
|
+
function t(key, params) {
|
|
524
|
+
return window.I18n ? window.I18n.t(key, params) : key;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
let loginPending = false;
|
|
528
|
+
|
|
529
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
530
|
+
document.title = t('page.login');
|
|
531
|
+
|
|
532
|
+
const toggleButton = document.getElementById('togglePassword');
|
|
533
|
+
const passwordInput = document.getElementById('password');
|
|
534
|
+
|
|
535
|
+
toggleButton.addEventListener('click', function() {
|
|
536
|
+
const visible = passwordInput.type === 'text';
|
|
537
|
+
passwordInput.type = visible ? 'password' : 'text';
|
|
538
|
+
toggleButton.classList.toggle('is-visible', !visible);
|
|
539
|
+
|
|
540
|
+
const label = t(visible ? 'login.showPassword' : 'login.hidePassword');
|
|
541
|
+
toggleButton.setAttribute('title', label);
|
|
542
|
+
toggleButton.setAttribute('aria-label', label);
|
|
543
|
+
passwordInput.focus();
|
|
544
|
+
});
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
document.addEventListener('languagechange', function() {
|
|
548
|
+
document.title = t('page.login');
|
|
549
|
+
|
|
550
|
+
if (loginPending) {
|
|
551
|
+
document.getElementById('loginBtn').textContent = t('login.submitting');
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
const toggleButton = document.getElementById('togglePassword');
|
|
555
|
+
const passwordInput = document.getElementById('password');
|
|
556
|
+
if (toggleButton && passwordInput) {
|
|
557
|
+
const label = t(passwordInput.type === 'text' ? 'login.hidePassword' : 'login.showPassword');
|
|
558
|
+
toggleButton.setAttribute('title', label);
|
|
559
|
+
toggleButton.setAttribute('aria-label', label);
|
|
560
|
+
}
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
document.getElementById('loginForm').addEventListener('submit', async function(e) {
|
|
241
564
|
e.preventDefault();
|
|
242
565
|
|
|
243
566
|
const loginBtn = document.getElementById('loginBtn');
|
|
@@ -246,50 +569,50 @@
|
|
|
246
569
|
const username = document.getElementById('username').value;
|
|
247
570
|
const password = document.getElementById('password').value;
|
|
248
571
|
|
|
249
|
-
// 重置消息
|
|
250
572
|
errorMessage.style.display = 'none';
|
|
251
573
|
successMessage.style.display = 'none';
|
|
252
574
|
|
|
253
|
-
|
|
575
|
+
loginPending = true;
|
|
254
576
|
loginBtn.disabled = true;
|
|
255
|
-
loginBtn.textContent = '
|
|
577
|
+
loginBtn.textContent = t('login.submitting');
|
|
256
578
|
|
|
257
579
|
try {
|
|
258
580
|
const response = await fetch('/api/login', {
|
|
259
581
|
method: 'POST',
|
|
260
582
|
headers: {
|
|
261
583
|
'Content-Type': 'application/json',
|
|
584
|
+
'X-Language': window.I18n ? window.I18n.getLanguage() : 'zh-CN'
|
|
262
585
|
},
|
|
586
|
+
credentials: 'same-origin',
|
|
263
587
|
body: JSON.stringify({ username, password })
|
|
264
588
|
});
|
|
265
589
|
|
|
266
|
-
|
|
590
|
+
let data;
|
|
591
|
+
try {
|
|
592
|
+
data = await response.json();
|
|
593
|
+
} catch (error) {
|
|
594
|
+
data = { success: false, message: t('common.invalidResponse') };
|
|
595
|
+
}
|
|
267
596
|
|
|
268
597
|
if (data.success) {
|
|
269
|
-
successMessage.textContent = '
|
|
598
|
+
successMessage.textContent = t('login.success');
|
|
270
599
|
successMessage.style.display = 'block';
|
|
271
600
|
|
|
272
601
|
setTimeout(() => {
|
|
273
602
|
window.location.href = '/dashboard';
|
|
274
|
-
},
|
|
275
|
-
|
|
276
|
-
errorMessage.textContent = data.message;
|
|
277
|
-
errorMessage.style.display = 'block';
|
|
603
|
+
}, 700);
|
|
604
|
+
return;
|
|
278
605
|
}
|
|
606
|
+
|
|
607
|
+
errorMessage.textContent = data.message || t('login.failedNetwork');
|
|
608
|
+
errorMessage.style.display = 'block';
|
|
279
609
|
} catch (error) {
|
|
280
|
-
errorMessage.textContent = '
|
|
610
|
+
errorMessage.textContent = t('login.failedNetwork');
|
|
281
611
|
errorMessage.style.display = 'block';
|
|
282
612
|
} finally {
|
|
283
|
-
|
|
613
|
+
loginPending = false;
|
|
284
614
|
loginBtn.disabled = false;
|
|
285
|
-
loginBtn.textContent = '
|
|
286
|
-
}
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
// 回车提交
|
|
290
|
-
document.addEventListener('keypress', (e) => {
|
|
291
|
-
if (e.key === 'Enter') {
|
|
292
|
-
document.getElementById('loginForm').dispatchEvent(new Event('submit'));
|
|
615
|
+
loginBtn.textContent = t('login.submit');
|
|
293
616
|
}
|
|
294
617
|
});
|
|
295
618
|
</script>
|