yangtb24 commited on
Commit
9c5947c
·
verified ·
1 Parent(s): 48d99a6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +154 -125
app.py CHANGED
@@ -6,7 +6,6 @@ import json
6
  app = Flask(__name__)
7
 
8
  lightModeStyle = """
9
- /* ... (Your CSS styles, same as before) ... */
10
  * {
11
  margin: 0;
12
  padding: 0;
@@ -143,10 +142,12 @@ body {
143
  }
144
  .status-online {
145
  background-color: #2ecc71;
 
146
  box-shadow: 0 0 5px rgba(46, 204, 113, 0.4);
147
  }
148
  .status-offline {
149
  background-color: #e74c3c;
 
150
  box-shadow: 0 0 5px rgba(231, 76, 60, 0.4);
151
  }
152
  @keyframes fadeIn {
@@ -195,9 +196,6 @@ body {
195
  }
196
  """
197
 
198
- # Define USERNAME *before* using it in htmlTemplate
199
- USERNAME = os.environ.get("USERNAME", "yangtb24")
200
-
201
  htmlTemplate = f"""
202
  <!DOCTYPE html>
203
  <html lang="zh">
@@ -206,7 +204,6 @@ htmlTemplate = f"""
206
  <title>HF Space Monitor</title>
207
  <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🚀</text></svg>">
208
  <style>{lightModeStyle}</style>
209
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" integrity="sha512-9usAa10IRO0HhonpyAIVpjrylPvoDwiPUiKdWk5t3PyolY1cOd4DSE0Ga+ri4AuTroPR5aQvXU9xC6qOPnzFeg==" crossorigin="anonymous" referrerpolicy="no-referrer" />
210
  </head>
211
  <body>
212
  <div class="container">
@@ -223,8 +220,10 @@ htmlTemplate = f"""
223
  <div id="servers" class="stats-container">
224
  </div>
225
  </div>
 
 
226
  <script>
227
- const username = '{USERNAME}';
228
 
229
  async function fetchInstances() {{
230
  try {{
@@ -237,53 +236,54 @@ htmlTemplate = f"""
237
  }}
238
  }}
239
 
240
- class MetricsManager {{
241
  constructor() {{
242
  this.eventSources = new Map();
243
- this.serversData = new Map();
244
  this.instanceOwners = new Map();
245
  this.spaceIds = new Map();
 
246
  }}
247
 
248
- connect(instanceId, username) {{
249
  if (this.eventSources.has(instanceId)) return;
250
 
251
- const eventSource = new EventSource(`/metrics/${{username}}/${{instanceId}}`);
252
- this.eventSources.set(instanceId, eventSource);
253
- this.spaceIds.set(instanceId, instanceId);
254
- this.instanceOwners.set(instanceId, username);
255
- this.serversData.set(instanceId, {{ lastSeen: 0, uploadBps: 0, downloadBps: 0 }});
256
-
257
- eventSource.addEventListener("metric", (event) => {{
258
- try {{
259
- const data = JSON.parse(event.data);
260
- this.serversData.set(instanceId, {{
261
- lastSeen: Date.now(),
262
- uploadBps: data.tx_bps,
263
- downloadBps: data.rx_bps,
 
264
  }});
265
- updateServerCard(data, instanceId);
266
- }} catch (error) {{
267
- console.error(`解析数据失败 (${{instanceId}}):`, error);
268
- }}
269
- }});
270
-
271
- eventSource.onerror = (error) => {{
272
- console.error(`EventSource 错误 (${{instanceId}}):`, error);
273
- eventSource.close();
274
- this.eventSources.delete(instanceId);
275
- }};
276
 
277
- }}
 
 
 
 
 
 
 
 
 
 
278
 
279
  disconnectAll() {{
280
  this.eventSources.forEach(es => es.close());
281
  this.eventSources.clear();
282
- this.serversData.clear();
283
  }}
284
  }}
285
 
286
  const metricsManager = new MetricsManager();
 
287
 
288
  async function initialize() {{
289
  const instances = await fetchInstances();
@@ -292,100 +292,121 @@ htmlTemplate = f"""
292
  }});
293
  }}
294
 
 
295
  function updateServerCard(data, spaceId) {{
296
- const serverId = data.replica;
297
- let serverElement = document.getElementById(`server-${{serverId}}`);
298
- const owner = metricsManager.instanceOwners.get(spaceId);
299
-
300
- if (!serverElement) {{
301
- serverElement = document.createElement('div');
302
- serverElement.id = `server-${{serverId}}`;
303
- serverElement.className = 'server-card';
304
- serverElement.innerHTML = `
305
- <div class="server-header">
306
- <div class="server-name">
307
- <div class="status-dot status-online"></div>
308
- <svg class="server-flag" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
309
- <path d="M21 3H3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H4V5h16v14z"/>
310
- </svg>
311
- <div>${{serverId}} (${{owner}}/${{spaceId}})</div>
312
- </div>
313
- </div>
314
- <div class="metric-grid">
315
- <div class="metric-item">
316
- <div class="metric-label">CPU</div>
317
- <div class="progress-bar-container">
318
- <div class="cpu-progress-bar"></div>
319
  </div>
320
- <div class="metric-value cpu-usage">0%</div>
321
- </div>
322
- <div class="metric-item">
323
- <div class="metric-label">内存</div>
324
- <div class="progress-bar-container">
325
- <div class="memory-progress-bar"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
326
  </div>
327
- <div class="metric-value memory-usage">0%</div>
328
- </div>
329
- <div class="metric-item">
330
- <div class="metric-label">上传</div>
331
- <div class="metric-value upload">0 KB/s</div>
332
- </div>
333
- <div class="metric-item">
334
- <div class="metric-label">下载</div>
335
- <div class="metric-value download">0 KB/s</div>
336
- </div>
337
- </div>
338
- `;
339
- document.getElementById('servers').appendChild(serverElement);
340
- }}
341
 
342
- const cpuUsage = data.cpu_usage_pct;
343
- const memoryUsage = (data.memory_used_bytes / data.memory_total_bytes) * 100;
344
- const uploadBps = data.tx_bps;
345
- const downloadBps = data.rx_bps;
 
346
 
347
- serverElement.querySelector('.cpu-usage').textContent = `${{cpuUsage.toFixed(2)}}%`;
348
- serverElement.querySelector('.cpu-progress-bar').style.width = `${{cpuUsage}}%`;
349
 
350
- serverElement.querySelector('.memory-usage').textContent = `${{memoryUsage.toFixed(2)}}%`;
351
- serverElement.querySelector('.memory-progress-bar').style.width = `${{memoryUsage}}%`;
352
 
353
- serverElement.querySelector('.upload').textContent = `${{formatBytes(uploadBps)}}/s`;
354
- serverElement.querySelector('.download').textContent = `${{formatBytes(downloadBps)}}/s`;
355
- updateSummary();
 
 
356
  }}
357
 
358
- function updateSummary() {{
359
  const now = Date.now();
360
  let online = 0;
361
  let offline = 0;
362
  let totalUpload = 0;
363
  let totalDownload = 0;
364
 
365
- for (const [serverId, serverData] of metricsManager.serversData) {{
366
- const isOnline = (now - serverData.lastSeen) < 10000;
367
- const serverCard = document.getElementById(`server-${{serverId}}`);
368
 
369
- if (serverCard) {{
 
 
370
  const statusDot = serverCard.querySelector('.status-dot');
371
- statusDot.className = `status-dot status-${{isOnline ? 'online' : 'offline'}}`;
372
- }}
373
-
374
- if (isOnline) {{
375
- online++;
376
- totalUpload += serverData.uploadBps || 0;
377
- totalDownload += serverData.downloadBps || 0;
378
- }} else {{
379
- offline++;
380
- }}
381
- }}
382
-
383
- document.getElementById('totalServers').textContent = metricsManager.serversData.size;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
384
  document.getElementById('onlineServers').textContent = online;
385
  document.getElementById('offlineServers').textContent = offline;
386
- document.getElementById('totalUpload').textContent = `${{formatBytes(totalUpload)}}/s`;
387
- document.getElementById('totalDownload').textContent = `${{formatBytes(totalDownload)}}/s`;
388
- }}
 
389
 
390
  function formatBytes(bytes) {{
391
  if (bytes === 0) return '0 B';
@@ -395,21 +416,36 @@ htmlTemplate = f"""
395
  return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
396
  }}
397
 
398
- initialize();
 
 
399
  setInterval(updateSummary, 2000);
 
400
  setInterval(async () => {{
401
- metricsManager.disconnectAll();
402
- await initialize();
403
  }}, 300000);
 
404
  </script>
405
  </body>
406
  </html>
407
  """
408
 
 
 
 
 
 
 
 
 
 
 
 
409
 
410
  @app.route('/')
411
  def index():
412
- return render_template_string(htmlTemplate)
413
 
414
  @app.route('/instances')
415
  def get_instances():
@@ -434,30 +470,23 @@ def stream_metrics(username, instance_id):
434
  lines = event_data.split("\n")
435
  event_type = "message"
436
  data_lines = []
437
-
438
  for line in lines:
439
  if line.startswith("event:"):
440
  event_type = line.split(":", 1)[1].strip()
441
  elif line.startswith("data:"):
442
  data_lines.append(line.split(":", 1)[1].strip())
443
-
444
  if event_type == "metric":
445
- try:
446
- json_data = json.loads("".join(data_lines))
447
- yield f"event: {event_type}\ndata: {json.dumps(json_data)}\n\n"
448
- except json.JSONDecodeError as e:
449
- print(f"JSONDecodeError: {e}")
450
- yield f"event: error\ndata: Invalid JSON received\n\n"
451
 
452
  except requests.exceptions.RequestException as e:
453
  print(f"Request Exception: {e}")
454
- yield f"event: error\ndata: Connection error\n\n"
455
  except Exception as e:
456
  print(f"An error occurred: {e}")
457
- yield f"event: error\ndata: An unexpected error occurred\n\n"
458
 
459
  return Response(generate(), mimetype='text/event-stream')
460
 
461
-
462
  if __name__ == '__main__':
463
  app.run(debug=True, host='0.0.0.0', port=7860)
 
6
  app = Flask(__name__)
7
 
8
  lightModeStyle = """
 
9
  * {
10
  margin: 0;
11
  padding: 0;
 
142
  }
143
  .status-online {
144
  background-color: #2ecc71;
145
+ color: #2ecc71;
146
  box-shadow: 0 0 5px rgba(46, 204, 113, 0.4);
147
  }
148
  .status-offline {
149
  background-color: #e74c3c;
150
+ color: #e74c3c;
151
  box-shadow: 0 0 5px rgba(231, 76, 60, 0.4);
152
  }
153
  @keyframes fadeIn {
 
196
  }
197
  """
198
 
 
 
 
199
  htmlTemplate = f"""
200
  <!DOCTYPE html>
201
  <html lang="zh">
 
204
  <title>HF Space Monitor</title>
205
  <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🚀</text></svg>">
206
  <style>{lightModeStyle}</style>
 
207
  </head>
208
  <body>
209
  <div class="container">
 
220
  <div id="servers" class="stats-container">
221
  </div>
222
  </div>
223
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/js/all.min.js" integrity="sha512-yFjZbTYRCJodnuyGlsKamNE/LlEaEA/3uWCGാരി7eIq7jWqVl3J8jL/kof/tfu9Xqzh/y/VM5sJd/tq5iEew==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
224
+
225
  <script>
226
+ const username = '{{username}}';
227
 
228
  async function fetchInstances() {{
229
  try {{
 
236
  }}
237
  }}
238
 
239
+ class MetricsManager {{
240
  constructor() {{
241
  this.eventSources = new Map();
242
+ this.servers = new Map();
243
  this.instanceOwners = new Map();
244
  this.spaceIds = new Map();
245
+ this.lastMetrics = new Map(); // Store last metrics for each instance
246
  }}
247
 
248
+ async connect(instanceId, username) {{
249
  if (this.eventSources.has(instanceId)) return;
250
 
251
+ try {{
252
+ const eventSource = new EventSource(`/metrics/${{username}}/${{instanceId}}`);
253
+
254
+ this.spaceIds.set(instanceId, instanceId);
255
+ this.instanceOwners.set(instanceId, username);
256
+
257
+ eventSource.addEventListener("metric", (event) => {{
258
+ try {{
259
+ const data = JSON.parse(event.data);
260
+ this.lastMetrics.set(instanceId, data); // Store the received data
261
+ updateServerCard(data, instanceId);
262
+ }} catch (error) {{
263
+ console.error(`解析数据失败 (${{instanceId}}):`, error);
264
+ }}
265
  }});
 
 
 
 
 
 
 
 
 
 
 
266
 
267
+ eventSource.onerror = (error) => {{
268
+ console.error(`EventSource 错误 (${{instanceId}}):`, error);
269
+ eventSource.close();
270
+ this.eventSources.delete(instanceId);
271
+ }};
272
+
273
+ this.eventSources.set(instanceId, eventSource);
274
+ }} catch (error) {{
275
+ console.error(`连接失败 (${{username}}/${{instanceId}}):`, error);
276
+ }}
277
+ }}
278
 
279
  disconnectAll() {{
280
  this.eventSources.forEach(es => es.close());
281
  this.eventSources.clear();
 
282
  }}
283
  }}
284
 
285
  const metricsManager = new MetricsManager();
286
+ const servers = new Map();
287
 
288
  async function initialize() {{
289
  const instances = await fetchInstances();
 
292
  }});
293
  }}
294
 
295
+
296
  function updateServerCard(data, spaceId) {{
297
+ const serverId = data.replica;
298
+ const serverElement = document.getElementById(`server-${{serverId}}`);
299
+ const owner = metricsManager.instanceOwners.get(spaceId);
300
+
301
+ if (!serverElement) {{
302
+ const card = document.createElement('div');
303
+ card.id = `server-${{serverId}}`;
304
+ card.className = 'server-card';
305
+ card.innerHTML = `
306
+ <div class="server-header">
307
+ <div class="server-name">
308
+ <div class="status-dot status-online"></div>
309
+ <svg class="server-flag" width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
310
+ <path d="M21 3H3C1.9 3 1 3.9 1 5v3c0 1.1.9 2 2 10h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 5H4V6h16v2zm1 4H3c-1.1 0-2 .9-2 2v3c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2v-3c0-1.1-.9-2-2-2zm-1 5H4v-2h16v2zm1 4H3c-1.1 0-2 .9-2 2v3c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2v-3c0-1.1-.9-2-2-2zm-1 5H4v-2h16v2z"/>
311
+ </svg>
312
+ <div>${{serverId}} (${{owner}}/${{spaceId}})</div>
313
+ </div>
 
 
 
 
 
 
314
  </div>
315
+ <div class="metric-grid">
316
+ <div class="metric-item">
317
+ <div class="metric-label">CPU</div>
318
+ <div class="progress-bar-container">
319
+ <div class="cpu-progress-bar"></div>
320
+ </div>
321
+ <div class="metric-value cpu-usage">0%</div>
322
+ </div>
323
+ <div class="metric-item">
324
+ <div class="metric-label">内存</div>
325
+ <div class="progress-bar-container">
326
+ <div class="memory-progress-bar"></div>
327
+ </div>
328
+ <div class="metric-value memory-usage">0%</div>
329
+ </div>
330
+ <div class="metric-item">
331
+ <div class="metric-label">上传</div>
332
+ <div class="metric-value upload">0 KB/s</div>
333
+ </div>
334
+ <div class="metric-item">
335
+ <div class="metric-label">下载</div>
336
+ <div class="metric-value download">0 KB/s</div>
337
+ </div>
338
  </div>
339
+ `;
340
+ document.getElementById('servers').appendChild(card);
341
+ }}
 
 
 
 
 
 
 
 
 
 
 
342
 
343
+ const card = document.getElementById(`server-${{serverId}}`);
344
+ const cpuUsage = data.cpu_usage_pct;
345
+ const memoryUsage = (data.memory_used_bytes / data.memory_total_bytes) * 100;
346
+ const uploadBps = data.tx_bps;
347
+ const downloadBps = data.rx_bps;
348
 
349
+ card.querySelector('.cpu-usage').textContent = `${{cpuUsage.toFixed(2)}}%`;
350
+ card.querySelector('.cpu-progress-bar').style.width = `${{cpuUsage}}%`;
351
 
352
+ card.querySelector('.memory-usage').textContent = `${{memoryUsage.toFixed(2)}}%`;
353
+ card.querySelector('.memory-progress-bar').style.width = `${{memoryUsage}}%`;
354
 
355
+ card.querySelector('.upload').textContent = `${{formatBytes(uploadBps)}}/s`;
356
+ card.querySelector('.download').textContent = `${{formatBytes(downloadBps)}}/s`;
357
+
358
+ servers.set(serverId, Date.now());
359
+ updateSummary();
360
  }}
361
 
362
+ function updateSummary() {
363
  const now = Date.now();
364
  let online = 0;
365
  let offline = 0;
366
  let totalUpload = 0;
367
  let totalDownload = 0;
368
 
369
+ for (const [instanceId, lastMetric] of metricsManager.lastMetrics) {
370
+ const serverId = instanceId; // instanceId is the same as serverId in this context
371
+ const serverCard = document.getElementById(`server-${serverId}`);
372
 
373
+ if (serverCard) {
374
+ const lastSeen = servers.get(serverId);
375
+ const isOnline = lastSeen && (now - lastSeen) < 10000;
376
  const statusDot = serverCard.querySelector('.status-dot');
377
+ statusDot.className = `status-dot status-${isOnline ? 'online' : 'offline'}`;
378
+
379
+ if (isOnline && lastMetric) {
380
+ totalUpload += lastMetric.tx_bps;
381
+ totalDownload += lastMetric.rx_bps;
382
+ online++;
383
+ } else if (!isOnline) {
384
+ offline++;
385
+ }
386
+ }
387
+ }
388
+
389
+ // Count servers that might not have metrics yet (newly added)
390
+ let totalServers = servers.size;
391
+ for (let serverId of servers.keys()) {
392
+ if (!metricsManager.lastMetrics.has(serverId)) {
393
+ const lastSeen = servers.get(serverId);
394
+ if (lastSeen && (now-lastSeen) < 10000){
395
+ online++;
396
+ } else {
397
+ offline++;
398
+ }
399
+ }
400
+ }
401
+
402
+
403
+ document.getElementById('totalServers').textContent = totalServers;
404
  document.getElementById('onlineServers').textContent = online;
405
  document.getElementById('offlineServers').textContent = offline;
406
+ document.getElementById('totalUpload').textContent = `${formatBytes(totalUpload)}/s`;
407
+ document.getElementById('totalDownload').textContent = `${formatBytes(totalDownload)}/s`;
408
+ }
409
+
410
 
411
  function formatBytes(bytes) {{
412
  if (bytes === 0) return '0 B';
 
416
  return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
417
  }}
418
 
419
+
420
+ initialize(); // Initial load
421
+
422
  setInterval(updateSummary, 2000);
423
+
424
  setInterval(async () => {{
425
+ metricsManager.disconnectAll();
426
+ await initialize();
427
  }}, 300000);
428
+
429
  </script>
430
  </body>
431
  </html>
432
  """
433
 
434
+ USERNAME = os.environ.get("USERNAME", "yangtb24")
435
+
436
+ def fetch_instances(username):
437
+ try:
438
+ response = requests.get(f"https://huggingface.co/api/spaces?author={username}")
439
+ response.raise_for_status()
440
+ user_instances = response.json()
441
+ return [{"id": instance["id"].split('/')[1], "owner": username} for instance in user_instances]
442
+ except requests.exceptions.RequestException as e:
443
+ print(f"Error fetching instances: {e}")
444
+ return []
445
 
446
  @app.route('/')
447
  def index():
448
+ return render_template_string(htmlTemplate, username=USERNAME)
449
 
450
  @app.route('/instances')
451
  def get_instances():
 
470
  lines = event_data.split("\n")
471
  event_type = "message"
472
  data_lines = []
 
473
  for line in lines:
474
  if line.startswith("event:"):
475
  event_type = line.split(":", 1)[1].strip()
476
  elif line.startswith("data:"):
477
  data_lines.append(line.split(":", 1)[1].strip())
478
+
479
  if event_type == "metric":
480
+ yield f"event: {event_type}\ndata: {''.join(data_lines)}\n\n"
 
 
 
 
 
481
 
482
  except requests.exceptions.RequestException as e:
483
  print(f"Request Exception: {e}")
484
+ yield f"event: error\ndata: Connection error: {e}\\n\\n"
485
  except Exception as e:
486
  print(f"An error occurred: {e}")
487
+ yield f"event: error\ndata: An error occurred: {e}\\n\\n"
488
 
489
  return Response(generate(), mimetype='text/event-stream')
490
 
 
491
  if __name__ == '__main__':
492
  app.run(debug=True, host='0.0.0.0', port=7860)