i0110 commited on
Commit
cd42879
·
verified ·
1 Parent(s): 5d52c1c

Update server.js

Browse files
Files changed (1) hide show
  1. server.js +225 -285
server.js CHANGED
@@ -1,195 +1,206 @@
1
  const express = require('express');
2
- const path = require('path');
3
  const axios = require('axios');
 
4
  const crypto = require('crypto');
5
  const app = express();
6
  const port = process.env.PORT || 8080;
7
 
8
- // 启用 JSON 和 URL-encoded 请求解析
9
- app.use(express.json());
10
- app.use(express.urlencoded({ extended: true }));
11
-
12
- // 从环境变量获取 HuggingFace 用户名和对应的 API Token 映射
13
- const userTokenMapping = {};
14
- const usernames = [];
15
- const hfUserConfig = process.env.HF_USER || '';
16
- if (hfUserConfig) {
17
- hfUserConfig.split(',').forEach(pair => {
18
- const parts = pair.split(':').map(part => part.trim());
19
- const username = parts[0];
20
- const token = parts[1] || '';
21
- if (username) {
22
- usernames.push(username);
23
- if (token) {
24
- userTokenMapping[username] = token;
25
- }
26
- }
27
- });
28
- }
29
-
30
- // 从环境变量获取登录凭据
31
- const ADMIN_USERNAME = process.env.USER_NAME || 'admin';
32
- const ADMIN_PASSWORD = process.env.USER_PASSWORD || 'password';
33
-
34
- // 存储会话 token 的简单内存数据库(生产环境中应使用数据库或 Redis)
35
- const sessions = new Map();
36
- const SESSION_TIMEOUT = 24 * 60 * 60 * 1000; // 24小时超时
37
 
38
- // 缓存管理
39
- class SpaceCache {
40
- constructor() {
41
- this.spaces = {};
42
- this.lastUpdate = null;
43
- }
44
 
45
- updateAll(spacesData) {
46
- this.spaces = spacesData.reduce((acc, space) => ({ ...acc, [space.repo_id]: space }), {});
47
- this.lastUpdate = Date.now();
48
- }
49
 
50
- getAll() {
51
- return Object.values(this.spaces);
52
- }
53
 
54
- isExpired(expireMinutes = 5) {
55
- if (!this.lastUpdate) return true;
56
- return (Date.now() - this.lastUpdate) > (expireMinutes * 60 * 1000);
 
 
 
57
  }
 
58
  }
59
 
60
- const spaceCache = new SpaceCache();
61
-
62
- // 提供静态文件(前端文件)
63
- app.use(express.static(path.join(__dirname, 'public')));
64
-
65
- // 提供配置信息的 API 接口
66
- app.get('/api/config', (req, res) => {
67
- res.json({ usernames: usernames.join(',') });
68
- });
69
-
70
- // 登录 API 接口
71
  app.post('/api/login', (req, res) => {
72
  const { username, password } = req.body;
73
- if (username === ADMIN_USERNAME && password === ADMIN_PASSWORD) {
74
- // 生成一个随机 token 作为会话标识
 
75
  const token = crypto.randomBytes(16).toString('hex');
76
- const expiresAt = Date.now() + SESSION_TIMEOUT;
77
- sessions.set(token, { username, expiresAt });
78
- console.log(`用户 ${username} 登录成功,生成 token: ${token.slice(0, 8)}...`);
79
  res.json({ success: true, token });
80
  } else {
81
- console.log(`用户 ${username} 登录失败,凭据无效`);
82
- res.status(401).json({ success: false, message: '用户名或密码错误' });
83
  }
84
  });
85
 
86
- // 验证登录状态 API 接口
87
- app.post('/api/verify-token', (req, res) => {
88
- const { token } = req.body;
89
- const session = sessions.get(token);
90
- if (session && session.expiresAt > Date.now()) {
91
- res.json({ success: true, message: 'Token 有效' });
92
- } else {
93
- if (session) {
94
- sessions.delete(token); // 删除过期的 token
95
- console.log(`Token ${token.slice(0, 8)}... 已过期,已删除`);
96
- }
97
- res.status(401).json({ success: false, message: 'Token 无效或已过期' });
98
- }
99
  });
100
 
101
- // 登出 API 接口
102
- app.post('/api/logout', (req, res) => {
103
- const { token } = req.body;
104
- sessions.delete(token);
105
- console.log(`Token ${token.slice(0, 8)}... 已手动登出`);
106
- res.json({ success: true, message: '登出成功' });
107
- });
 
 
 
 
 
 
 
108
 
109
- // 中间件:验证请求中的 token
110
- const authenticateToken = (req, res, next) => {
111
- const authHeader = req.headers['authorization'];
112
- if (!authHeader || !authHeader.startsWith('Bearer ')) {
113
- return res.status(401).json({ error: '未提供有效的认证令牌' });
 
114
  }
115
- const token = authHeader.split(' ')[1];
116
- const session = sessions.get(token);
117
- if (session && session.expiresAt > Date.now()) {
118
- req.session = session;
119
- next();
120
- } else {
121
- if (session) {
122
- sessions.delete(token); // 删除过期的 token
123
- console.log(`Token ${token.slice(0, 8)}... 已过期,拒绝访问`);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
  }
125
- return res.status(401).json({ error: '认证令牌无效或已过期' });
126
  }
127
- };
 
128
 
129
- // 获取所有 spaces 列表(包括私有)
130
- app.get('/api/proxy/spaces', async (req, res) => {
 
 
 
 
 
 
131
  try {
132
- if (!spaceCache.isExpired()) {
133
- console.log('从缓存获取 Spaces 数据');
134
- return res.json(spaceCache.getAll());
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
  }
 
 
 
136
 
137
- const allSpaces = [];
138
- for (const username of usernames) {
139
- const token = userTokenMapping[username];
140
- if (!token) {
141
- console.warn(`用户 ${username} 没有配置 API Token,将尝试无认证访问公开数据`);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
  }
143
 
144
- try {
145
- // 调用 HuggingFace API 获取 Spaces 列表
146
- const headers = token ? { 'Authorization': `Bearer ${token}` } : {};
147
- const response = await axios.get(`https://huggingface.co/api/spaces?author=${username}`, { headers });
148
- const spaces = response.data;
149
- console.log(`获取到 ${spaces.length} 个 Spaces for ${username}`);
150
-
151
- for (const space of spaces) {
152
- try {
153
- // 获取 Space 详细信息
154
- const spaceInfoResponse = await axios.get(`https://huggingface.co/api/spaces/${space.id}`, { headers });
155
- const spaceInfo = spaceInfoResponse.data;
156
- const spaceRuntime = spaceInfo.runtime || {};
157
-
158
- allSpaces.push({
159
- repo_id: spaceInfo.id,
160
- name: spaceInfo.cardData?.title || spaceInfo.id.split('/')[1],
161
- owner: spaceInfo.author,
162
- username: username,
163
- token: token || '',
164
- url: `https://${spaceInfo.author}-${spaceInfo.id.split('/')[1]}.hf.space`,
165
- status: spaceRuntime.stage || 'unknown',
166
- last_modified: spaceInfo.lastModified || 'unknown',
167
- created_at: spaceInfo.createdAt || 'unknown',
168
- sdk: spaceInfo.sdk || 'unknown',
169
- tags: spaceInfo.tags || [],
170
- private: spaceInfo.private || false,
171
- app_port: spaceInfo.cardData?.app_port || 'unknown'
172
- });
173
- } catch (error) {
174
- console.error(`处理 Space ${space.id} 失败:`, error.message);
175
- }
176
- }
177
- } catch (error) {
178
- console.error(`获取 Spaces 列表失败 for ${username}:`, error.message);
179
  }
 
 
 
 
180
  }
 
181
 
182
- allSpaces.sort((a, b) => a.name.localeCompare(b.name));
183
- spaceCache.updateAll(allSpaces);
184
- console.log(`总共获取到 ${allSpaces.length} 个 Spaces`);
185
- res.json(allSpaces);
186
- } catch (error) {
187
- console.error(`代理获取 spaces 列表失败:`, error.message);
188
- res.status(500).json({ error: '获取 spaces 列表失败', details: error.message });
189
- }
 
 
 
 
 
 
 
 
 
190
  });
191
 
192
- // 代理重启 Space(需要认证)
193
  app.post('/api/proxy/restart/:repoId(*)', authenticateToken, async (req, res) => {
194
  try {
195
  const { repoId } = req.params;
@@ -202,21 +213,23 @@ app.post('/api/proxy/restart/:repoId(*)', authenticateToken, async (req, res) =>
202
  }
203
 
204
  const headers = { 'Authorization': `Bearer ${space.token}`, 'Content-Type': 'application/json' };
205
- const response = await axios.post(`https://huggingface.co/api/spaces/${repoId}/restart`, {}, { headers });
206
- console.log(`重启 Space ${repoId} 成功,状态码: ${response.status}`);
207
- res.json({ success: true, message: `Space ${repoId} 重启成功` });
 
 
208
  } catch (error) {
209
- console.error(`重启 space 失败 (${req.params.repoId}):`, error.message);
210
  if (error.response) {
211
- console.error(`状态码: ${error.response.status}, 响应数据:`, error.response.data);
212
- res.status(error.response.status || 500).json({ error: '重启 space 失败', details: error.response.data?.message || error.message });
213
  } else {
214
- res.status(500).json({ error: '重启 space 失败', details: error.message });
215
  }
216
  }
217
  });
218
 
219
- // 代理重建 Space(需要认证)
220
  app.post('/api/proxy/rebuild/:repoId(*)', authenticateToken, async (req, res) => {
221
  try {
222
  const { repoId } = req.params;
@@ -229,23 +242,23 @@ app.post('/api/proxy/rebuild/:repoId(*)', authenticateToken, async (req, res) =>
229
  }
230
 
231
  const headers = { 'Authorization': `Bearer ${space.token}`, 'Content-Type': 'application/json' };
232
- const payload = { factoryReboot: true };
233
- console.log(`发送重建请求,payload: ${JSON.stringify(payload)}`);
234
  const response = await axios.post(`https://huggingface.co/api/spaces/${repoId}/restart`, payload, { headers });
235
- console.log(`重建 Space ${repoId} 成功,状态码: ${response.status}`);
236
- res.json({ success: true, message: `Space ${repoId} 重建成功` });
237
  } catch (error) {
238
- console.error(`重建 space 失败 (${req.params.repoId}):`, error.message);
239
  if (error.response) {
240
- console.error(`状态码: ${error.response.status}, 响应数据:`, error.response.data);
241
- res.status(error.response.status || 500).json({ error: '重建 space 失败', details: error.response.data?.message || error.message });
242
  } else {
243
- res.status(500).json({ error: '重建 space 失败', details: error.message });
244
  }
245
  }
246
  });
247
 
248
- // 外部 API 服务(类似于 Flask 的 /api/v1)
249
  app.get('/api/v1/info/:token', async (req, res) => {
250
  try {
251
  const { token } = req.params;
@@ -254,29 +267,22 @@ app.get('/api/v1/info/:token', async (req, res) => {
254
  return res.status(401).json({ error: '无效的 API 密钥' });
255
  }
256
 
257
- const headers = { 'Authorization': `Bearer ${token}` };
258
- const userInfoResponse = await axios.get('https://huggingface.co/api/whoami-v2', { headers });
259
- const username = userInfoResponse.data.name;
260
- const spacesResponse = await axios.get(`https://huggingface.co/api/spaces?author=${username}`, { headers });
261
- const spaces = spacesResponse.data;
262
- const spaceList = [];
263
-
264
- for (const space of spaces) {
265
- try {
266
- const spaceInfoResponse = await axios.get(`https://huggingface.co/api/spaces/${space.id}`, { headers });
267
- spaceList.push(spaceInfoResponse.data.id);
268
- } catch (error) {
269
- console.error(`获取 Space 信息失败 (${space.id}):`, error.message);
270
- }
271
- }
272
-
273
- res.json({ spaces: spaceList, total: spaceList.length });
274
  } catch (error) {
275
- console.error(`获取 spaces 列表失败 (外部 API):`, error.message);
276
- res.status(500).json({ error: error.message });
 
 
 
 
277
  }
278
  });
279
 
 
280
  app.get('/api/v1/info/:token/:spaceId(*)', async (req, res) => {
281
  try {
282
  const { token, spaceId } = req.params;
@@ -285,26 +291,20 @@ app.get('/api/v1/info/:token/:spaceId(*)', async (req, res) => {
285
  return res.status(401).json({ error: '无效的 API 密钥' });
286
  }
287
 
288
- const headers = { 'Authorization': `Bearer ${token}` };
289
- const spaceInfoResponse = await axios.get(`https://huggingface.co/api/spaces/${spaceId}`, { headers });
290
- const spaceInfo = spaceInfoResponse.data;
291
- const spaceRuntime = spaceInfo.runtime || {};
292
-
293
- res.json({
294
- id: spaceInfo.id,
295
- status: spaceRuntime.stage || 'unknown',
296
- last_modified: spaceInfo.lastModified || null,
297
- created_at: spaceInfo.createdAt || null,
298
- sdk: spaceInfo.sdk || 'unknown',
299
- tags: spaceInfo.tags || [],
300
- private: spaceInfo.private || false
301
- });
302
  } catch (error) {
303
- console.error(`获取 space 信息失败 (外部 API):`, error.message);
304
- res.status(error.response?.status || 404).json({ error: error.message });
 
 
 
 
305
  }
306
  });
307
 
 
308
  app.post('/api/v1/action/:token/:spaceId(*)/restart', async (req, res) => {
309
  try {
310
  const { token, spaceId } = req.params;
@@ -314,14 +314,23 @@ app.post('/api/v1/action/:token/:spaceId(*)/restart', async (req, res) => {
314
  }
315
 
316
  const headers = { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' };
317
- await axios.post(`https://huggingface.co/api/spaces/${spaceId}/restart`, {}, { headers });
 
 
 
318
  res.json({ success: true, message: `Space ${spaceId} 重启成功` });
319
  } catch (error) {
320
- console.error(`重启 space 失败 (外部 API):`, error.message);
321
- res.status(error.response?.status || 500).json({ success: false, error: error.message });
 
 
 
 
 
322
  }
323
  });
324
 
 
325
  app.post('/api/v1/action/:token/:spaceId(*)/rebuild', async (req, res) => {
326
  try {
327
  const { token, spaceId } = req.params;
@@ -331,15 +340,15 @@ app.post('/api/v1/action/:token/:spaceId(*)/rebuild', async (req, res) => {
331
  }
332
 
333
  const headers = { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' };
334
- const payload = { factoryReboot: true };
335
- console.log(`外部 API 发送重建请求,spaceId: ${spaceId}`);
336
  const response = await axios.post(`https://huggingface.co/api/spaces/${spaceId}/restart`, payload, { headers });
337
- console.log(`外部 API 重建 Space ${spaceId} 成功,状态码: ${response.status}`);
338
  res.json({ success: true, message: `Space ${spaceId} 重建成功` });
339
  } catch (error) {
340
- console.error(`重建 space 失败 (外部 API):`, error.message);
341
  if (error.response) {
342
- console.error(`状态码: ${error.response.status}, 响应数据:`, error.response.data);
343
  res.status(error.response.status || 500).json({ success: false, error: error.response.data?.message || error.message });
344
  } else {
345
  res.status(500).json({ success: false, error: error.message });
@@ -347,76 +356,7 @@ app.post('/api/v1/action/:token/:spaceId(*)/rebuild', async (req, res) => {
347
  }
348
  });
349
 
350
- // 代理 HuggingFace API:获取实时监控数据(SSE)
351
- app.get('/api/proxy/live-metrics/:username/:instanceId', async (req, res) => {
352
- try {
353
- const { username, instanceId } = req.params;
354
- const url = `https://api.hf.space/v1/${username}/${instanceId}/live-metrics/sse`;
355
-
356
- // 检查实例状态,决定是否继续请求
357
- const spaces = spaceCache.getAll();
358
- const space = spaces.find(s => s.repo_id === `${username}/${instanceId}`);
359
- if (!space) {
360
- console.log(`实例 ${username}/${instanceId} 未找到,不尝试获取监控数据`);
361
- return res.status(404).json({ error: '实例未找到,无法获取监控数据' });
362
- }
363
- if (space.status.toLowerCase() !== 'running') {
364
- console.log(`实例 ${username}/${instanceId} 状态为 ${space.status},不尝试获取监控数据`);
365
- return res.status(400).json({ error: '实例未运行,无法获取监控数据' });
366
- }
367
-
368
- const token = userTokenMapping[username];
369
- let headers = {
370
- 'Accept': 'text/event-stream',
371
- 'Cache-Control': 'no-cache',
372
- 'Connection': 'keep-alive'
373
- };
374
- if (token) {
375
- headers['Authorization'] = `Bearer ${token}`;
376
- }
377
-
378
- const response = await axios({
379
- method: 'get',
380
- url,
381
- headers,
382
- responseType: 'stream',
383
- timeout: 10000
384
- });
385
-
386
- res.set({
387
- 'Content-Type': 'text/event-stream',
388
- 'Cache-Control': 'no-cache',
389
- 'Connection': 'keep-alive'
390
- });
391
- response.data.pipe(res);
392
-
393
- req.on('close', () => {
394
- response.data.destroy();
395
- });
396
- } catch (error) {
397
- console.error(`代理获取直播监控数据失败 (${req.params.username}/${req.params.instanceId}):`, error.message);
398
- res.status(error.response?.status || 500).json({ error: '获取监控数据失败', details: error.message });
399
- }
400
- });
401
-
402
- // 处理其他请求,重定向到 index.html
403
- app.get('*', (req, res) => {
404
- res.sendFile(path.join(__dirname, 'public', 'index.html'));
405
- });
406
-
407
- // 定期清理过期的会话
408
- setInterval(() => {
409
- const now = Date.now();
410
- for (const [token, session] of sessions.entries()) {
411
- if (session.expiresAt < now) {
412
- sessions.delete(token);
413
- console.log(`Token ${token.slice(0, 8)}... 已过期,自动清理`);
414
- }
415
- }
416
- }, 60 * 60 * 1000); // 每小时清理一次
417
-
418
  app.listen(port, () => {
419
- console.log(`Server running on port ${port}`);
420
- console.log(`User configurations:`, usernames.map(user => `${user}: ${userTokenMapping[user] ? 'Token Configured' : 'No Token'}`).join(', ') || 'None');
421
- console.log(`Admin login enabled: Username=${ADMIN_USERNAME}, Password=${ADMIN_PASSWORD ? 'Configured' : 'Not Configured'}`);
422
  });
 
1
  const express = require('express');
 
2
  const axios = require('axios');
3
+ const path = require('path');
4
  const crypto = require('crypto');
5
  const app = express();
6
  const port = process.env.PORT || 8080;
7
 
8
+ // 加载环境变量
9
+ require('dotenv').config();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
+ // 解析 JSON 请求体
12
+ app.use(express.json());
 
 
 
 
13
 
14
+ // 提供静态文件(前端页面)
15
+ app.use(express.static(path.join(__dirname, 'public')));
 
 
16
 
17
+ // 临时会话存储(内存中,实际部署建议使用数据库或 Redis)
18
+ const sessions = {};
 
19
 
20
+ // 认证中间件
21
+ function authenticateToken(req, res, next) {
22
+ const authHeader = req.headers['authorization'];
23
+ const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN
24
+ if (!token || !sessions[token]) {
25
+ return res.status(401).json({ error: '未授权访问' });
26
  }
27
+ next();
28
  }
29
 
30
+ // 登录 API
 
 
 
 
 
 
 
 
 
 
31
  app.post('/api/login', (req, res) => {
32
  const { username, password } = req.body;
33
+ const validUsername = process.env.USER_NAME || 'admin';
34
+ const validPassword = process.env.USER_PASSWORD || 'password';
35
+ if (username === validUsername && password === validPassword) {
36
  const token = crypto.randomBytes(16).toString('hex');
37
+ sessions[token] = { username, timestamp: Date.now() }; // 存储会话
38
+ console.log(`用户登录成功,生成 Token: ${token}`);
 
39
  res.json({ success: true, token });
40
  } else {
41
+ res.status(401).json({ success: false, error: '用户名或密码错误' });
 
42
  }
43
  });
44
 
45
+ // 检查登录状态 API
46
+ app.get('/api/check-login', authenticateToken, (req, res) => {
47
+ res.json({ success: true, loggedIn: true });
 
 
 
 
 
 
 
 
 
 
48
  });
49
 
50
+ // 缓存对象,用于存储空间信息
51
+ const spaceCache = {
52
+ data: [],
53
+ lastUpdated: 0,
54
+ isUpdating: false,
55
+ getAll: function() {
56
+ return this.data;
57
+ },
58
+ update: function(spaces) {
59
+ this.data = spaces;
60
+ this.lastUpdated = Date.now();
61
+ this.isUpdating = false;
62
+ }
63
+ };
64
 
65
+ // 通过 HuggingFace API 获取所有用户的 Space 信息
66
+ async function fetchSpacesFromAPI() {
67
+ const hfUsers = process.env.HF_USER ? process.env.HF_USER.split(',').map(u => u.trim()) : [];
68
+ if (hfUsers.length === 0) {
69
+ console.warn('未配置 HuggingFace 用户(HF_USER 环境变量为空)');
70
+ return [];
71
  }
72
+
73
+ const allSpaces = [];
74
+ for (const userTokenPair of hfUsers) {
75
+ let username, token;
76
+ if (userTokenPair.includes(':')) {
77
+ [username, token] = userTokenPair.split(':').map(s => s.trim());
78
+ } else {
79
+ username = userTokenPair.trim();
80
+ token = '';
81
+ }
82
+ if (!username) continue;
83
+
84
+ try {
85
+ console.log(`正在获取用户 ${username} 的 Space 信息...`);
86
+ const headers = token ? { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' } : {};
87
+ const response = await axios.get(`https://huggingface.co/api/spaces?author=${username}&full=username`, { headers });
88
+ if (Array.isArray(response.data)) {
89
+ const spaces = response.data.map(space => ({
90
+ repo_id: space.id,
91
+ author: space.author || username,
92
+ title: space.title || space.id.split('/')[1],
93
+ token: token || '',
94
+ private: space.private || false
95
+ }));
96
+ console.log(`用户 ${username} 的 Space 获取成功,共找到 ${spaces.length} 个 Space`);
97
+ allSpaces.push(...spaces);
98
+ } else {
99
+ console.warn(`用户 ${username} 的 Space 列表获取失败,API 返回数据不是数组`);
100
+ }
101
+ } catch (error) {
102
+ console.error(`获取用户 ${username} 的 Space 列表时出错:`, error.message);
103
  }
 
104
  }
105
+ return allSpaces;
106
+ }
107
 
108
+ // 定时更新缓存
109
+ async function updateSpaceCache() {
110
+ if (spaceCache.isUpdating) {
111
+ console.log('已有更新任务在运行,跳过本次更新');
112
+ return;
113
+ }
114
+ spaceCache.isUpdating = true;
115
+ console.log('开始更新 Space 信息...');
116
  try {
117
+ const spaces = await fetchSpacesFromAPI();
118
+ spaceCache.update(spaces);
119
+ console.log(`Space 信息更新完成,总计 ${spaces.length} 个 Space`);
120
+ } catch (error) {
121
+ console.error('更新 Space 信息时出错:', error.message);
122
+ spaceCache.isUpdating = false;
123
+ }
124
+ }
125
+
126
+ // 初始更新
127
+ updateSpaceCache();
128
+
129
+ // 每 5 分钟更新一次
130
+ setInterval(updateSpaceCache, 5 * 60 * 1000);
131
+
132
+ // 获取 Space 监控数据
133
+ async function fetchSpaceMetrics(repoId, token) {
134
+ try {
135
+ const headers = token ? { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' } : {};
136
+ const metricsUrl = `https://huggingface.co/api/spaces/${repoId}/metrics`;
137
+ console.log(`正在获取 Space 监控数据: ${repoId}, 使用 Token: ${token ? '已提供' : '未提供'}`);
138
+ const response = await axios.get(metricsUrl, { headers });
139
+ console.log(`Space ${repoId} 监控数据获取成功`);
140
+ return response.data;
141
+ } catch (error) {
142
+ console.error(`获取 Space 监控数据失败 (${repoId}):`, error.message);
143
+ if (error.response) {
144
+ console.error(`状态码: ${error.response.status}, 响应数据:`, error.response.data || '无额外数据');
145
  }
146
+ return null;
147
+ }
148
+ }
149
 
150
+ // 实时监控数据 API(使用 EventSource)
151
+ app.get('/api/monitor', async (req, res) => {
152
+ res.writeHead(200, {
153
+ 'Content-Type': 'text/event-stream',
154
+ 'Cache-Control': 'no-cache',
155
+ 'Connection': 'keep-alive'
156
+ });
157
+
158
+ console.log('客户端已连接,开始实时监控数据传输...');
159
+
160
+ const sendEvent = (data) => {
161
+ res.write(`data: ${JSON.stringify(data)}\n\n`);
162
+ };
163
+
164
+ const intervalId = setInterval(async () => {
165
+ try {
166
+ const spaces = spaceCache.getAll();
167
+ if (spaces.length === 0) {
168
+ sendEvent({ spaces: [], message: '暂无 Space 信息,请配置 HF_USER 环境变量' });
169
+ return;
170
  }
171
 
172
+ const updatedSpaces = [];
173
+ for (const space of spaces) {
174
+ const metrics = await fetchSpaceMetrics(space.repo_id, space.token);
175
+ updatedSpaces.push({ ...space, metrics });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
  }
177
+ sendEvent({ spaces: updatedSpaces });
178
+ } catch (error) {
179
+ console.error('监控数据处理出错:', error.message);
180
+ sendEvent({ error: '监控数据获取失败,请稍后再试' });
181
  }
182
+ }, 10000);
183
 
184
+ req.on('close', () => {
185
+ console.log('客户端断开连接,停止数据传输');
186
+ clearInterval(intervalId);
187
+ res.end();
188
+ });
189
+ });
190
+
191
+ // 获取配置的用户名列表(隐藏 Token)
192
+ app.get('/api/users', (req, res) => {
193
+ const hfUsers = process.env.HF_USER ? process.env.HF_USER.split(',').map(u => u.trim()) : [];
194
+ const usernames = hfUsers.map(userTokenPair => {
195
+ if (userTokenPair.includes(':')) {
196
+ return userTokenPair.split(':')[0].trim();
197
+ }
198
+ return userTokenPair.trim();
199
+ }).filter(username => username);
200
+ res.json({ users: usernames });
201
  });
202
 
203
+ // 代理重启 Space
204
  app.post('/api/proxy/restart/:repoId(*)', authenticateToken, async (req, res) => {
205
  try {
206
  const { repoId } = req.params;
 
213
  }
214
 
215
  const headers = { 'Authorization': `Bearer ${space.token}`, 'Content-Type': 'application/json' };
216
+ const payload = {}; // 普通重启不包含 factory_reboot 参数
217
+ console.log(`发送普通重启请求到 HuggingFace API,repoId: ${repoId}`);
218
+ const response = await axios.post(`https://huggingface.co/api/spaces/${repoId}/restart`, payload, { headers });
219
+ console.log(`重启 Space ${repoId} 请求完成,状态码: ${response.status}, 响应数据: ${JSON.stringify(response.data || {})}`);
220
+ res.json({ success: true, message: `Space ${repoId} 重启请求已发送` });
221
  } catch (error) {
222
+ console.error(`重启 Space 失败 (${req.params.repoId}):`, error.message);
223
  if (error.response) {
224
+ console.error(`状态码: ${error.response.status}, 响应数据:`, error.response.data || '无额外数据');
225
+ res.status(error.response.status || 500).json({ error: '重启 Space 失败', details: error.response.data?.message || error.message });
226
  } else {
227
+ res.status(500).json({ error: '重启 Space 失败', details: error.message });
228
  }
229
  }
230
  });
231
 
232
+ // 代理重建 Space(使用正确的 factory_reboot 字段)
233
  app.post('/api/proxy/rebuild/:repoId(*)', authenticateToken, async (req, res) => {
234
  try {
235
  const { repoId } = req.params;
 
242
  }
243
 
244
  const headers = { 'Authorization': `Bearer ${space.token}`, 'Content-Type': 'application/json' };
245
+ const payload = { factory_reboot: true }; // 修正为 factory_reboot,确保符合 API 规范
246
+ console.log(`发送重建请求到 HuggingFace API,repoId: ${repoId}, payload: ${JSON.stringify(payload)}`);
247
  const response = await axios.post(`https://huggingface.co/api/spaces/${repoId}/restart`, payload, { headers });
248
+ console.log(`重建 Space ${repoId} 请求完成,状态码: ${response.status}, 响应数据: ${JSON.stringify(response.data || {})}`);
249
+ res.json({ success: true, message: `Space ${repoId} 重建请求已发送` });
250
  } catch (error) {
251
+ console.error(`重建 Space 失败 (${req.params.repoId}):`, error.message);
252
  if (error.response) {
253
+ console.error(`状态码: ${error.response.status}, 响应数据:`, error.response.data || '无额外数据');
254
+ res.status(error.response.status || 500).json({ error: '重建 Space 失败', details: error.response.data?.message || error.message });
255
  } else {
256
+ res.status(500).json({ error: '重建 Space 失败', details: error.message });
257
  }
258
  }
259
  });
260
 
261
+ // 外部 API:获取用户信息
262
  app.get('/api/v1/info/:token', async (req, res) => {
263
  try {
264
  const { token } = req.params;
 
267
  return res.status(401).json({ error: '无效的 API 密钥' });
268
  }
269
 
270
+ const headers = { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' };
271
+ const response = await axios.get('https://huggingface.co/api/whoami-v2', { headers });
272
+ const username = response.data.name;
273
+ const spacesResponse = await axios.get(`https://huggingface.co/api/spaces?author=${username}&full=username`, { headers });
274
+ res.json({ spaces: spacesResponse.data.map(s => s.id), total: spacesResponse.data.length });
 
 
 
 
 
 
 
 
 
 
 
 
275
  } catch (error) {
276
+ console.error('外部 API 获取用户信息失败:', error.message);
277
+ if (error.response) {
278
+ res.status(error.response.status || 500).json({ error: error.response.data?.message || error.message });
279
+ } else {
280
+ res.status(500).json({ error: error.message });
281
+ }
282
  }
283
  });
284
 
285
+ // 外部 API:获取 Space 信息
286
  app.get('/api/v1/info/:token/:spaceId(*)', async (req, res) => {
287
  try {
288
  const { token, spaceId } = req.params;
 
291
  return res.status(401).json({ error: '无效的 API 密钥' });
292
  }
293
 
294
+ const headers = { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' };
295
+ const response = await axios.get(`https://huggingface.co/api/spaces/${spaceId}`, { headers });
296
+ res.json(response.data);
 
 
 
 
 
 
 
 
 
 
 
297
  } catch (error) {
298
+ console.error(`外部 API 获取 Space 信息失败 (${req.params.spaceId}):`, error.message);
299
+ if (error.response) {
300
+ res.status(error.response.status || 500).json({ error: error.response.data?.message || error.message });
301
+ } else {
302
+ res.status(500).json({ error: error.message });
303
+ }
304
  }
305
  });
306
 
307
+ // 外部 API:重启 Space
308
  app.post('/api/v1/action/:token/:spaceId(*)/restart', async (req, res) => {
309
  try {
310
  const { token, spaceId } = req.params;
 
314
  }
315
 
316
  const headers = { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' };
317
+ const payload = {}; // 普通重启
318
+ console.log(`外部 API 发送重启请求,spaceId: ${spaceId}`);
319
+ const response = await axios.post(`https://huggingface.co/api/spaces/${spaceId}/restart`, payload, { headers });
320
+ console.log(`外部 API 重启 Space ${spaceId} 请求完成,状态码: ${response.status}, 响应数据: ${JSON.stringify(response.data || {})}`);
321
  res.json({ success: true, message: `Space ${spaceId} 重启成功` });
322
  } catch (error) {
323
+ console.error(`重启 Space 失败 (外部 API):`, error.message);
324
+ if (error.response) {
325
+ console.error(`状态码: ${error.response.status}, 响应数据:`, error.response.data || '无额外数据');
326
+ res.status(error.response.status || 500).json({ success: false, error: error.response.data?.message || error.message });
327
+ } else {
328
+ res.status(500).json({ success: false, error: error.message });
329
+ }
330
  }
331
  });
332
 
333
+ // 外部 API:重建 Space(使用 factory_reboot)
334
  app.post('/api/v1/action/:token/:spaceId(*)/rebuild', async (req, res) => {
335
  try {
336
  const { token, spaceId } = req.params;
 
340
  }
341
 
342
  const headers = { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' };
343
+ const payload = { factory_reboot: true }; // 修正为 factory_reboot
344
+ console.log(`外部 API 发送重建请求,spaceId: ${spaceId}, payload: ${JSON.stringify(payload)}`);
345
  const response = await axios.post(`https://huggingface.co/api/spaces/${spaceId}/restart`, payload, { headers });
346
+ console.log(`外部 API 重建 Space ${spaceId} 请求完成,状态码: ${response.status}, 响应数据: ${JSON.stringify(response.data || {})}`);
347
  res.json({ success: true, message: `Space ${spaceId} 重建成功` });
348
  } catch (error) {
349
+ console.error(`重建 Space 失败 (外部 API):`, error.message);
350
  if (error.response) {
351
+ console.error(`状态码: ${error.response.status}, 响应数据:`, error.response.data || '无额外数据');
352
  res.status(error.response.status || 500).json({ success: false, error: error.response.data?.message || error.message });
353
  } else {
354
  res.status(500).json({ success: false, error: error.message });
 
356
  }
357
  });
358
 
359
+ // 启动服务器
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
360
  app.listen(port, () => {
361
+ console.log(`服务器启动,监听端口: ${port}`);
 
 
362
  });