asemxin commited on
Commit
776bfbd
·
1 Parent(s): 784ab0d

Add status page with nginx proxy

Browse files
Files changed (3) hide show
  1. .gitignore +13 -0
  2. Dockerfile +44 -7
  3. index.html +396 -0
.gitignore ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Local scripts with secrets
2
+ local_refresh.py
3
+ get_cookie.py
4
+ deploy.py
5
+
6
+ # Python
7
+ __pycache__/
8
+ *.pyc
9
+ *.pyo
10
+
11
+ # Editor
12
+ .vscode/
13
+ .idea/
Dockerfile CHANGED
@@ -1,5 +1,5 @@
1
  # Stage 1: Build from source
2
- FROM golang:1.23-alpine AS builder
3
 
4
  RUN apk add --no-cache git ca-certificates
5
 
@@ -12,19 +12,56 @@ RUN git clone https://github.com/ycvk/monica-proxy.git .
12
  RUN go mod download
13
  RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o monica-proxy main.go
14
 
15
- # Stage 2: Runtime
16
  FROM alpine:latest
17
 
18
- RUN apk --no-cache add ca-certificates tzdata
19
 
20
  WORKDIR /app
21
 
22
  COPY --from=builder /app/monica-proxy .
 
23
 
24
- # HF Spaces uses port 7860
25
- ENV SERVER_PORT=7860
26
- ENV SERVER_HOST=0.0.0.0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
 
28
  EXPOSE 7860
29
 
30
- CMD ["./monica-proxy"]
 
1
  # Stage 1: Build from source
2
+ FROM golang:1.25-alpine AS builder
3
 
4
  RUN apk add --no-cache git ca-certificates
5
 
 
12
  RUN go mod download
13
  RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o monica-proxy main.go
14
 
15
+ # Stage 2: Runtime with nginx
16
  FROM alpine:latest
17
 
18
+ RUN apk --no-cache add ca-certificates tzdata nginx
19
 
20
  WORKDIR /app
21
 
22
  COPY --from=builder /app/monica-proxy .
23
+ COPY index.html /usr/share/nginx/html/
24
 
25
+ # nginx config
26
+ RUN cat > /etc/nginx/http.d/default.conf << 'EOF'
27
+ server {
28
+ listen 7860;
29
+
30
+ # Static files (status page)
31
+ location = / {
32
+ root /usr/share/nginx/html;
33
+ try_files /index.html =404;
34
+ }
35
+
36
+ location = /index.html {
37
+ root /usr/share/nginx/html;
38
+ }
39
+
40
+ # Proxy all API requests to monica-proxy
41
+ location / {
42
+ proxy_pass http://127.0.0.1:8080;
43
+ proxy_http_version 1.1;
44
+ proxy_set_header Host $host;
45
+ proxy_set_header X-Real-IP $remote_addr;
46
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
47
+ proxy_set_header X-Forwarded-Proto $scheme;
48
+ proxy_buffering off;
49
+ proxy_read_timeout 300s;
50
+ }
51
+ }
52
+ EOF
53
+
54
+ # Start script
55
+ RUN cat > /app/start.sh << 'EOF'
56
+ #!/bin/sh
57
+ # Start monica-proxy in background on port 8080
58
+ SERVER_PORT=8080 SERVER_HOST=0.0.0.0 ./monica-proxy &
59
+ # Start nginx in foreground
60
+ nginx -g 'daemon off;'
61
+ EOF
62
+
63
+ RUN chmod +x /app/start.sh
64
 
65
  EXPOSE 7860
66
 
67
+ CMD ["/app/start.sh"]
index.html ADDED
@@ -0,0 +1,396 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>Monica Proxy - Cookie 状态检测</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com">
8
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
9
+ <style>
10
+ :root {
11
+ --bg-primary: #0a0a0f;
12
+ --bg-secondary: #12121a;
13
+ --bg-card: rgba(255, 255, 255, 0.03);
14
+ --border-color: rgba(255, 255, 255, 0.08);
15
+ --text-primary: #f5f5f7;
16
+ --text-secondary: #8e8e93;
17
+ --accent-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
18
+ --success-color: #30d158;
19
+ --error-color: #ff453a;
20
+ --warning-color: #ffd60a;
21
+ }
22
+
23
+ * {
24
+ margin: 0;
25
+ padding: 0;
26
+ box-sizing: border-box;
27
+ }
28
+
29
+ body {
30
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
31
+ background: var(--bg-primary);
32
+ color: var(--text-primary);
33
+ min-height: 100vh;
34
+ display: flex;
35
+ align-items: center;
36
+ justify-content: center;
37
+ padding: 20px;
38
+ background-image:
39
+ radial-gradient(ellipse 80% 50% at 50% -20%, rgba(102, 126, 234, 0.15), transparent),
40
+ radial-gradient(ellipse 60% 40% at 100% 100%, rgba(118, 75, 162, 0.1), transparent);
41
+ }
42
+
43
+ .container {
44
+ width: 100%;
45
+ max-width: 520px;
46
+ }
47
+
48
+ .card {
49
+ background: var(--bg-card);
50
+ border: 1px solid var(--border-color);
51
+ border-radius: 20px;
52
+ padding: 40px;
53
+ backdrop-filter: blur(20px);
54
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
55
+ }
56
+
57
+ .header {
58
+ text-align: center;
59
+ margin-bottom: 36px;
60
+ }
61
+
62
+ .logo {
63
+ font-size: 48px;
64
+ margin-bottom: 16px;
65
+ display: block;
66
+ }
67
+
68
+ h1 {
69
+ font-size: 24px;
70
+ font-weight: 600;
71
+ background: var(--accent-gradient);
72
+ -webkit-background-clip: text;
73
+ -webkit-text-fill-color: transparent;
74
+ background-clip: text;
75
+ margin-bottom: 8px;
76
+ }
77
+
78
+ .subtitle {
79
+ color: var(--text-secondary);
80
+ font-size: 14px;
81
+ }
82
+
83
+ .form-group {
84
+ margin-bottom: 20px;
85
+ }
86
+
87
+ label {
88
+ display: block;
89
+ font-size: 13px;
90
+ font-weight: 500;
91
+ color: var(--text-secondary);
92
+ margin-bottom: 8px;
93
+ }
94
+
95
+ input {
96
+ width: 100%;
97
+ padding: 14px 16px;
98
+ background: var(--bg-secondary);
99
+ border: 1px solid var(--border-color);
100
+ border-radius: 12px;
101
+ color: var(--text-primary);
102
+ font-size: 14px;
103
+ font-family: inherit;
104
+ transition: all 0.2s ease;
105
+ }
106
+
107
+ input:focus {
108
+ outline: none;
109
+ border-color: #667eea;
110
+ box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.15);
111
+ }
112
+
113
+ input::placeholder {
114
+ color: var(--text-secondary);
115
+ opacity: 0.6;
116
+ }
117
+
118
+ .btn {
119
+ width: 100%;
120
+ padding: 16px;
121
+ background: var(--accent-gradient);
122
+ border: none;
123
+ border-radius: 12px;
124
+ color: white;
125
+ font-size: 15px;
126
+ font-weight: 600;
127
+ font-family: inherit;
128
+ cursor: pointer;
129
+ transition: all 0.2s ease;
130
+ margin-top: 8px;
131
+ }
132
+
133
+ .btn:hover {
134
+ transform: translateY(-2px);
135
+ box-shadow: 0 8px 24px rgba(102, 126, 234, 0.35);
136
+ }
137
+
138
+ .btn:active {
139
+ transform: translateY(0);
140
+ }
141
+
142
+ .btn:disabled {
143
+ opacity: 0.6;
144
+ cursor: not-allowed;
145
+ transform: none;
146
+ }
147
+
148
+ .status-panel {
149
+ margin-top: 28px;
150
+ padding: 20px;
151
+ background: var(--bg-secondary);
152
+ border-radius: 14px;
153
+ border: 1px solid var(--border-color);
154
+ }
155
+
156
+ .status-header {
157
+ display: flex;
158
+ align-items: center;
159
+ justify-content: space-between;
160
+ margin-bottom: 16px;
161
+ }
162
+
163
+ .status-label {
164
+ font-size: 13px;
165
+ font-weight: 500;
166
+ color: var(--text-secondary);
167
+ }
168
+
169
+ .status-badge {
170
+ display: flex;
171
+ align-items: center;
172
+ gap: 6px;
173
+ padding: 6px 12px;
174
+ border-radius: 20px;
175
+ font-size: 12px;
176
+ font-weight: 600;
177
+ }
178
+
179
+ .status-badge.idle {
180
+ background: rgba(142, 142, 147, 0.15);
181
+ color: var(--text-secondary);
182
+ }
183
+
184
+ .status-badge.loading {
185
+ background: rgba(255, 214, 10, 0.15);
186
+ color: var(--warning-color);
187
+ }
188
+
189
+ .status-badge.success {
190
+ background: rgba(48, 209, 88, 0.15);
191
+ color: var(--success-color);
192
+ }
193
+
194
+ .status-badge.error {
195
+ background: rgba(255, 69, 58, 0.15);
196
+ color: var(--error-color);
197
+ }
198
+
199
+ .status-dot {
200
+ width: 8px;
201
+ height: 8px;
202
+ border-radius: 50%;
203
+ background: currentColor;
204
+ }
205
+
206
+ .status-badge.loading .status-dot {
207
+ animation: pulse 1.5s ease-in-out infinite;
208
+ }
209
+
210
+ @keyframes pulse {
211
+ 0%, 100% { opacity: 1; }
212
+ 50% { opacity: 0.4; }
213
+ }
214
+
215
+ .status-details {
216
+ font-size: 13px;
217
+ color: var(--text-secondary);
218
+ line-height: 1.6;
219
+ }
220
+
221
+ .status-details pre {
222
+ background: rgba(0, 0, 0, 0.3);
223
+ padding: 12px;
224
+ border-radius: 8px;
225
+ overflow-x: auto;
226
+ font-family: 'SF Mono', 'Consolas', monospace;
227
+ font-size: 12px;
228
+ margin-top: 12px;
229
+ white-space: pre-wrap;
230
+ word-break: break-all;
231
+ }
232
+
233
+ .model-list {
234
+ margin-top: 12px;
235
+ }
236
+
237
+ .model-item {
238
+ display: flex;
239
+ align-items: center;
240
+ gap: 8px;
241
+ padding: 8px 12px;
242
+ background: rgba(255, 255, 255, 0.03);
243
+ border-radius: 8px;
244
+ margin-bottom: 6px;
245
+ font-size: 13px;
246
+ }
247
+
248
+ .model-icon {
249
+ font-size: 16px;
250
+ }
251
+
252
+ .model-name {
253
+ flex: 1;
254
+ font-family: 'SF Mono', 'Consolas', monospace;
255
+ }
256
+
257
+ .footer {
258
+ text-align: center;
259
+ margin-top: 24px;
260
+ font-size: 12px;
261
+ color: var(--text-secondary);
262
+ }
263
+
264
+ .footer a {
265
+ color: #667eea;
266
+ text-decoration: none;
267
+ }
268
+
269
+ .footer a:hover {
270
+ text-decoration: underline;
271
+ }
272
+ </style>
273
+ </head>
274
+ <body>
275
+ <div class="container">
276
+ <div class="card">
277
+ <div class="header">
278
+ <span class="logo">🤖</span>
279
+ <h1>Monica Proxy</h1>
280
+ <p class="subtitle">Cookie 状态检测工具</p>
281
+ </div>
282
+
283
+ <div class="form-group">
284
+ <label for="apiUrl">API 地址</label>
285
+ <input type="text" id="apiUrl" value="https://asem12345-monica-proxy.hf.space" placeholder="输入 API 地址">
286
+ </div>
287
+
288
+ <div class="form-group">
289
+ <label for="bearerToken">Bearer Token</label>
290
+ <input type="password" id="bearerToken" placeholder="输入你的 Bearer Token">
291
+ </div>
292
+
293
+ <button class="btn" id="checkBtn" onclick="checkStatus()">
294
+ 检测 Cookie 状态
295
+ </button>
296
+
297
+ <div class="status-panel">
298
+ <div class="status-header">
299
+ <span class="status-label">状态</span>
300
+ <div class="status-badge idle" id="statusBadge">
301
+ <span class="status-dot"></span>
302
+ <span id="statusText">等待检测</span>
303
+ </div>
304
+ </div>
305
+ <div class="status-details" id="statusDetails">
306
+ 点击上方按钮检测 Monica Proxy 的 Cookie 是否可用。
307
+ </div>
308
+ </div>
309
+ </div>
310
+
311
+ <div class="footer">
312
+ Powered by <a href="https://github.com/ycvk/monica-proxy" target="_blank">ycvk/monica-proxy</a>
313
+ </div>
314
+ </div>
315
+
316
+ <script>
317
+ async function checkStatus() {
318
+ const apiUrl = document.getElementById('apiUrl').value.trim().replace(/\/$/, '');
319
+ const bearerToken = document.getElementById('bearerToken').value.trim();
320
+ const btn = document.getElementById('checkBtn');
321
+ const statusBadge = document.getElementById('statusBadge');
322
+ const statusText = document.getElementById('statusText');
323
+ const statusDetails = document.getElementById('statusDetails');
324
+
325
+ if (!apiUrl) {
326
+ alert('请输入 API 地址');
327
+ return;
328
+ }
329
+
330
+ if (!bearerToken) {
331
+ alert('请输入 Bearer Token');
332
+ return;
333
+ }
334
+
335
+ // Loading state
336
+ btn.disabled = true;
337
+ btn.textContent = '检测中...';
338
+ statusBadge.className = 'status-badge loading';
339
+ statusText.textContent = '检测中';
340
+ statusDetails.innerHTML = '正在连接到 Monica Proxy...';
341
+
342
+ try {
343
+ const response = await fetch(`${apiUrl}/v1/models`, {
344
+ method: 'GET',
345
+ headers: {
346
+ 'Authorization': `Bearer ${bearerToken}`,
347
+ 'Content-Type': 'application/json'
348
+ }
349
+ });
350
+
351
+ const data = await response.json();
352
+
353
+ if (response.ok && data.data && data.data.length > 0) {
354
+ // Success
355
+ statusBadge.className = 'status-badge success';
356
+ statusText.textContent = 'Cookie 可用';
357
+
358
+ let modelsHtml = `<p>✅ Cookie 工作正常!检测到 <strong>${data.data.length}</strong> 个可用模型:</p><div class="model-list">`;
359
+ data.data.slice(0, 10).forEach(model => {
360
+ modelsHtml += `<div class="model-item"><span class="model-icon">🧠</span><span class="model-name">${model.id}</span></div>`;
361
+ });
362
+ if (data.data.length > 10) {
363
+ modelsHtml += `<div class="model-item" style="color: var(--text-secondary);">... 还有 ${data.data.length - 10} 个模型</div>`;
364
+ }
365
+ modelsHtml += '</div>';
366
+ statusDetails.innerHTML = modelsHtml;
367
+ } else if (response.status === 401) {
368
+ // Unauthorized
369
+ statusBadge.className = 'status-badge error';
370
+ statusText.textContent = 'Token 错误';
371
+ statusDetails.innerHTML = `<p>❌ Bearer Token 验证失败</p><pre>${JSON.stringify(data, null, 2)}</pre>`;
372
+ } else {
373
+ // Other error - possibly cookie issue
374
+ statusBadge.className = 'status-badge error';
375
+ statusText.textContent = 'Cookie 失效';
376
+ statusDetails.innerHTML = `<p>❌ Cookie 可能已失效或存在其他问题</p><pre>${JSON.stringify(data, null, 2)}</pre>`;
377
+ }
378
+ } catch (error) {
379
+ statusBadge.className = 'status-badge error';
380
+ statusText.textContent = '连接失败';
381
+ statusDetails.innerHTML = `<p>❌ 无法连接到 API</p><pre>${error.message}</pre><p style="margin-top: 12px; color: var(--text-secondary);">可能原因:网络问题、CORS 限制、或服务不可用</p>`;
382
+ } finally {
383
+ btn.disabled = false;
384
+ btn.textContent = '检测 Cookie 状态';
385
+ }
386
+ }
387
+
388
+ // Allow Enter key to trigger check
389
+ document.getElementById('bearerToken').addEventListener('keypress', function(e) {
390
+ if (e.key === 'Enter') {
391
+ checkStatus();
392
+ }
393
+ });
394
+ </script>
395
+ </body>
396
+ </html>