mistpe commited on
Commit
3922044
·
verified ·
1 Parent(s): 1df5e59

Upload 3 files

Browse files
templates/action_result.html ADDED
@@ -0,0 +1,192 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="zh">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>操作结果 - HF Space Manager</title>
7
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
8
+ <style>
9
+ :root {
10
+ --primary-color: #00ff9d;
11
+ --primary-dark: #00cc7d;
12
+ --secondary-color: #ff00ff;
13
+ --background-color: #0a0b0f;
14
+ --card-background: #12141c;
15
+ --text-primary: #ffffff;
16
+ --text-secondary: #7f8ea3;
17
+ --border-color: #1e2029;
18
+ --card-border: 1px solid #2a2d3a;
19
+ --card-glow: 0 0 20px rgba(0, 255, 157, 0.1);
20
+ --border-radius: 12px;
21
+ --transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
22
+ }
23
+
24
+ * {
25
+ margin: 0;
26
+ padding: 0;
27
+ box-sizing: border-box;
28
+ font-family: 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
29
+ }
30
+
31
+ body {
32
+ background-color: var(--background-color);
33
+ background-image:
34
+ radial-gradient(circle at 10% 20%, rgba(0, 255, 157, 0.03) 0%, transparent 20%),
35
+ radial-gradient(circle at 90% 80%, rgba(255, 0, 255, 0.03) 0%, transparent 20%);
36
+ min-height: 100vh;
37
+ display: flex;
38
+ flex-direction: column;
39
+ color: var(--text-primary);
40
+ }
41
+
42
+ .container {
43
+ flex: 1;
44
+ display: flex;
45
+ justify-content: center;
46
+ align-items: center;
47
+ padding: 2rem;
48
+ position: relative;
49
+ }
50
+
51
+ .container::before {
52
+ content: '';
53
+ position: absolute;
54
+ top: 50%;
55
+ left: 50%;
56
+ transform: translate(-50%, -50%);
57
+ width: 100%;
58
+ height: 100%;
59
+ background: radial-gradient(circle at center, rgba(0, 255, 157, 0.03) 0%, transparent 70%);
60
+ pointer-events: none;
61
+ }
62
+
63
+ .result-card {
64
+ background: var(--card-background);
65
+ padding: 2.5rem;
66
+ border-radius: var(--border-radius);
67
+ border: var(--card-border);
68
+ box-shadow: var(--card-glow);
69
+ width: 100%;
70
+ max-width: 500px;
71
+ text-align: center;
72
+ position: relative;
73
+ overflow: hidden;
74
+ animation: cardAppear 0.5s ease-out;
75
+ }
76
+
77
+ @keyframes cardAppear {
78
+ from {
79
+ opacity: 0;
80
+ transform: translateY(20px);
81
+ }
82
+ to {
83
+ opacity: 1;
84
+ transform: translateY(0);
85
+ }
86
+ }
87
+
88
+ .result-card::before {
89
+ content: '';
90
+ position: absolute;
91
+ top: 0;
92
+ left: 0;
93
+ right: 0;
94
+ height: 2px;
95
+ background: linear-gradient(90deg, transparent, var(--primary-color), transparent);
96
+ opacity: 0.5;
97
+ }
98
+
99
+ .result-message {
100
+ color: var(--text-primary);
101
+ margin-bottom: 2rem;
102
+ font-size: 1.1rem;
103
+ line-height: 1.6;
104
+ position: relative;
105
+ padding: 1rem;
106
+ }
107
+
108
+ .result-message::after {
109
+ content: '';
110
+ position: absolute;
111
+ bottom: -10px;
112
+ left: 50%;
113
+ transform: translateX(-50%);
114
+ width: 50px;
115
+ height: 2px;
116
+ background: var(--primary-color);
117
+ opacity: 0.5;
118
+ }
119
+
120
+ .back-button {
121
+ display: inline-flex;
122
+ align-items: center;
123
+ gap: 0.5rem;
124
+ padding: 0.75rem 1.5rem;
125
+ background: rgba(0, 255, 157, 0.1);
126
+ border: 1px solid var(--primary-color);
127
+ color: var(--primary-color);
128
+ text-decoration: none;
129
+ border-radius: 6px;
130
+ font-weight: 500;
131
+ transition: var(--transition);
132
+ }
133
+
134
+ .back-button:hover {
135
+ background: var(--primary-color);
136
+ color: var(--background-color);
137
+ box-shadow: 0 0 15px rgba(0, 255, 157, 0.3);
138
+ transform: translateY(-1px);
139
+ }
140
+
141
+ .footer {
142
+ text-align: center;
143
+ padding: 2rem;
144
+ color: var(--text-secondary);
145
+ font-size: 0.9rem;
146
+ background: rgba(18, 20, 28, 0.5);
147
+ backdrop-filter: blur(10px);
148
+ border-top: 1px solid var(--border-color);
149
+ }
150
+
151
+ .footer a {
152
+ color: var(--primary-color);
153
+ text-decoration: none;
154
+ transition: var(--transition);
155
+ }
156
+
157
+ .footer a:hover {
158
+ text-shadow: 0 0 10px var(--primary-color);
159
+ }
160
+
161
+ @media (max-width: 480px) {
162
+ .container {
163
+ padding: 1rem;
164
+ }
165
+
166
+ .result-card {
167
+ padding: 1.5rem;
168
+ }
169
+ }
170
+ </style>
171
+ </head>
172
+ <body>
173
+ <div class="container">
174
+ <div class="result-card">
175
+ <div class="result-message">
176
+ <i class="fas fa-info-circle" style="font-size: 2rem; color: var(--primary-color); margin-bottom: 1rem; display: block; text-shadow: 0 0 10px var(--primary-color);"></i>
177
+ {{ message }}
178
+ </div>
179
+ <a href="/dashboard" class="back-button">
180
+ <i class="fas fa-arrow-left"></i>
181
+ 返回控制面板
182
+ </a>
183
+ </div>
184
+ </div>
185
+
186
+ <footer class="footer">
187
+ <a href="https://github.com/ssfun/hf-space-manager">HF Space Manager</a> 由
188
+ <a href="https://github.com/ssfun">ssfun</a> 构建,源代码遵循
189
+ <a href="https://github.com/zhanghxiao">MistPeak</a> 二次开发遵循
190
+ <a href="https://opensource.org/license/mit">MIT 协议</a>
191
+ </footer>
192
+ </body>
templates/dashboard.html ADDED
@@ -0,0 +1,503 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="zh">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>HF Space Manager - 控制面板</title>
7
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
8
+ <style>
9
+ :root {
10
+ --primary-color: #00ff9d;
11
+ --primary-dark: #00cc7d;
12
+ --secondary-color: #ff00ff;
13
+ --background-color: #0a0b0f;
14
+ --card-background: #12141c;
15
+ --text-primary: #ffffff;
16
+ --text-secondary: #7f8ea3;
17
+ --success-color: #00ff9d;
18
+ --warning-color: #ff9d00;
19
+ --danger-color: #ff2d55;
20
+ --sleeping-color: #00ffff;
21
+ --border-color: #1e2029;
22
+ --card-border: 1px solid #2a2d3a;
23
+ --card-glow: 0 0 20px rgba(0, 255, 157, 0.1);
24
+ --border-radius: 12px;
25
+ --transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
26
+ }
27
+
28
+ * {
29
+ margin: 0;
30
+ padding: 0;
31
+ box-sizing: border-box;
32
+ font-family: 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
33
+ }
34
+
35
+ body {
36
+ background-color: var(--background-color);
37
+ background-image:
38
+ radial-gradient(circle at 10% 20%, rgba(0, 255, 157, 0.03) 0%, transparent 20%),
39
+ radial-gradient(circle at 90% 80%, rgba(255, 0, 255, 0.03) 0%, transparent 20%);
40
+ min-height: 100vh;
41
+ display: flex;
42
+ flex-direction: column;
43
+ color: var(--text-primary);
44
+ }
45
+
46
+ .header {
47
+ background: rgba(18, 20, 28, 0.8);
48
+ backdrop-filter: blur(10px);
49
+ position: fixed;
50
+ top: 0;
51
+ left: 0;
52
+ right: 0;
53
+ z-index: 1000;
54
+ padding: 1rem 2rem;
55
+ display: flex;
56
+ justify-content: space-between;
57
+ align-items: center;
58
+ border-bottom: 1px solid var(--border-color);
59
+ }
60
+
61
+ .header h1 {
62
+ font-size: 1.5rem;
63
+ color: var(--text-primary);
64
+ font-weight: 600;
65
+ display: flex;
66
+ align-items: center;
67
+ gap: 0.5rem;
68
+ }
69
+
70
+ .header h1 i {
71
+ color: var(--primary-color);
72
+ text-shadow: 0 0 10px var(--primary-color);
73
+ }
74
+
75
+ .container {
76
+ max-width: 1400px;
77
+ margin: 90px auto 2rem;
78
+ padding: 0 2rem;
79
+ }
80
+
81
+ .owner-section {
82
+ background: var(--card-background);
83
+ border-radius: var(--border-radius);
84
+ padding: 2rem;
85
+ margin-bottom: 2rem;
86
+ border: var(--card-border);
87
+ box-shadow: var(--card-glow);
88
+ position: relative;
89
+ overflow: hidden;
90
+ }
91
+
92
+ .owner-section::before {
93
+ content: '';
94
+ position: absolute;
95
+ top: 0;
96
+ left: 0;
97
+ right: 0;
98
+ height: 1px;
99
+ background: linear-gradient(90deg, transparent, var(--primary-color), transparent);
100
+ opacity: 0.5;
101
+ }
102
+
103
+ .owner-header {
104
+ display: flex;
105
+ justify-content: space-between;
106
+ align-items: center;
107
+ margin-bottom: 2rem;
108
+ padding-bottom: 1rem;
109
+ border-bottom: 1px solid var(--border-color);
110
+ }
111
+
112
+ .owner-name {
113
+ font-size: 1.5rem;
114
+ font-weight: 600;
115
+ color: var(--text-primary);
116
+ display: flex;
117
+ align-items: center;
118
+ gap: 0.5rem;
119
+ }
120
+
121
+ .status-stats {
122
+ display: flex;
123
+ gap: 1rem;
124
+ flex-wrap: wrap;
125
+ }
126
+
127
+ .stat-item {
128
+ padding: 0.5rem 1rem;
129
+ border-radius: 8px;
130
+ background: rgba(255, 255, 255, 0.05);
131
+ display: flex;
132
+ align-items: center;
133
+ gap: 0.5rem;
134
+ border: 1px solid var(--border-color);
135
+ }
136
+
137
+ .space-grid {
138
+ display: grid;
139
+ grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
140
+ gap: 1.5rem;
141
+ }
142
+
143
+ .space-card {
144
+ background: var(--card-background);
145
+ border-radius: var(--border-radius);
146
+ padding: 1.5rem;
147
+ border: var(--card-border);
148
+ transition: var(--transition);
149
+ position: relative;
150
+ overflow: hidden;
151
+ }
152
+
153
+ .space-card:hover {
154
+ transform: translateY(-2px);
155
+ box-shadow: var(--card-glow);
156
+ }
157
+
158
+ .space-card::after {
159
+ content: '';
160
+ position: absolute;
161
+ top: 0;
162
+ right: 0;
163
+ width: 6px;
164
+ height: 100%;
165
+ background: var(--primary-color);
166
+ opacity: 0.5;
167
+ transition: var(--transition);
168
+ }
169
+
170
+ .space-card:hover::after {
171
+ opacity: 1;
172
+ }
173
+
174
+ .space-header {
175
+ display: flex;
176
+ justify-content: space-between;
177
+ align-items: flex-start;
178
+ margin-bottom: 1rem;
179
+ }
180
+
181
+ .space-name {
182
+ font-size: 1.25rem;
183
+ font-weight: 600;
184
+ color: var(--text-primary);
185
+ }
186
+
187
+ .status-badge {
188
+ padding: 0.25rem 0.75rem;
189
+ border-radius: 6px;
190
+ font-size: 0.875rem;
191
+ font-weight: 500;
192
+ text-transform: uppercase;
193
+ letter-spacing: 0.5px;
194
+ border: 1px solid transparent;
195
+ }
196
+
197
+ .status-RUNNING {
198
+ background-color: rgba(0, 255, 157, 0.1);
199
+ border-color: var(--success-color);
200
+ color: var(--success-color);
201
+ }
202
+
203
+ .status-BUILDING {
204
+ background-color: rgba(255, 157, 0, 0.1);
205
+ border-color: var(--warning-color);
206
+ color: var(--warning-color);
207
+ }
208
+
209
+ .status-SLEEPING {
210
+ background-color: rgba(0, 255, 255, 0.1);
211
+ border-color: var(--sleeping-color);
212
+ color: var(--sleeping-color);
213
+ }
214
+
215
+ .status-STOPPED {
216
+ background-color: rgba(127, 142, 163, 0.1);
217
+ border-color: var(--text-secondary);
218
+ color: var(--text-secondary);
219
+ }
220
+
221
+ .status-FAILED, .status-BUILD_ERROR {
222
+ background-color: rgba(255, 45, 85, 0.1);
223
+ border-color: var(--danger-color);
224
+ color: var(--danger-color);
225
+ }
226
+
227
+ .space-info {
228
+ display: grid;
229
+ grid-template-columns: repeat(2, 1fr);
230
+ gap: 1rem;
231
+ margin-bottom: 1.5rem;
232
+ }
233
+
234
+ .info-item {
235
+ display: flex;
236
+ flex-direction: column;
237
+ gap: 0.25rem;
238
+ }
239
+
240
+ .info-label {
241
+ font-size: 0.875rem;
242
+ color: var(--text-secondary);
243
+ text-transform: uppercase;
244
+ letter-spacing: 0.5px;
245
+ }
246
+
247
+ .info-value {
248
+ font-size: 0.9375rem;
249
+ color: var(--text-primary);
250
+ font-weight: 500;
251
+ }
252
+
253
+ .action-buttons {
254
+ display: grid;
255
+ grid-template-columns: repeat(3, 1fr);
256
+ gap: 0.75rem;
257
+ }
258
+
259
+ .action-button {
260
+ padding: 0.75rem;
261
+ border-radius: 6px;
262
+ font-size: 0.9375rem;
263
+ font-weight: 500;
264
+ text-align: center;
265
+ cursor: pointer;
266
+ transition: var(--transition);
267
+ display: flex;
268
+ align-items: center;
269
+ justify-content: center;
270
+ gap: 0.5rem;
271
+ text-decoration: none;
272
+ border: 1px solid var(--border-color);
273
+ background: transparent;
274
+ color: var(--text-primary);
275
+ }
276
+
277
+ .action-button:hover {
278
+ border-color: var(--primary-color);
279
+ box-shadow: 0 0 10px rgba(0, 255, 157, 0.2);
280
+ }
281
+
282
+ .action-button.restart {
283
+ background-color: rgba(0, 255, 157, 0.1);
284
+ border-color: var(--primary-color);
285
+ color: var(--primary-color);
286
+ }
287
+
288
+ .action-button.restart:hover {
289
+ background-color: var(--primary-color);
290
+ color: var(--background-color);
291
+ }
292
+
293
+ .loading-overlay {
294
+ position: fixed;
295
+ top: 0;
296
+ left: 0;
297
+ right: 0;
298
+ bottom: 0;
299
+ background: rgba(10, 11, 15, 0.8);
300
+ backdrop-filter: blur(5px);
301
+ display: flex;
302
+ justify-content: center;
303
+ align-items: center;
304
+ z-index: 9999;
305
+ }
306
+
307
+ .loading-spinner {
308
+ width: 40px;
309
+ height: 40px;
310
+ border: 3px solid var(--card-background);
311
+ border-top: 3px solid var(--primary-color);
312
+ border-radius: 50%;
313
+ animation: spin 1s linear infinite;
314
+ box-shadow: 0 0 20px rgba(0, 255, 157, 0.2);
315
+ }
316
+
317
+ @keyframes spin {
318
+ 0% { transform: rotate(0deg); }
319
+ 100% { transform: rotate(360deg); }
320
+ }
321
+
322
+ @media (max-width: 768px) {
323
+ .container {
324
+ padding: 0 1rem;
325
+ }
326
+
327
+ .owner-header {
328
+ flex-direction: column;
329
+ gap: 1rem;
330
+ }
331
+
332
+ .status-stats {
333
+ justify-content: flex-start;
334
+ }
335
+
336
+ .space-grid {
337
+ grid-template-columns: 1fr;
338
+ }
339
+
340
+ .space-info {
341
+ grid-template-columns: 1fr;
342
+ }
343
+ }
344
+ </style>
345
+ </head>
346
+ <body>
347
+ <div id="loading" class="loading-overlay">
348
+ <div class="loading-spinner"></div>
349
+ </div>
350
+
351
+ <header class="header">
352
+ <h1><i class="fas fa-server"></i> HF Space Manager</h1>
353
+ <a href="/logout" class="action-button">
354
+ <i class="fas fa-sign-out-alt"></i> 退出
355
+ </a>
356
+ </header>
357
+
358
+ <div class="container">
359
+ {% if spaces %}
360
+ {% for owner, owner_spaces in grouped_spaces.items() %}
361
+ <div class="owner-section">
362
+ <div class="owner-header">
363
+ <div class="owner-name">
364
+ <i class="fas fa-user"></i>
365
+ {{ owner }}
366
+ </div>
367
+ <div class="status-stats">
368
+ <div class="stat-item">
369
+ <i class="fas fa-cube"></i>
370
+ 总数: {{ owner_spaces|length }}
371
+ </div>
372
+ <div class="stat-item">
373
+ <i class="fas fa-play-circle"></i>
374
+ 运行中: {{ running_count }}
375
+ </div>
376
+ <div class="stat-item">
377
+ <i class="fas fa-moon"></i>
378
+ 休眠: {{ sleeping_count }}
379
+ </div>
380
+ <div class="stat-item">
381
+ <i class="fas fa-stop-circle"></i>
382
+ 停止: {{ stopped_count }}
383
+ </div>
384
+ <div class="stat-item">
385
+ <i class="fas fa-exclamation-circle"></i>
386
+ 失败: {{ failed_count }}
387
+ </div>
388
+ </div>
389
+ </div>
390
+ <div class="space-grid">
391
+ {% for space in owner_spaces %}
392
+ <div class="space-card" data-space-id="{{ space.repo_id }}">
393
+ <div class="space-header">
394
+ <div class="space-name">{{ space.name }}</div>
395
+ <span class="status-badge status-{{ space.status }}">{{ space.status }}</span>
396
+ </div>
397
+ <div class="space-info">
398
+ <div class="info-item">
399
+ <span class="info-label">Space ID</span>
400
+ <span class="info-value">{{ space.repo_id }}</span>
401
+ </div>
402
+ <div class="info-item">
403
+ <span class="info-label">创建时间</span>
404
+ <span class="info-value">{{ space.created_at }}</span>
405
+ </div>
406
+ <div class="info-item">
407
+ <span class="info-label">最后修改</span>
408
+ <span class="info-value">{{ space.last_modified }}</span>
409
+ </div>
410
+ <div class="info-item">
411
+ <span class="info-label">SDK 版本</span>
412
+ <span class="info-value">{{ space.sdk }}</span>
413
+ </div>
414
+ <div class="info-item">
415
+ <span class="info-label">应用端口</span>
416
+ <span class="info-value">{{ space.app_port }}</span>
417
+ </div>
418
+ <div class="info-item">
419
+ <span class="info-label">访问权限</span>
420
+ <span class="info-value">{{ '私有' if space.private else '公开' }}</span>
421
+ </div>
422
+ </div>
423
+ <div class="action-buttons">
424
+ <a href="{{ space.url }}" target="_blank" class="action-button view">
425
+ <i class="fas fa-external-link-alt"></i> 查看
426
+ </a>
427
+ <button onclick="confirmAction('restart', '{{ space.repo_id }}')" class="action-button restart">
428
+ <i class="fas fa-sync-alt"></i> 重启
429
+ </button>
430
+ <button onclick="confirmAction('rebuild', '{{ space.repo_id }}')" class="action-button rebuild">
431
+ <i class="fas fa-tools"></i> 重建
432
+ </button>
433
+ </div>
434
+ </div>
435
+ {% endfor %}
436
+ </div>
437
+ </div>
438
+ {% endfor %}
439
+ {% else %}
440
+ <div class="owner-section">
441
+ <p style="text-align: center; color: var(--text-secondary);">
442
+ <i class="fas fa-info-circle"></i> 没有找到任何 Spaces。请确保你的账户中有创建的 Spaces,并且提供的 token 有正确的权限。
443
+ </p>
444
+ </div>
445
+ {% endif %}
446
+ </div>
447
+
448
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js"></script>
449
+ <script>
450
+ const socket = io();
451
+
452
+ socket.on('connect', () => {
453
+ console.log('Connected to server');
454
+ });
455
+
456
+ socket.on('disconnect', () => {
457
+ console.log('Disconnected from server');
458
+ });
459
+
460
+ socket.on('spaces_updated', (data) => {
461
+ updateSpaceStatuses();
462
+ });
463
+
464
+ document.addEventListener('visibilitychange', function() {
465
+ if (document.hidden) {
466
+ socket.disconnect();
467
+ } else {
468
+ socket.connect();
469
+ }
470
+ });
471
+
472
+ window.addEventListener('load', function() {
473
+ document.getElementById('loading').style.display = 'none';
474
+ });
475
+
476
+ function updateSpaceStatuses() {
477
+ document.querySelectorAll('.space-card').forEach(card => {
478
+ const spaceId = card.dataset.spaceId;
479
+ fetch(`/api/space/${spaceId}/status`)
480
+ .then(response => response.json())
481
+ .then(data => {
482
+ const statusElement = card.querySelector('.status-badge');
483
+ if (statusElement && data.status) {
484
+ statusElement.className = `status-badge status-${data.status}`;
485
+ statusElement.textContent = data.status;
486
+ }
487
+ })
488
+ .catch(error => console.error('Error updating status:', error));
489
+ });
490
+ }
491
+
492
+ function confirmAction(action, spaceId) {
493
+ const actionText = action === 'restart' ? '重启' : '重建';
494
+ if (confirm(`确定要${actionText} "${spaceId}" 吗?`)) {
495
+ document.getElementById('loading').style.display = 'flex';
496
+ window.location.href = `/action/${action}/${spaceId}`;
497
+ }
498
+ }
499
+
500
+ setInterval(updateSpaceStatuses, 30000);
501
+ </script>
502
+ </body>
503
+ </html>
templates/index.html ADDED
@@ -0,0 +1,249 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="zh">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>登录 - HF Space Manager</title>
7
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
8
+ <style>
9
+ :root {
10
+ --primary-color: #00ff9d;
11
+ --primary-dark: #00cc7d;
12
+ --secondary-color: #ff00ff;
13
+ --background-color: #0a0b0f;
14
+ --card-background: #12141c;
15
+ --text-primary: #ffffff;
16
+ --text-secondary: #7f8ea3;
17
+ --border-color: #1e2029;
18
+ --card-border: 1px solid #2a2d3a;
19
+ --card-glow: 0 0 20px rgba(0, 255, 157, 0.1);
20
+ --border-radius: 12px;
21
+ --transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
22
+ }
23
+
24
+ * {
25
+ margin: 0;
26
+ padding: 0;
27
+ box-sizing: border-box;
28
+ font-family: 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
29
+ }
30
+
31
+ body {
32
+ background-color: var(--background-color);
33
+ background-image:
34
+ radial-gradient(circle at 10% 20%, rgba(0, 255, 157, 0.03) 0%, transparent 20%),
35
+ radial-gradient(circle at 90% 80%, rgba(255, 0, 255, 0.03) 0%, transparent 20%);
36
+ min-height: 100vh;
37
+ display: flex;
38
+ flex-direction: column;
39
+ color: var(--text-primary);
40
+ }
41
+
42
+ .container {
43
+ flex: 1;
44
+ display: flex;
45
+ flex-direction: column;
46
+ justify-content: center;
47
+ align-items: center;
48
+ padding: 2rem;
49
+ position: relative;
50
+ }
51
+
52
+ .container::before {
53
+ content: '';
54
+ position: absolute;
55
+ top: 50%;
56
+ left: 50%;
57
+ transform: translate(-50%, -50%);
58
+ width: 100%;
59
+ height: 100%;
60
+ background: radial-gradient(circle at center, rgba(0, 255, 157, 0.03) 0%, transparent 70%);
61
+ pointer-events: none;
62
+ }
63
+
64
+ .logo {
65
+ font-size: 2.5rem;
66
+ font-weight: 600;
67
+ color: var(--text-primary);
68
+ margin-bottom: 2rem;
69
+ display: flex;
70
+ align-items: center;
71
+ gap: 1rem;
72
+ text-shadow: 0 0 10px rgba(0, 255, 157, 0.3);
73
+ animation: glow 2s ease-in-out infinite alternate;
74
+ }
75
+
76
+ @keyframes glow {
77
+ from {
78
+ text-shadow: 0 0 10px rgba(0, 255, 157, 0.3);
79
+ }
80
+ to {
81
+ text-shadow: 0 0 20px rgba(0, 255, 157, 0.5);
82
+ }
83
+ }
84
+
85
+ .logo i {
86
+ color: var(--primary-color);
87
+ }
88
+
89
+ .login-card {
90
+ background: var(--card-background);
91
+ padding: 2.5rem;
92
+ border-radius: var(--border-radius);
93
+ border: var(--card-border);
94
+ box-shadow: var(--card-glow);
95
+ width: 100%;
96
+ max-width: 400px;
97
+ position: relative;
98
+ overflow: hidden;
99
+ }
100
+
101
+ .login-card::before {
102
+ content: '';
103
+ position: absolute;
104
+ top: 0;
105
+ left: 0;
106
+ right: 0;
107
+ height: 2px;
108
+ background: linear-gradient(90deg, transparent, var(--primary-color), transparent);
109
+ opacity: 0.5;
110
+ }
111
+
112
+ .form-group {
113
+ margin-bottom: 1.5rem;
114
+ }
115
+
116
+ label {
117
+ display: block;
118
+ margin-bottom: 0.5rem;
119
+ color: var(--text-primary);
120
+ font-weight: 500;
121
+ font-size: 0.9rem;
122
+ text-transform: uppercase;
123
+ letter-spacing: 0.5px;
124
+ }
125
+
126
+ input {
127
+ width: 100%;
128
+ padding: 0.75rem 1rem;
129
+ background: rgba(255, 255, 255, 0.05);
130
+ border: 1px solid var(--border-color);
131
+ border-radius: 6px;
132
+ color: var(--text-primary);
133
+ font-size: 1rem;
134
+ transition: var(--transition);
135
+ }
136
+
137
+ input:focus {
138
+ outline: none;
139
+ border-color: var(--primary-color);
140
+ box-shadow: 0 0 0 3px rgba(0, 255, 157, 0.1);
141
+ }
142
+
143
+ button {
144
+ width: 100%;
145
+ padding: 0.875rem;
146
+ background: rgba(0, 255, 157, 0.1);
147
+ border: 1px solid var(--primary-color);
148
+ border-radius: 6px;
149
+ color: var(--primary-color);
150
+ font-size: 1rem;
151
+ font-weight: 500;
152
+ cursor: pointer;
153
+ transition: var(--transition);
154
+ display: flex;
155
+ align-items: center;
156
+ justify-content: center;
157
+ gap: 0.5rem;
158
+ }
159
+
160
+ button:hover {
161
+ background: var(--primary-color);
162
+ color: var(--background-color);
163
+ box-shadow: 0 0 15px rgba(0, 255, 157, 0.3);
164
+ }
165
+
166
+ .error-message {
167
+ background: rgba(255, 45, 85, 0.1);
168
+ border: 1px solid #ff2d55;
169
+ color: #ff2d55;
170
+ padding: 0.75rem 1rem;
171
+ border-radius: 6px;
172
+ margin-bottom: 1.5rem;
173
+ font-size: 0.9rem;
174
+ display: flex;
175
+ align-items: center;
176
+ gap: 0.5rem;
177
+ }
178
+
179
+ .footer {
180
+ text-align: center;
181
+ padding: 2rem;
182
+ color: var(--text-secondary);
183
+ font-size: 0.9rem;
184
+ background: rgba(18, 20, 28, 0.5);
185
+ backdrop-filter: blur(10px);
186
+ border-top: 1px solid var(--border-color);
187
+ }
188
+
189
+ .footer a {
190
+ color: var(--primary-color);
191
+ text-decoration: none;
192
+ transition: var(--transition);
193
+ }
194
+
195
+ .footer a:hover {
196
+ text-shadow: 0 0 10px var(--primary-color);
197
+ }
198
+
199
+ @media (max-width: 480px) {
200
+ .container {
201
+ padding: 1rem;
202
+ }
203
+
204
+ .login-card {
205
+ padding: 1.5rem;
206
+ }
207
+
208
+ .logo {
209
+ font-size: 2rem;
210
+ }
211
+ }
212
+ </style>
213
+ </head>
214
+ <body>
215
+ <div class="container">
216
+ <div class="logo">
217
+ <i class="fas fa-server"></i>
218
+ HF Space Manager
219
+ </div>
220
+ <form method="post" class="login-card">
221
+ <div class="form-group">
222
+ <label for="username">用户名</label>
223
+ <input type="text" id="username" name="username" required autocomplete="username">
224
+ </div>
225
+ <div class="form-group">
226
+ <label for="password">密码</label>
227
+ <input type="password" id="password" name="password" required autocomplete="current-password">
228
+ </div>
229
+ {% if error %}
230
+ <div class="error-message">
231
+ <i class="fas fa-exclamation-circle"></i>
232
+ {{ error }}
233
+ </div>
234
+ {% endif %}
235
+ <button type="submit">
236
+ <i class="fas fa-sign-in-alt"></i>
237
+ 登录
238
+ </button>
239
+ </form>
240
+ </div>
241
+
242
+ <footer class="footer">
243
+ <a href="https://github.com/ssfun/hf-space-manager">HF Space Manager</a> 由
244
+ <a href="https://github.com/ssfun">ssfun</a> 构建,源代码遵循
245
+ <a href="https://github.com/zhanghxiao">MistPeak</a> 二次开发遵循
246
+ <a href="https://opensource.org/license/mit">MIT 协议</a>
247
+ </footer>
248
+ </body>
249
+ </html>