@wenbin_wb/dsh-bridge 2.10.2 → 2.10.4
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 +45 -0
- package/README.en.md +2 -0
- package/README.md +2 -0
- package/client/client.js +86 -14
- package/client/index.js +58 -14
- package/client/mobile-styles.js +834 -802
- package/lib/auth/login-template.js +417 -382
- package/lib/bridge-rpc.js +2 -2
- package/lib/feishu/node.js +10 -4
- package/lib/index.js +109 -15
- package/lib/platform/conversation-bridge.js +8 -3
- package/lib/platform/message-split.js +39 -1
- package/lib/qq/node.js +539 -532
- package/lib/session-strip.js +67 -0
- package/lib/telegram/gateway.js +28 -26
- package/lib/telegram/node.js +0 -27
- package/lib/tunnel-client.mjs +130 -35
- package/lib/wechat/gateway.js +17 -4
- package/package.json +2 -2
|
@@ -1,382 +1,417 @@
|
|
|
1
|
-
// lib/auth/login-template.js
|
|
2
|
-
// DeepSeek Harness 官方质感高颜值远程访问认证登录页(零外部 CDN 依赖,响应式适配手机与桌面)
|
|
3
|
-
|
|
4
|
-
export function renderLoginPage({ error = '', hasPassword = true, locked = false } = {}) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
.
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
border:
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
font-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
background:
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
.submit-btn
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
.
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
</div>
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
<
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
1
|
+
// lib/auth/login-template.js
|
|
2
|
+
// DeepSeek Harness 官方质感高颜值远程访问认证登录页(零外部 CDN 依赖,响应式适配手机与桌面)
|
|
3
|
+
|
|
4
|
+
export function renderLoginPage({ error = '', hasPassword = true, locked = false, noPasswordConfigured = false, mode = 'token_and_password' } = {}) {
|
|
5
|
+
// 管理员从未设置任何密码时的如实提示:
|
|
6
|
+
// - password_only 模式:门禁真实存在但管理员未初始化密码,登录页应明确告知,
|
|
7
|
+
// 而不是让访客试一个永远不对的密码。
|
|
8
|
+
// - token_and_password(默认):认证已开启但无任何密码可校验,密码登录对任意
|
|
9
|
+
// 输入都放行(宽松降级),如实提示"免密开放"避免假门禁误导。
|
|
10
|
+
// - token_only:仅专属 Token 可进,与密码无关,提示走二维码 / Token 链接。
|
|
11
|
+
let noPasswordNotice = '';
|
|
12
|
+
let buttonLabel = '解锁并访问';
|
|
13
|
+
if (noPasswordConfigured) {
|
|
14
|
+
if (mode === 'password_only') {
|
|
15
|
+
noPasswordNotice = '管理员尚未设置访问密码(仅密码模式),请联系管理员在控制台「安全认证」中设置密码后再访问。';
|
|
16
|
+
buttonLabel = '无法访问(未设置密码)';
|
|
17
|
+
} else if (mode === 'token_only') {
|
|
18
|
+
noPasswordNotice = '当前为「仅安全 Token」模式,请使用管理员分享的专属二维码或带 Token 的链接访问(不支持密码登录)。';
|
|
19
|
+
buttonLabel = '需专属 Token';
|
|
20
|
+
} else {
|
|
21
|
+
noPasswordNotice = '当前访问认证已开启,但管理员尚未设置任何访问密码 —— 免密开放状态,任何访客均可直接进入。请管理员尽快在控制台「安全认证」中设置访问密码。';
|
|
22
|
+
buttonLabel = '直接进入';
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const heading = noPasswordConfigured ? '远程访问 · 尚未设置密码' : '远程安全访问认证';
|
|
26
|
+
const inputPlaceholder = hasPassword
|
|
27
|
+
? '请输入管理员设置的访问密码'
|
|
28
|
+
: (noPasswordConfigured ? '(尚未设置密码)' : '请输入访问密码');
|
|
29
|
+
return `<!DOCTYPE html>
|
|
30
|
+
<html lang="zh-CN">
|
|
31
|
+
<head>
|
|
32
|
+
<meta charset="UTF-8">
|
|
33
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
|
34
|
+
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
35
|
+
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
|
36
|
+
<meta name="mobile-web-app-capable" content="yes">
|
|
37
|
+
<meta name="theme-color" content="#121413">
|
|
38
|
+
<title>远程访问认证 - DeepSeek Harness</title>
|
|
39
|
+
<style>
|
|
40
|
+
:root {
|
|
41
|
+
--bg: #121413;
|
|
42
|
+
--card-bg: rgba(26, 29, 27, 0.85);
|
|
43
|
+
--card-border: rgba(255, 255, 255, 0.08);
|
|
44
|
+
--brand: #22c55e;
|
|
45
|
+
--brand-hover: #16a34a;
|
|
46
|
+
--brand-glow: rgba(34, 197, 94, 0.25);
|
|
47
|
+
--text: #f3f4f6;
|
|
48
|
+
--text-secondary: #9ca3af;
|
|
49
|
+
--input-bg: rgba(18, 20, 19, 0.7);
|
|
50
|
+
--input-border: rgba(255, 255, 255, 0.12);
|
|
51
|
+
--error-bg: rgba(239, 68, 68, 0.12);
|
|
52
|
+
--error-border: rgba(239, 68, 68, 0.3);
|
|
53
|
+
--error-text: #f87171;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
* {
|
|
57
|
+
box-sizing: border-box;
|
|
58
|
+
margin: 0;
|
|
59
|
+
padding: 0;
|
|
60
|
+
-webkit-tap-highlight-color: transparent;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
body {
|
|
64
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
|
|
65
|
+
background-color: var(--bg);
|
|
66
|
+
background-image:
|
|
67
|
+
radial-gradient(circle at 50% 0%, rgba(34, 197, 94, 0.12) 0%, transparent 50%),
|
|
68
|
+
radial-gradient(circle at 100% 100%, rgba(16, 185, 129, 0.05) 0%, transparent 40%);
|
|
69
|
+
color: var(--text);
|
|
70
|
+
min-height: 100vh;
|
|
71
|
+
display: flex;
|
|
72
|
+
align-items: center;
|
|
73
|
+
justify-content: center;
|
|
74
|
+
padding: 20px;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.container {
|
|
78
|
+
width: 100%;
|
|
79
|
+
max-width: 400px;
|
|
80
|
+
perspective: 1000px;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.card {
|
|
84
|
+
background: var(--card-bg);
|
|
85
|
+
backdrop-filter: blur(20px);
|
|
86
|
+
-webkit-backdrop-filter: blur(20px);
|
|
87
|
+
border: 1px solid var(--card-border);
|
|
88
|
+
border-radius: 20px;
|
|
89
|
+
padding: 36px 28px;
|
|
90
|
+
box-shadow:
|
|
91
|
+
0 20px 40px -15px rgba(0, 0, 0, 0.5),
|
|
92
|
+
0 0 0 1px rgba(255, 255, 255, 0.05);
|
|
93
|
+
animation: fadeIn 0.4s cubic-bezier(0.16, 1, 0.3, 1);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
@keyframes fadeIn {
|
|
97
|
+
from { opacity: 0; transform: translateY(16px) scale(0.98); }
|
|
98
|
+
to { opacity: 1; transform: translateY(0) scale(1); }
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.logo-area {
|
|
102
|
+
text-align: center;
|
|
103
|
+
margin-bottom: 28px;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.logo-icon {
|
|
107
|
+
width: 56px;
|
|
108
|
+
height: 56px;
|
|
109
|
+
margin: 0 auto 16px;
|
|
110
|
+
border-radius: 16px;
|
|
111
|
+
background: linear-gradient(135deg, rgba(34, 197, 94, 0.2), rgba(16, 185, 129, 0.05));
|
|
112
|
+
border: 1px solid rgba(34, 197, 94, 0.3);
|
|
113
|
+
display: flex;
|
|
114
|
+
align-items: center;
|
|
115
|
+
justify-content: center;
|
|
116
|
+
box-shadow: 0 8px 16px -4px var(--brand-glow);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.logo-icon svg {
|
|
120
|
+
width: 32px;
|
|
121
|
+
height: 32px;
|
|
122
|
+
fill: var(--brand);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
h1 {
|
|
126
|
+
font-size: 20px;
|
|
127
|
+
font-weight: 600;
|
|
128
|
+
letter-spacing: -0.01em;
|
|
129
|
+
color: #ffffff;
|
|
130
|
+
margin-bottom: 6px;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.subtitle {
|
|
134
|
+
font-size: 13px;
|
|
135
|
+
color: var(--text-secondary);
|
|
136
|
+
line-height: 1.5;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.alert {
|
|
140
|
+
background: var(--error-bg);
|
|
141
|
+
border: 1px solid var(--error-border);
|
|
142
|
+
color: var(--error-text);
|
|
143
|
+
padding: 10px 14px;
|
|
144
|
+
border-radius: 10px;
|
|
145
|
+
font-size: 13px;
|
|
146
|
+
margin-bottom: 20px;
|
|
147
|
+
display: flex;
|
|
148
|
+
align-items: center;
|
|
149
|
+
gap: 8px;
|
|
150
|
+
animation: shake 0.4s ease;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
@keyframes shake {
|
|
154
|
+
0%, 100% { transform: translateX(0); }
|
|
155
|
+
20%, 60% { transform: translateX(-6px); }
|
|
156
|
+
40%, 80% { transform: translateX(6px); }
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.form-group {
|
|
160
|
+
margin-bottom: 20px;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
label {
|
|
164
|
+
display: block;
|
|
165
|
+
font-size: 12px;
|
|
166
|
+
font-weight: 500;
|
|
167
|
+
color: var(--text-secondary);
|
|
168
|
+
margin-bottom: 8px;
|
|
169
|
+
text-transform: uppercase;
|
|
170
|
+
letter-spacing: 0.05em;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.input-wrapper {
|
|
174
|
+
position: relative;
|
|
175
|
+
display: flex;
|
|
176
|
+
align-items: center;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
input[type="password"], input[type="text"] {
|
|
180
|
+
width: 100%;
|
|
181
|
+
background: var(--input-bg);
|
|
182
|
+
border: 1px solid var(--input-border);
|
|
183
|
+
border-radius: 12px;
|
|
184
|
+
padding: 14px 44px 14px 16px;
|
|
185
|
+
font-size: 15px;
|
|
186
|
+
color: #ffffff;
|
|
187
|
+
outline: none;
|
|
188
|
+
transition: all 0.2s ease;
|
|
189
|
+
font-family: inherit;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
input:focus {
|
|
193
|
+
border-color: var(--brand);
|
|
194
|
+
box-shadow: 0 0 0 3px var(--brand-glow);
|
|
195
|
+
background: rgba(18, 20, 19, 0.9);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.toggle-pwd {
|
|
199
|
+
position: absolute;
|
|
200
|
+
right: 12px;
|
|
201
|
+
background: none;
|
|
202
|
+
border: none;
|
|
203
|
+
cursor: pointer;
|
|
204
|
+
color: var(--text-secondary);
|
|
205
|
+
padding: 6px;
|
|
206
|
+
display: flex;
|
|
207
|
+
align-items: center;
|
|
208
|
+
justify-content: center;
|
|
209
|
+
border-radius: 6px;
|
|
210
|
+
transition: color 0.2s;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.toggle-pwd:hover {
|
|
214
|
+
color: #ffffff;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.submit-btn {
|
|
218
|
+
width: 100%;
|
|
219
|
+
background: var(--brand);
|
|
220
|
+
color: #0b150f;
|
|
221
|
+
border: none;
|
|
222
|
+
border-radius: 12px;
|
|
223
|
+
padding: 14px;
|
|
224
|
+
font-size: 15px;
|
|
225
|
+
font-weight: 600;
|
|
226
|
+
cursor: pointer;
|
|
227
|
+
display: flex;
|
|
228
|
+
align-items: center;
|
|
229
|
+
justify-content: center;
|
|
230
|
+
gap: 8px;
|
|
231
|
+
transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1);
|
|
232
|
+
box-shadow: 0 4px 12px var(--brand-glow);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.submit-btn:hover {
|
|
236
|
+
background: var(--brand-hover);
|
|
237
|
+
transform: translateY(-1px);
|
|
238
|
+
box-shadow: 0 6px 16px rgba(34, 197, 94, 0.35);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.submit-btn:active {
|
|
242
|
+
transform: translateY(1px);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.submit-btn:disabled {
|
|
246
|
+
opacity: 0.6;
|
|
247
|
+
cursor: not-allowed;
|
|
248
|
+
transform: none;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.spinner {
|
|
252
|
+
width: 18px;
|
|
253
|
+
height: 18px;
|
|
254
|
+
border: 2px solid rgba(11, 21, 15, 0.3);
|
|
255
|
+
border-top-color: #0b150f;
|
|
256
|
+
border-radius: 50%;
|
|
257
|
+
animation: spin 0.6s linear infinite;
|
|
258
|
+
display: none;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
@keyframes spin {
|
|
262
|
+
to { transform: rotate(360deg); }
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
.footer {
|
|
266
|
+
text-align: center;
|
|
267
|
+
margin-top: 24px;
|
|
268
|
+
font-size: 12px;
|
|
269
|
+
color: rgba(255, 255, 255, 0.35);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.footer a {
|
|
273
|
+
color: var(--text-secondary);
|
|
274
|
+
text-decoration: none;
|
|
275
|
+
}
|
|
276
|
+
</style>
|
|
277
|
+
</head>
|
|
278
|
+
<body>
|
|
279
|
+
<div class="container">
|
|
280
|
+
<div class="card">
|
|
281
|
+
<div class="logo-area">
|
|
282
|
+
<div class="logo-icon">
|
|
283
|
+
<svg viewBox="0 0 24 24">
|
|
284
|
+
<path d="M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4zm0 10.99h7c-.53 4.12-3.28 7.79-7 8.94V12H5V6.3l7-3.11v8.8z"/>
|
|
285
|
+
</svg>
|
|
286
|
+
</div>
|
|
287
|
+
<h1>DeepSeek Harness</h1>
|
|
288
|
+
<div class="subtitle">${heading}</div>
|
|
289
|
+
</div>
|
|
290
|
+
|
|
291
|
+
${noPasswordNotice ? `
|
|
292
|
+
<div id="noPwdNotice" style="background: rgba(245,158,11,0.12); border: 1px solid rgba(245,158,11,0.35); color: #fbbf24; padding: 10px 14px; border-radius: 10px; font-size: 13px; line-height: 1.6; margin-bottom: 16px; display: flex; align-items: flex-start; gap: 8px;">
|
|
293
|
+
<span style="flex-shrink:0;">⚠️</span>
|
|
294
|
+
<span>${noPasswordNotice}</span>
|
|
295
|
+
</div>` : ''}
|
|
296
|
+
|
|
297
|
+
<div id="errorAlert" class="alert" style="${error ? '' : 'display: none;'}">
|
|
298
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
299
|
+
<circle cx="12" cy="12" r="10"></circle>
|
|
300
|
+
<line x1="12" y1="8" x2="12" y2="12"></line>
|
|
301
|
+
<line x1="12" y1="16" x2="12.01" y2="16"></line>
|
|
302
|
+
</svg>
|
|
303
|
+
<span id="errorMsg">${error || ''}</span>
|
|
304
|
+
</div>
|
|
305
|
+
|
|
306
|
+
<form id="loginForm" onsubmit="return handleLogin(event)">
|
|
307
|
+
<div class="form-group">
|
|
308
|
+
<label for="password">访问密码 / PIN 码</label>
|
|
309
|
+
<div class="input-wrapper">
|
|
310
|
+
<input type="password" id="password" name="password" placeholder="${inputPlaceholder}" autocomplete="current-password" autofocus ${hasPassword ? 'required' : ''}>
|
|
311
|
+
<button type="button" class="toggle-pwd" onclick="togglePassword()" title="显示/隐藏密码">
|
|
312
|
+
<svg id="eyeIcon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
313
|
+
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
|
|
314
|
+
<circle cx="12" cy="12" r="3"></circle>
|
|
315
|
+
</svg>
|
|
316
|
+
</button>
|
|
317
|
+
</div>
|
|
318
|
+
</div>
|
|
319
|
+
|
|
320
|
+
<button type="submit" id="submitBtn" class="submit-btn">
|
|
321
|
+
<span class="spinner" id="btnSpinner"></span>
|
|
322
|
+
<span id="btnText">${buttonLabel}</span>
|
|
323
|
+
</button>
|
|
324
|
+
</form>
|
|
325
|
+
|
|
326
|
+
${!hasPassword && noPasswordConfigured && mode !== 'password_only' ? `
|
|
327
|
+
<div style="margin-top: 12px; text-align: center;">
|
|
328
|
+
<span style="font-size: 12px; color: rgba(255,255,255,0.45);">${mode === 'token_only' ? '仅凭专属 Token 进入 · 无 Token 将无法访问' : '当前为免密开放状态 · 请管理员尽快在控制台设置访问密码'}</span>
|
|
329
|
+
</div>` : ''}
|
|
330
|
+
|
|
331
|
+
<div style="margin-top: 16px; text-align: center;">
|
|
332
|
+
<a href="javascript:void(0)" onclick="toggleForgotGuide()" style="font-size: 12px; color: var(--text-secondary); text-decoration: none; display: inline-flex; align-items: center; gap: 4px; transition: color 0.2s;">
|
|
333
|
+
❓ 忘记密码怎么办?
|
|
334
|
+
</a>
|
|
335
|
+
</div>
|
|
336
|
+
|
|
337
|
+
<div id="forgotGuide" style="display: none; margin-top: 14px; padding: 12px 14px; background: rgba(255,255,255,0.04); border: 1px solid rgba(255,255,255,0.08); border-radius: 10px; font-size: 12px; line-height: 1.6; color: var(--text-secondary); text-align: left;">
|
|
338
|
+
<div style="font-weight: 600; color: #f3f4f6; margin-bottom: 4px;">🛟 找回与重置密码指引:</div>
|
|
339
|
+
<div>1. <strong>电脑本机免密直连</strong>:在运行本程序的电脑本机打开本控制台,物理机享有永久免密特权,可直接修改或清除密码。</div>
|
|
340
|
+
<div style="margin-top: 4px;">2. <strong>服务器 / 无头环境</strong>:救急重置步骤请参见 <a href="https://github.com/wenbin-wb/dsh-bridge#readme" target="_blank" style="color: #93c5fd;">GitHub README</a> 的「三重容灾保命体系」章节。</div>
|
|
341
|
+
</div>
|
|
342
|
+
|
|
343
|
+
<div class="footer">
|
|
344
|
+
由 <a href="https://github.com/wenbin-wb/dsh-bridge" target="_blank">dsh-bridge</a> 安全网关守护
|
|
345
|
+
</div>
|
|
346
|
+
</div>
|
|
347
|
+
</div>
|
|
348
|
+
|
|
349
|
+
<script>
|
|
350
|
+
function toggleForgotGuide() {
|
|
351
|
+
const g = document.getElementById('forgotGuide');
|
|
352
|
+
g.style.display = g.style.display === 'none' ? 'block' : 'none';
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function togglePassword() {
|
|
356
|
+
const input = document.getElementById('password');
|
|
357
|
+
const icon = document.getElementById('eyeIcon');
|
|
358
|
+
if (input.type === 'password') {
|
|
359
|
+
input.type = 'text';
|
|
360
|
+
icon.innerHTML = '<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"></path><line x1="1" y1="1" x2="23" y2="23"></line>';
|
|
361
|
+
} else {
|
|
362
|
+
input.type = 'password';
|
|
363
|
+
icon.innerHTML = '<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path><circle cx="12" cy="12" r="3"></circle>';
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
async function handleLogin(e) {
|
|
368
|
+
e.preventDefault();
|
|
369
|
+
const pwd = document.getElementById('password').value;
|
|
370
|
+
const btn = document.getElementById('submitBtn');
|
|
371
|
+
const spinner = document.getElementById('btnSpinner');
|
|
372
|
+
const btnText = document.getElementById('btnText');
|
|
373
|
+
const alert = document.getElementById('errorAlert');
|
|
374
|
+
const errorMsg = document.getElementById('errorMsg');
|
|
375
|
+
|
|
376
|
+
btn.disabled = true;
|
|
377
|
+
spinner.style.display = 'inline-block';
|
|
378
|
+
btnText.textContent = '验证中…';
|
|
379
|
+
alert.style.display = 'none';
|
|
380
|
+
|
|
381
|
+
try {
|
|
382
|
+
const resp = await fetch('/__dsh_bridge__/login', {
|
|
383
|
+
method: 'POST',
|
|
384
|
+
headers: { 'Content-Type': 'application/json' },
|
|
385
|
+
body: JSON.stringify({ password: pwd })
|
|
386
|
+
});
|
|
387
|
+
const data = await resp.json();
|
|
388
|
+
if (data.ok) {
|
|
389
|
+
btnText.textContent = '✓ 认证成功,正在跳转…';
|
|
390
|
+
setTimeout(() => {
|
|
391
|
+
window.location.reload();
|
|
392
|
+
}, 300);
|
|
393
|
+
} else {
|
|
394
|
+
errorMsg.textContent = data.error || '访问密码错误';
|
|
395
|
+
alert.style.display = 'flex';
|
|
396
|
+
alert.style.animation = 'none';
|
|
397
|
+
void alert.offsetWidth; // 触发 reflow
|
|
398
|
+
alert.style.animation = 'shake 0.4s ease';
|
|
399
|
+
btn.disabled = false;
|
|
400
|
+
spinner.style.display = 'none';
|
|
401
|
+
btnText.textContent = '解锁并访问';
|
|
402
|
+
document.getElementById('password').focus();
|
|
403
|
+
document.getElementById('password').select();
|
|
404
|
+
}
|
|
405
|
+
} catch (err) {
|
|
406
|
+
errorMsg.textContent = '网络请求失败,请重试';
|
|
407
|
+
alert.style.display = 'flex';
|
|
408
|
+
btn.disabled = false;
|
|
409
|
+
spinner.style.display = 'none';
|
|
410
|
+
btnText.textContent = '解锁并访问';
|
|
411
|
+
}
|
|
412
|
+
return false;
|
|
413
|
+
}
|
|
414
|
+
</script>
|
|
415
|
+
</body>
|
|
416
|
+
</html>`;
|
|
417
|
+
}
|