@wenbin_wb/dsh-bridge 2.4.0 → 2.5.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,378 @@
1
+ // lib/auth/login-template.js
2
+ // DeepSeek Harness 官方质感高颜值远程访问认证登录页(零外部 CDN 依赖,响应式适配手机与桌面)
3
+
4
+ export function renderLoginPage({ error = '', hasPassword = true, locked = false } = {}) {
5
+ return `<!DOCTYPE html>
6
+ <html lang="zh-CN">
7
+ <head>
8
+ <meta charset="UTF-8">
9
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
10
+ <title>远程访问认证 - DeepSeek Harness</title>
11
+ <style>
12
+ :root {
13
+ --bg: #121413;
14
+ --card-bg: rgba(26, 29, 27, 0.85);
15
+ --card-border: rgba(255, 255, 255, 0.08);
16
+ --brand: #22c55e;
17
+ --brand-hover: #16a34a;
18
+ --brand-glow: rgba(34, 197, 94, 0.25);
19
+ --text: #f3f4f6;
20
+ --text-secondary: #9ca3af;
21
+ --input-bg: rgba(18, 20, 19, 0.7);
22
+ --input-border: rgba(255, 255, 255, 0.12);
23
+ --error-bg: rgba(239, 68, 68, 0.12);
24
+ --error-border: rgba(239, 68, 68, 0.3);
25
+ --error-text: #f87171;
26
+ }
27
+
28
+ * {
29
+ box-sizing: border-box;
30
+ margin: 0;
31
+ padding: 0;
32
+ -webkit-tap-highlight-color: transparent;
33
+ }
34
+
35
+ body {
36
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
37
+ background-color: var(--bg);
38
+ background-image:
39
+ radial-gradient(circle at 50% 0%, rgba(34, 197, 94, 0.12) 0%, transparent 50%),
40
+ radial-gradient(circle at 100% 100%, rgba(16, 185, 129, 0.05) 0%, transparent 40%);
41
+ color: var(--text);
42
+ min-height: 100vh;
43
+ display: flex;
44
+ align-items: center;
45
+ justify-content: center;
46
+ padding: 20px;
47
+ }
48
+
49
+ .container {
50
+ width: 100%;
51
+ max-width: 400px;
52
+ perspective: 1000px;
53
+ }
54
+
55
+ .card {
56
+ background: var(--card-bg);
57
+ backdrop-filter: blur(20px);
58
+ -webkit-backdrop-filter: blur(20px);
59
+ border: 1px solid var(--card-border);
60
+ border-radius: 20px;
61
+ padding: 36px 28px;
62
+ box-shadow:
63
+ 0 20px 40px -15px rgba(0, 0, 0, 0.5),
64
+ 0 0 0 1px rgba(255, 255, 255, 0.05);
65
+ animation: fadeIn 0.4s cubic-bezier(0.16, 1, 0.3, 1);
66
+ }
67
+
68
+ @keyframes fadeIn {
69
+ from { opacity: 0; transform: translateY(16px) scale(0.98); }
70
+ to { opacity: 1; transform: translateY(0) scale(1); }
71
+ }
72
+
73
+ .logo-area {
74
+ text-align: center;
75
+ margin-bottom: 28px;
76
+ }
77
+
78
+ .logo-icon {
79
+ width: 56px;
80
+ height: 56px;
81
+ margin: 0 auto 16px;
82
+ border-radius: 16px;
83
+ background: linear-gradient(135deg, rgba(34, 197, 94, 0.2), rgba(16, 185, 129, 0.05));
84
+ border: 1px solid rgba(34, 197, 94, 0.3);
85
+ display: flex;
86
+ align-items: center;
87
+ justify-content: center;
88
+ box-shadow: 0 8px 16px -4px var(--brand-glow);
89
+ }
90
+
91
+ .logo-icon svg {
92
+ width: 32px;
93
+ height: 32px;
94
+ fill: var(--brand);
95
+ }
96
+
97
+ h1 {
98
+ font-size: 20px;
99
+ font-weight: 600;
100
+ letter-spacing: -0.01em;
101
+ color: #ffffff;
102
+ margin-bottom: 6px;
103
+ }
104
+
105
+ .subtitle {
106
+ font-size: 13px;
107
+ color: var(--text-secondary);
108
+ line-height: 1.5;
109
+ }
110
+
111
+ .alert {
112
+ background: var(--error-bg);
113
+ border: 1px solid var(--error-border);
114
+ color: var(--error-text);
115
+ padding: 10px 14px;
116
+ border-radius: 10px;
117
+ font-size: 13px;
118
+ margin-bottom: 20px;
119
+ display: flex;
120
+ align-items: center;
121
+ gap: 8px;
122
+ animation: shake 0.4s ease;
123
+ }
124
+
125
+ @keyframes shake {
126
+ 0%, 100% { transform: translateX(0); }
127
+ 20%, 60% { transform: translateX(-6px); }
128
+ 40%, 80% { transform: translateX(6px); }
129
+ }
130
+
131
+ .form-group {
132
+ margin-bottom: 20px;
133
+ }
134
+
135
+ label {
136
+ display: block;
137
+ font-size: 12px;
138
+ font-weight: 500;
139
+ color: var(--text-secondary);
140
+ margin-bottom: 8px;
141
+ text-transform: uppercase;
142
+ letter-spacing: 0.05em;
143
+ }
144
+
145
+ .input-wrapper {
146
+ position: relative;
147
+ display: flex;
148
+ align-items: center;
149
+ }
150
+
151
+ input[type="password"], input[type="text"] {
152
+ width: 100%;
153
+ background: var(--input-bg);
154
+ border: 1px solid var(--input-border);
155
+ border-radius: 12px;
156
+ padding: 14px 44px 14px 16px;
157
+ font-size: 15px;
158
+ color: #ffffff;
159
+ outline: none;
160
+ transition: all 0.2s ease;
161
+ font-family: inherit;
162
+ }
163
+
164
+ input:focus {
165
+ border-color: var(--brand);
166
+ box-shadow: 0 0 0 3px var(--brand-glow);
167
+ background: rgba(18, 20, 19, 0.9);
168
+ }
169
+
170
+ .toggle-pwd {
171
+ position: absolute;
172
+ right: 12px;
173
+ background: none;
174
+ border: none;
175
+ cursor: pointer;
176
+ color: var(--text-secondary);
177
+ padding: 6px;
178
+ display: flex;
179
+ align-items: center;
180
+ justify-content: center;
181
+ border-radius: 6px;
182
+ transition: color 0.2s;
183
+ }
184
+
185
+ .toggle-pwd:hover {
186
+ color: #ffffff;
187
+ }
188
+
189
+ .submit-btn {
190
+ width: 100%;
191
+ background: var(--brand);
192
+ color: #0b150f;
193
+ border: none;
194
+ border-radius: 12px;
195
+ padding: 14px;
196
+ font-size: 15px;
197
+ font-weight: 600;
198
+ cursor: pointer;
199
+ display: flex;
200
+ align-items: center;
201
+ justify-content: center;
202
+ gap: 8px;
203
+ transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1);
204
+ box-shadow: 0 4px 12px var(--brand-glow);
205
+ }
206
+
207
+ .submit-btn:hover {
208
+ background: var(--brand-hover);
209
+ transform: translateY(-1px);
210
+ box-shadow: 0 6px 16px rgba(34, 197, 94, 0.35);
211
+ }
212
+
213
+ .submit-btn:active {
214
+ transform: translateY(1px);
215
+ }
216
+
217
+ .submit-btn:disabled {
218
+ opacity: 0.6;
219
+ cursor: not-allowed;
220
+ transform: none;
221
+ }
222
+
223
+ .spinner {
224
+ width: 18px;
225
+ height: 18px;
226
+ border: 2px solid rgba(11, 21, 15, 0.3);
227
+ border-top-color: #0b150f;
228
+ border-radius: 50%;
229
+ animation: spin 0.6s linear infinite;
230
+ display: none;
231
+ }
232
+
233
+ @keyframes spin {
234
+ to { transform: rotate(360deg); }
235
+ }
236
+
237
+ .footer {
238
+ text-align: center;
239
+ margin-top: 24px;
240
+ font-size: 12px;
241
+ color: rgba(255, 255, 255, 0.35);
242
+ }
243
+
244
+ .footer a {
245
+ color: var(--text-secondary);
246
+ text-decoration: none;
247
+ }
248
+ </style>
249
+ </head>
250
+ <body>
251
+ <div class="container">
252
+ <div class="card">
253
+ <div class="logo-area">
254
+ <div class="logo-icon">
255
+ <svg viewBox="0 0 24 24">
256
+ <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"/>
257
+ </svg>
258
+ </div>
259
+ <h1>DeepSeek Harness</h1>
260
+ <div class="subtitle">远程安全访问认证</div>
261
+ </div>
262
+
263
+ <div id="errorAlert" class="alert" style="${error ? '' : 'display: none;'}">
264
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
265
+ <circle cx="12" cy="12" r="10"></circle>
266
+ <line x1="12" y1="8" x2="12" y2="12"></line>
267
+ <line x1="12" y1="16" x2="12.01" y2="16"></line>
268
+ </svg>
269
+ <span id="errorMsg">${error || ''}</span>
270
+ </div>
271
+
272
+ <form id="loginForm" onsubmit="return handleLogin(event)">
273
+ <div class="form-group">
274
+ <label for="password">访问密码 / PIN 码</label>
275
+ <div class="input-wrapper">
276
+ <input type="password" id="password" name="password" placeholder="请输入管理员设置的访问密码" autocomplete="current-password" autofocus required>
277
+ <button type="button" class="toggle-pwd" onclick="togglePassword()" title="显示/隐藏密码">
278
+ <svg id="eyeIcon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
279
+ <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
280
+ <circle cx="12" cy="12" r="3"></circle>
281
+ </svg>
282
+ </button>
283
+ </div>
284
+ </div>
285
+
286
+ <button type="submit" id="submitBtn" class="submit-btn">
287
+ <span class="spinner" id="btnSpinner"></span>
288
+ <span id="btnText">解锁并访问</span>
289
+ </button>
290
+ </form>
291
+
292
+ <div style="margin-top: 16px; text-align: center;">
293
+ <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;">
294
+ ❓ 忘记密码怎么办?
295
+ </a>
296
+ </div>
297
+
298
+ <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;">
299
+ <div style="font-weight: 600; color: #f3f4f6; margin-bottom: 4px;">🛟 找回与重置密码指引:</div>
300
+ <div>1. <strong>电脑本机免密直连</strong>:在运行本程序的电脑本机打开本控制台,物理机享有永久免密特权,可直接修改或清除密码。</div>
301
+ <div style="margin-top: 4px;">2. <strong>服务器救急指令</strong>:在宿主电脑终端执行 <code style="background: rgba(0,0,0,0.4); padding: 2px 6px; border-radius: 4px; color: #34d399; font-family: monospace;">touch ~/.dsh/dsh-bridge/reset-auth</code> 即可瞬间恢复初始免密状态。</div>
302
+ </div>
303
+
304
+ <div class="footer">
305
+ 由 <a href="https://github.com/wenbin-wb/dsh-bridge" target="_blank">dsh-bridge</a> 安全网关守护
306
+ </div>
307
+ </div>
308
+ </div>
309
+
310
+ <script>
311
+ function toggleForgotGuide() {
312
+ const g = document.getElementById('forgotGuide');
313
+ g.style.display = g.style.display === 'none' ? 'block' : 'none';
314
+ }
315
+
316
+ function togglePassword() {
317
+ const input = document.getElementById('password');
318
+ const icon = document.getElementById('eyeIcon');
319
+ if (input.type === 'password') {
320
+ input.type = 'text';
321
+ 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>';
322
+ } else {
323
+ input.type = 'password';
324
+ 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>';
325
+ }
326
+ }
327
+
328
+ async function handleLogin(e) {
329
+ e.preventDefault();
330
+ const pwd = document.getElementById('password').value;
331
+ const btn = document.getElementById('submitBtn');
332
+ const spinner = document.getElementById('btnSpinner');
333
+ const btnText = document.getElementById('btnText');
334
+ const alert = document.getElementById('errorAlert');
335
+ const errorMsg = document.getElementById('errorMsg');
336
+
337
+ btn.disabled = true;
338
+ spinner.style.display = 'inline-block';
339
+ btnText.textContent = '验证中…';
340
+ alert.style.display = 'none';
341
+
342
+ try {
343
+ const resp = await fetch('/__dsh_bridge__/login', {
344
+ method: 'POST',
345
+ headers: { 'Content-Type': 'application/json' },
346
+ body: JSON.stringify({ password: pwd })
347
+ });
348
+ const data = await resp.json();
349
+ if (data.ok) {
350
+ btnText.textContent = '✓ 认证成功,正在跳转…';
351
+ setTimeout(() => {
352
+ window.location.reload();
353
+ }, 300);
354
+ } else {
355
+ errorMsg.textContent = data.error || '访问密码错误';
356
+ alert.style.display = 'flex';
357
+ alert.style.animation = 'none';
358
+ void alert.offsetWidth; // 触发 reflow
359
+ alert.style.animation = 'shake 0.4s ease';
360
+ btn.disabled = false;
361
+ spinner.style.display = 'none';
362
+ btnText.textContent = '解锁并访问';
363
+ document.getElementById('password').focus();
364
+ document.getElementById('password').select();
365
+ }
366
+ } catch (err) {
367
+ errorMsg.textContent = '网络请求失败,请重试';
368
+ alert.style.display = 'flex';
369
+ btn.disabled = false;
370
+ spinner.style.display = 'none';
371
+ btnText.textContent = '解锁并访问';
372
+ }
373
+ return false;
374
+ }
375
+ </script>
376
+ </body>
377
+ </html>`;
378
+ }