malt666 commited on
Commit
7831ad4
·
verified ·
1 Parent(s): 82a1d77

Upload 6 files

Browse files
Files changed (3) hide show
  1. app.py +12 -26
  2. templates/dashboard.html +0 -0
  3. templates/login.html +349 -339
app.py CHANGED
@@ -18,6 +18,9 @@ app = Flask(__name__, template_folder='templates')
18
  app.secret_key = os.environ.get("SECRET_KEY", "abacus_chat_proxy_secret_key")
19
  app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=7)
20
 
 
 
 
21
 
22
  API_ENDPOINT_URL = "https://abacus.ai/api/v0/describeDeployment"
23
  MODEL_LIST_URL = "https://abacus.ai/api/v0/listExternalApplications"
@@ -922,41 +925,22 @@ def get_compute_points_log(session, cookies, session_token):
922
  print(f"获取计算点使用日志异常: {e}")
923
 
924
 
925
- # 获取系统环境变量中的部署URL
926
- def get_space_url():
927
- # 尝试从Hugging Face Space环境变量获取
928
- space_url = os.environ.get("SPACE_URL")
929
- if space_url:
930
- return space_url
931
-
932
- # 尝试构建URL
933
- space_username = os.environ.get("SPACE_USERNAME")
934
- space_name = os.environ.get("SPACE_NAME")
935
- if space_username and space_name:
936
- return f"https://{space_username}-{space_name}.hf.space"
937
-
938
- # 如果无法从环境变量获取,返回默认值或None
939
- return os.environ.get("CUSTOM_URL", None)
940
-
941
- # 获取部署URL
942
- SPACE_URL = get_space_url()
943
-
944
  @app.route("/login", methods=["GET", "POST"])
945
  def login():
946
  error = None
947
- space_url = SPACE_URL
948
-
949
  if request.method == "POST":
950
  password = request.form.get("password")
951
  if password and hashlib.sha256(password.encode()).hexdigest() == PASSWORD:
952
  flask_session['logged_in'] = True
953
  flask_session.permanent = True
 
954
  return redirect(url_for('dashboard'))
955
  else:
956
- # 在密码错误时提示用户环境变量
957
- error = f"密码不正确。请使用在环境变量'password'中设置的密码。"
958
-
959
- return render_template('login.html', error=error, space_url=space_url)
960
 
961
 
962
  @app.route("/logout")
@@ -993,7 +977,9 @@ def dashboard():
993
  model_stats=model_usage_stats,
994
  total_tokens=total_tokens,
995
  compute_points=compute_points,
996
- compute_points_log=compute_points_log
 
 
997
  )
998
 
999
 
 
18
  app.secret_key = os.environ.get("SECRET_KEY", "abacus_chat_proxy_secret_key")
19
  app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=7)
20
 
21
+ # 获取 Hugging Face Space 主机名 (如果存在)
22
+ SPACE_HOST = os.environ.get("SPACE_HOST")
23
+ BASE_URL = f"https://{SPACE_HOST}" if SPACE_HOST else None
24
 
25
  API_ENDPOINT_URL = "https://abacus.ai/api/v0/describeDeployment"
26
  MODEL_LIST_URL = "https://abacus.ai/api/v0/listExternalApplications"
 
925
  print(f"获取计算点使用日志异常: {e}")
926
 
927
 
928
+ # 添加登录相关路由
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
929
  @app.route("/login", methods=["GET", "POST"])
930
  def login():
931
  error = None
 
 
932
  if request.method == "POST":
933
  password = request.form.get("password")
934
  if password and hashlib.sha256(password.encode()).hexdigest() == PASSWORD:
935
  flask_session['logged_in'] = True
936
  flask_session.permanent = True
937
+ # 登录成功后重定向到仪表盘
938
  return redirect(url_for('dashboard'))
939
  else:
940
+ # 更新错误提示信息
941
+ error = "密码不正确。密码为您在环境变量 'password' 中设置的值,该值也用作 API 调用的验证密钥。"
942
+ # 将 BASE_URL 传递给模板
943
+ return render_template('login.html', error=error, base_url=BASE_URL)
944
 
945
 
946
  @app.route("/logout")
 
977
  model_stats=model_usage_stats,
978
  total_tokens=total_tokens,
979
  compute_points=compute_points,
980
+ compute_points_log=compute_points_log,
981
+ # 将 BASE_URL 传递给模板
982
+ base_url=BASE_URL
983
  )
984
 
985
 
templates/dashboard.html CHANGED
The diff for this file is too large to render. See raw diff
 
templates/login.html CHANGED
@@ -1,340 +1,350 @@
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>Abacus Chat Proxy - 登录</title>
7
- <style>
8
- :root {
9
- --primary-color: #6366f1;
10
- --primary-dark: #4f46e5;
11
- --accent-color: #8b5cf6;
12
- --background: #f8fafc;
13
- --card-bg: #ffffff;
14
- --text-color: #1e293b;
15
- --text-light: #64748b;
16
- --error: #ef4444;
17
- --success: #10b981;
18
- }
19
-
20
- @media (prefers-color-scheme: dark) {
21
- :root {
22
- --primary-color: #6366f1;
23
- --primary-dark: #4f46e5;
24
- --accent-color: #a78bfa;
25
- --background: #0f172a;
26
- --card-bg: #1e293b;
27
- --text-color: #f1f5f9;
28
- --text-light: #94a3b8;
29
- }
30
- }
31
-
32
- * {
33
- margin: 0;
34
- padding: 0;
35
- box-sizing: border-box;
36
- font-family: 'Inter', 'SF Pro Display', -apple-system, BlinkMacSystemFont, sans-serif;
37
- }
38
-
39
- body {
40
- min-height: 100vh;
41
- background: var(--background);
42
- color: var(--text-color);
43
- display: flex;
44
- align-items: center;
45
- justify-content: center;
46
- position: relative;
47
- overflow: hidden;
48
- }
49
-
50
- /* 动态背景 */
51
- .background-animation {
52
- position: fixed;
53
- top: 0;
54
- left: 0;
55
- width: 100%;
56
- height: 100%;
57
- z-index: -1;
58
- background: linear-gradient(
59
- 135deg,
60
- rgba(99, 102, 241, 0.05) 0%,
61
- rgba(139, 92, 246, 0.05) 100%
62
- );
63
- }
64
-
65
- .background-animation:before {
66
- content: "";
67
- position: absolute;
68
- width: 100%;
69
- height: 100%;
70
- background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='40' height='40' viewBox='0 0 40 40'%3E%3Cg fill-rule='evenodd'%3E%3Cg fill='%236366f1' fill-opacity='0.05'%3E%3Cpath d='M0 38.59l2.83-2.83 1.41 1.41L1.41 40H0v-1.41zM0 20.83l2.83-2.83 1.41 1.41L1.41 22.24H0v-1.41zM0 3.06l2.83-2.83 1.41 1.41L1.41 4.47H0V3.06zm20 0l2.83-2.83 1.41 1.41L21.41 4.47h-1.41V3.06zm20 0l2.83-2.83 1.41 1.41L41.41 4.47h-1.41V3.06zm0 17.77l2.83-2.83 1.41 1.41-2.83 2.83h-1.41v-1.41zM20 20.83l2.83-2.83 1.41 1.41-2.83 2.83h-1.41v-1.41zm0 17.76l2.83-2.83 1.41 1.41-2.83 2.83h-1.41v-1.41zm20 0l2.83-2.83 1.41 1.41-2.83 2.83h-1.41v-1.41z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
71
- opacity: 0.2;
72
- animation: backgroundMove 30s linear infinite;
73
- }
74
-
75
- @keyframes backgroundMove {
76
- 0% {
77
- background-position: 0 0;
78
- }
79
- 100% {
80
- background-position: 100px 100px;
81
- }
82
- }
83
-
84
- /* 律动线条 */
85
- .line {
86
- position: fixed;
87
- height: 2px;
88
- background: linear-gradient(90deg, transparent, var(--primary-color), transparent);
89
- animation: linePulse 8s infinite;
90
- opacity: 0.1;
91
- }
92
-
93
- .line-1 {
94
- top: 20%;
95
- width: 80%;
96
- left: 10%;
97
- animation-delay: 0s;
98
- }
99
-
100
- .line-2 {
101
- top: 40%;
102
- width: 90%;
103
- left: 5%;
104
- animation-delay: 2s;
105
- }
106
-
107
- .line-3 {
108
- top: 60%;
109
- width: 70%;
110
- left: 15%;
111
- animation-delay: 4s;
112
- }
113
-
114
- .line-4 {
115
- top: 80%;
116
- width: 85%;
117
- left: 7.5%;
118
- animation-delay: 6s;
119
- }
120
-
121
- @keyframes linePulse {
122
- 0% {
123
- transform: scaleX(0);
124
- opacity: 0;
125
- }
126
- 50% {
127
- transform: scaleX(1);
128
- opacity: 0.1;
129
- }
130
- 100% {
131
- transform: scaleX(0);
132
- opacity: 0;
133
- }
134
- }
135
-
136
- .login-container {
137
- width: 100%;
138
- max-width: 400px;
139
- padding: 2.5rem;
140
- background: var(--card-bg);
141
- border-radius: 16px;
142
- box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.05);
143
- transform: translateY(0);
144
- transition: all 0.3s;
145
- position: relative;
146
- z-index: 1;
147
- overflow: hidden;
148
- }
149
-
150
- .login-container:hover {
151
- transform: translateY(-5px);
152
- box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
153
- }
154
-
155
- .login-container::after {
156
- content: '';
157
- position: absolute;
158
- top: 0;
159
- left: 0;
160
- right: 0;
161
- height: 4px;
162
- background: linear-gradient(90deg, var(--primary-color), var(--accent-color));
163
- z-index: 2;
164
- }
165
-
166
- .login-title {
167
- display: flex;
168
- align-items: center;
169
- margin-bottom: 2rem;
170
- position: relative;
171
- }
172
-
173
- .login-icon {
174
- font-size: 2rem;
175
- margin-right: 1rem;
176
- background-image: linear-gradient(45deg, var(--primary-color), var(--accent-color));
177
- -webkit-background-clip: text;
178
- -webkit-text-fill-color: transparent;
179
- animation: floating 3s ease-in-out infinite;
180
- }
181
-
182
- @keyframes floating {
183
- 0%, 100% {
184
- transform: translateY(0);
185
- }
186
- 50% {
187
- transform: translateY(-5px);
188
- }
189
- }
190
-
191
- .login-text {
192
- font-size: 1.5rem;
193
- font-weight: 700;
194
- color: var(--text-color);
195
- }
196
-
197
- .form-group {
198
- margin-bottom: 1.5rem;
199
- }
200
-
201
- .form-label {
202
- display: block;
203
- margin-bottom: 0.5rem;
204
- color: var(--text-light);
205
- font-weight: 500;
206
- font-size: 0.875rem;
207
- }
208
-
209
- .form-input {
210
- width: 100%;
211
- padding: 0.75rem 1rem;
212
- background: var(--background);
213
- border: 1px solid rgba(99, 102, 241, 0.1);
214
- border-radius: 8px;
215
- font-size: 1rem;
216
- color: var(--text-color);
217
- transition: all 0.2s;
218
- }
219
-
220
- .form-input:focus {
221
- outline: none;
222
- border-color: var(--primary-color);
223
- box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.2);
224
- }
225
-
226
- .form-input::placeholder {
227
- color: var(--text-light);
228
- opacity: 0.7;
229
- }
230
-
231
- .login-btn {
232
- width: 100%;
233
- padding: 0.75rem;
234
- background: var(--primary-color);
235
- color: white;
236
- border: none;
237
- border-radius: 8px;
238
- font-size: 1rem;
239
- font-weight: 600;
240
- cursor: pointer;
241
- transition: all 0.2s;
242
- position: relative;
243
- overflow: hidden;
244
- }
245
-
246
- .login-btn:hover {
247
- background: var(--primary-dark);
248
- }
249
-
250
- .login-btn::after {
251
- content: '';
252
- position: absolute;
253
- top: 0;
254
- left: -100%;
255
- width: 100%;
256
- height: 100%;
257
- background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
258
- animation: shine 3s infinite;
259
- }
260
-
261
- @keyframes shine {
262
- 0% {
263
- left: -100%;
264
- }
265
- 50%, 100% {
266
- left: 100%;
267
- }
268
- }
269
-
270
- .error-message {
271
- color: var(--error);
272
- font-size: 0.875rem;
273
- margin-top: 1rem;
274
- padding: 0.75rem;
275
- background: rgba(239, 68, 68, 0.1);
276
- border-radius: 8px;
277
- animation: shake 0.5s linear;
278
- }
279
-
280
- @keyframes shake {
281
- 0%, 100% { transform: translateX(0); }
282
- 10%, 30%, 50%, 70%, 90% { transform: translateX(-5px); }
283
- 20%, 40%, 60%, 80% { transform: translateX(5px); }
284
- }
285
-
286
- .space-url {
287
- margin-top: 1.5rem;
288
- text-align: center;
289
- color: var(--text-light);
290
- font-size: 0.875rem;
291
- }
292
-
293
- .space-url a {
294
- color: var(--primary-color);
295
- text-decoration: none;
296
- transition: all 0.2s;
297
- }
298
-
299
- .space-url a:hover {
300
- color: var(--accent-color);
301
- text-decoration: underline;
302
- }
303
- </style>
304
- </head>
305
- <body>
306
- <div class="background-animation"></div>
307
-
308
- <!-- 律动线条 -->
309
- <div class="line line-1"></div>
310
- <div class="line line-2"></div>
311
- <div class="line line-3"></div>
312
- <div class="line line-4"></div>
313
-
314
- <div class="login-container">
315
- <div class="login-title">
316
- <div class="login-icon">🤖</div>
317
- <div class="login-text">Abacus Chat Proxy</div>
318
- </div>
319
-
320
- <form method="post">
321
- <div class="form-group">
322
- <label class="form-label" for="password">请输入访问密码</label>
323
- <input class="form-input" type="password" id="password" name="password" placeholder="输入密码访问仪表盘" required autofocus>
324
- </div>
325
-
326
- <button class="login-btn" type="submit">登录</button>
327
-
328
- {% if error %}
329
- <div class="error-message">{{ error }}</div>
330
- {% endif %}
331
- </form>
332
-
333
- {% if space_url %}
334
- <div class="space-url">
335
- <p>请访问 <a href="{{ space_url }}" target="_blank">{{ space_url }}</a> 查看使用情况</p>
336
- </div>
337
- {% endif %}
338
- </div>
339
- </body>
 
 
 
 
 
 
 
 
 
 
340
  </html>
 
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>Abacus Chat Proxy - 登录</title>
7
+ <style>
8
+ :root {
9
+ --primary-color: #6f42c1;
10
+ --secondary-color: #4a32a8;
11
+ --bg-color: #0a0a1a;
12
+ --text-color: #e6e6ff;
13
+ --card-bg: rgba(30, 30, 60, 0.7);
14
+ --input-bg: rgba(40, 40, 80, 0.6);
15
+ --success-color: #36d399;
16
+ --error-color: #f87272;
17
+ }
18
+
19
+ * {
20
+ margin: 0;
21
+ padding: 0;
22
+ box-sizing: border-box;
23
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
24
+ }
25
+
26
+ body {
27
+ min-height: 100vh;
28
+ display: flex;
29
+ align-items: center;
30
+ justify-content: center;
31
+ background-color: var(--bg-color);
32
+ background-image:
33
+ radial-gradient(circle at 20% 35%, rgba(111, 66, 193, 0.15) 0%, transparent 40%),
34
+ radial-gradient(circle at 80% 10%, rgba(70, 111, 171, 0.1) 0%, transparent 40%);
35
+ color: var(--text-color);
36
+ position: relative;
37
+ overflow: hidden;
38
+ }
39
+
40
+ /* 动态背景网格 */
41
+ .grid-background {
42
+ position: absolute;
43
+ top: 0;
44
+ left: 0;
45
+ width: 100%;
46
+ height: 100%;
47
+ background-image: linear-gradient(rgba(50, 50, 100, 0.05) 1px, transparent 1px),
48
+ linear-gradient(90deg, rgba(50, 50, 100, 0.05) 1px, transparent 1px);
49
+ background-size: 30px 30px;
50
+ z-index: -1;
51
+ animation: grid-move 20s linear infinite;
52
+ }
53
+
54
+ @keyframes grid-move {
55
+ 0% {
56
+ transform: translateY(0);
57
+ }
58
+ 100% {
59
+ transform: translateY(30px);
60
+ }
61
+ }
62
+
63
+ /* 浮动粒子效果 */
64
+ .particles {
65
+ position: absolute;
66
+ top: 0;
67
+ left: 0;
68
+ width: 100%;
69
+ height: 100%;
70
+ overflow: hidden;
71
+ z-index: -1;
72
+ }
73
+
74
+ .particle {
75
+ position: absolute;
76
+ display: block;
77
+ pointer-events: none;
78
+ width: 6px;
79
+ height: 6px;
80
+ background-color: rgba(111, 66, 193, 0.2);
81
+ border-radius: 50%;
82
+ animation: float 20s infinite ease-in-out;
83
+ }
84
+
85
+ @keyframes float {
86
+ 0%, 100% {
87
+ transform: translateY(0) translateX(0);
88
+ opacity: 0;
89
+ }
90
+ 50% {
91
+ opacity: 0.5;
92
+ }
93
+ 25%, 75% {
94
+ transform: translateY(-100px) translateX(50px);
95
+ }
96
+ }
97
+
98
+ .login-card {
99
+ width: 380px;
100
+ padding: 2.5rem;
101
+ border-radius: 16px;
102
+ background: var(--card-bg);
103
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
104
+ backdrop-filter: blur(8px);
105
+ border: 1px solid rgba(255, 255, 255, 0.1);
106
+ z-index: 10;
107
+ animation: card-fade-in 0.6s ease-out;
108
+ }
109
+
110
+ @keyframes card-fade-in {
111
+ from {
112
+ opacity: 0;
113
+ transform: translateY(20px);
114
+ }
115
+ to {
116
+ opacity: 1;
117
+ transform: translateY(0);
118
+ }
119
+ }
120
+
121
+ .login-header {
122
+ text-align: center;
123
+ margin-bottom: 2rem;
124
+ }
125
+
126
+ .login-header h1 {
127
+ font-size: 2rem;
128
+ font-weight: 600;
129
+ margin-bottom: 0.5rem;
130
+ background: linear-gradient(45deg, #6f42c1, #5181f1);
131
+ -webkit-background-clip: text;
132
+ -webkit-text-fill-color: transparent;
133
+ letter-spacing: 0.5px;
134
+ }
135
+
136
+ .login-header p {
137
+ color: rgba(230, 230, 255, 0.7);
138
+ font-size: 0.95rem;
139
+ }
140
+
141
+ .login-form {
142
+ display: flex;
143
+ flex-direction: column;
144
+ }
145
+
146
+ .form-group {
147
+ margin-bottom: 1.5rem;
148
+ position: relative;
149
+ }
150
+
151
+ .form-group label {
152
+ display: block;
153
+ margin-bottom: 0.5rem;
154
+ font-size: 0.9rem;
155
+ font-weight: 500;
156
+ color: rgba(230, 230, 255, 0.9);
157
+ }
158
+
159
+ .form-control {
160
+ width: 100%;
161
+ padding: 0.75rem 1rem;
162
+ font-size: 1rem;
163
+ line-height: 1.5;
164
+ color: var(--text-color);
165
+ background-color: var(--input-bg);
166
+ border: 1px solid rgba(255, 255, 255, 0.1);
167
+ border-radius: 8px;
168
+ transition: all 0.2s ease;
169
+ outline: none;
170
+ }
171
+
172
+ .form-control:focus {
173
+ border-color: var(--primary-color);
174
+ box-shadow: 0 0 0 3px rgba(111, 66, 193, 0.2);
175
+ }
176
+
177
+ .btn {
178
+ display: inline-block;
179
+ font-weight: 500;
180
+ text-align: center;
181
+ vertical-align: middle;
182
+ cursor: pointer;
183
+ padding: 0.75rem 1rem;
184
+ font-size: 1rem;
185
+ line-height: 1.5;
186
+ border-radius: 8px;
187
+ transition: all 0.15s ease-in-out;
188
+ border: none;
189
+ }
190
+
191
+ .btn-primary {
192
+ color: #fff;
193
+ background: linear-gradient(45deg, var(--primary-color), var(--secondary-color));
194
+ box-shadow: 0 4px 10px rgba(111, 66, 193, 0.3);
195
+ position: relative;
196
+ overflow: hidden;
197
+ }
198
+
199
+ .btn-primary:hover {
200
+ transform: translateY(-2px);
201
+ box-shadow: 0 6px 15px rgba(111, 66, 193, 0.4);
202
+ }
203
+
204
+ .btn-primary:active {
205
+ transform: translateY(0);
206
+ }
207
+
208
+ /* 添加光效效果 */
209
+ .btn-primary::before {
210
+ content: '';
211
+ position: absolute;
212
+ top: -50%;
213
+ left: -50%;
214
+ width: 200%;
215
+ height: 200%;
216
+ background: linear-gradient(
217
+ to bottom right,
218
+ rgba(255, 255, 255, 0) 0%,
219
+ rgba(255, 255, 255, 0.1) 50%,
220
+ rgba(255, 255, 255, 0) 100%
221
+ );
222
+ transform: rotate(45deg);
223
+ animation: btn-shine 3s infinite;
224
+ }
225
+
226
+ @keyframes btn-shine {
227
+ 0% {
228
+ left: -50%;
229
+ }
230
+ 100% {
231
+ left: 150%;
232
+ }
233
+ }
234
+
235
+ .error-message {
236
+ background: rgba(248, 113, 113, 0.1); /* error-color with opacity */
237
+ color: var(--error-color);
238
+ padding: 1rem;
239
+ border-radius: 8px;
240
+ margin-bottom: 1.5rem;
241
+ border: 1px solid rgba(248, 113, 113, 0.3);
242
+ text-align: center;
243
+ font-size: 0.9rem;
244
+ line-height: 1.5; /* 增加行高以便阅读 */
245
+ }
246
+
247
+ .logo {
248
+ margin-bottom: 1rem;
249
+ font-size: 3rem;
250
+ animation: glow 2s infinite alternate;
251
+ }
252
+
253
+ @keyframes glow {
254
+ from {
255
+ text-shadow: 0 0 5px rgba(111, 66, 193, 0.5), 0 0 10px rgba(111, 66, 193, 0.5);
256
+ }
257
+ to {
258
+ text-shadow: 0 0 10px rgba(111, 66, 193, 0.8), 0 0 20px rgba(111, 66, 193, 0.8);
259
+ }
260
+ }
261
+
262
+ .info-text {
263
+ text-align: center;
264
+ color: rgba(230, 230, 255, 0.7);
265
+ margin-bottom: 1.5rem;
266
+ font-size: 0.9rem;
267
+ }
268
+
269
+ .info-text a {
270
+ color: var(--accent-color);
271
+ text-decoration: none;
272
+ font-weight: 600;
273
+ }
274
+
275
+ .info-text a:hover {
276
+ text-decoration: underline;
277
+ }
278
+ </style>
279
+ </head>
280
+ <body>
281
+ <div class="grid-background"></div>
282
+ <div class="particles">
283
+ <!-- 粒子元素会由JS生成 -->
284
+ </div>
285
+
286
+ <div class="login-card">
287
+ <div class="login-header">
288
+ <div class="logo">🤖</div>
289
+ <h1>Abacus Chat Proxy</h1>
290
+ <p>请输入访问密码</p>
291
+ </div>
292
+
293
+ {% if base_url %}
294
+ <p class="info-text">
295
+ 请访问 <a href="{{ base_url }}" target="_blank">{{ base_url }}</a> 来登录查看使用情况。
296
+ </p>
297
+ {% endif %}
298
+
299
+ <div class="error-message" id="error-message">
300
+ {{ error }}
301
+ </div>
302
+
303
+ <form class="login-form" method="post" action="/login">
304
+ <div class="form-group">
305
+ <label for="password">密码</label>
306
+ <input type="password" class="form-control" id="password" name="password" placeholder="请输入访问密码" required>
307
+ </div>
308
+
309
+ <button type="submit" class="btn btn-primary">登录</button>
310
+ </form>
311
+ </div>
312
+
313
+ <script>
314
+ // 创建浮动粒子
315
+ function createParticles() {
316
+ const particlesContainer = document.querySelector('.particles');
317
+ const particleCount = 20;
318
+
319
+ for (let i = 0; i < particleCount; i++) {
320
+ const particle = document.createElement('div');
321
+ particle.className = 'particle';
322
+
323
+ // 随机位置和大小
324
+ const size = Math.random() * 5 + 2;
325
+ const x = Math.random() * 100;
326
+ const y = Math.random() * 100;
327
+
328
+ particle.style.width = `${size}px`;
329
+ particle.style.height = `${size}px`;
330
+ particle.style.left = `${x}%`;
331
+ particle.style.top = `${y}%`;
332
+
333
+ // 随机动画延迟
334
+ particle.style.animationDelay = `${Math.random() * 10}s`;
335
+ particle.style.animationDuration = `${Math.random() * 10 + 10}s`;
336
+
337
+ // 随机透明度
338
+ particle.style.opacity = Math.random() * 0.5;
339
+
340
+ particlesContainer.appendChild(particle);
341
+ }
342
+ }
343
+
344
+ // 页面加载时创建粒子
345
+ window.addEventListener('load', () => {
346
+ createParticles();
347
+ });
348
+ </script>
349
+ </body>
350
  </html>