yangtb24 commited on
Commit
b5ebe66
·
verified ·
1 Parent(s): 3ea0beb

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +118 -184
app.py CHANGED
@@ -142,10 +142,12 @@ body {
142
  }
143
  .status-online {
144
  background-color: #2ecc71;
 
145
  box-shadow: 0 0 5px rgba(46, 204, 113, 0.4);
146
  }
147
  .status-offline {
148
  background-color: #e74c3c;
 
149
  box-shadow: 0 0 5px rgba(231, 76, 60, 0.4);
150
  }
151
  @keyframes fadeIn {
@@ -201,7 +203,11 @@ htmlTemplate = f"""
201
  <meta charset="UTF-8">
202
  <title>HF Space Monitor</title>
203
  <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>">
204
- <style>{lightModeStyle}</style>
 
 
 
 
205
  </head>
206
  <body>
207
  <div class="container">
@@ -218,7 +224,6 @@ htmlTemplate = f"""
218
  <div id="servers" class="stats-container">
219
  </div>
220
  </div>
221
- <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>
222
 
223
  <script>
224
  const username = '{{username}}';
@@ -242,48 +247,46 @@ htmlTemplate = f"""
242
  this.spaceIds = new Map();
243
  }}
244
 
245
- async connect(instanceId, username) {{
246
  if (this.eventSources.has(instanceId)) return;
247
 
248
- try {{
249
- const eventSource = new EventSource(`/metrics/${{username}}/${{instanceId}}`);
250
 
251
  this.spaceIds.set(instanceId, instanceId);
252
  this.instanceOwners.set(instanceId, username);
253
- this.servers.set(instanceId, {{ lastSeen: 0, uploadBps: 0, downloadBps: 0 }});
254
 
 
 
 
255
 
256
- eventSource.addEventListener("metric", (event) => {{
257
- try {{
258
  const data = JSON.parse(event.data);
259
- this.servers.set(instanceId, {{
260
  lastSeen: Date.now(),
261
  uploadBps: data.tx_bps,
262
  downloadBps: data.rx_bps,
263
- }});
264
- // console.log("Received data:", data);
265
 
266
- // Use MutationObserver to wait for the card to be added
267
- waitForElement(`#server-${{data.replica}}`, () => {{
268
- updateServerCard(data, instanceId);
269
- }});
270
 
271
- }} catch (error) {{
272
- console.error(`解析数据失败 (${{instanceId}}):`, error);
273
- }}
274
- }});
275
-
276
- eventSource.onerror = (error) => {{
277
- console.error(`EventSource 错误 (${{instanceId}}):`, error);
278
  eventSource.close();
279
  this.eventSources.delete(instanceId);
280
- }};
281
 
282
  this.eventSources.set(instanceId, eventSource);
283
- }} catch (error) {{
284
- console.error(`连接失败 (${{username}}/${{instanceId}}):`, error);
285
- }}
286
- }}
287
 
288
  disconnectAll() {{
289
  this.eventSources.forEach(es => es.close());
@@ -301,96 +304,67 @@ htmlTemplate = f"""
301
  }}
302
 
303
 
304
- function updateServerCard(data, spaceId) {{
305
- const serverId = data.replica;
306
- const serverElement = document.getElementById(`server-${{serverId}}`);
307
- const owner = metricsManager.instanceOwners.get(spaceId);
308
-
309
- if (!serverElement) {{
310
- // console.log(`Creating card for server: ${{serverId}}`);
311
- const card = document.createElement('div');
312
- card.id = `server-${{serverId}}`;
313
- card.className = 'server-card';
314
- card.innerHTML = `
315
- <div class="server-header">
316
- <div class="server-name">
317
- <div class="status-dot status-offline"></div>
318
- <svg class="server-flag" width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
319
- <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"/>
320
- </svg>
321
- <div>${{serverId}} (${{owner}}/${{spaceId}})</div>
322
- </div>
323
- </div>
324
- <div class="metric-grid">
325
- <div class="metric-item">
326
- <div class="metric-label">CPU</div>
327
- <div class="progress-bar-container">
328
- <div class="cpu-progress-bar"></div>
329
- </div>
330
- <div class="metric-value cpu-usage">0%</div>
331
  </div>
332
- <div class="metric-item">
333
- <div class="metric-label">内存</div>
334
- <div class="progress-bar-container">
335
- <div class="memory-progress-bar"></div>
336
- </div>
337
- <div class="metric-value memory-usage">0%</div>
338
  </div>
339
- <div class="metric-item">
340
- <div class="metric-label">上传</div>
341
- <div class="metric-value upload">0 KB/s</div>
342
- </div>
343
- <div class="metric-item">
344
- <div class="metric-label">下载</div>
345
- <div class="metric-value download">0 KB/s</div>
346
- </div>
347
- </div>
348
- `;
349
- document.getElementById('servers').appendChild(card);
350
- // console.log(`Card created and appended for server: ${{serverId}}`);
351
- }}
352
-
353
-
354
- // Use querySelectorAll and check for null to be extra safe
355
- const card = document.getElementById(`server-${{serverId}}`);
356
- if (!card) return; // Defensive check
357
-
358
- const cpuUsageElements = card.querySelectorAll('.cpu-usage');
359
- const cpuProgressBarElements = card.querySelectorAll('.cpu-progress-bar');
360
- const memoryUsageElements = card.querySelectorAll('.memory-usage');
361
- const memoryProgressBarElements = card.querySelectorAll('.memory-progress-bar');
362
- const uploadElements = card.querySelectorAll('.upload');
363
- const downloadElements = card.querySelectorAll('.download');
364
-
365
- const cpuUsage = data.cpu_usage_pct;
366
- const memoryUsage = (data.memory_used_bytes / data.memory_total_bytes) * 100;
367
- const uploadBps = data.tx_bps;
368
- const downloadBps = data.rx_bps;
369
-
370
- if (cpuUsageElements.length > 0) {{
371
- cpuUsageElements[0].textContent = `${{cpuUsage.toFixed(2)}}%`;
372
- }}
373
- if (cpuProgressBarElements.length > 0) {{
374
- cpuProgressBarElements[0].style.width = `${{cpuUsage}}%`;
375
- }}
376
-
377
- if (memoryUsageElements.length > 0) {{
378
- memoryUsageElements[0].textContent = `${{memoryUsage.toFixed(2)}}%`;
379
- }}
380
- if(memoryProgressBarElements.length > 0){{
381
- memoryProgressBarElements[0].style.width = `${{memoryUsage}}%`;
382
- }}
383
-
384
- if (uploadElements.length > 0) {{
385
- uploadElements[0].textContent = `${{formatBytes(uploadBps)}}/s`;
386
- }}
387
- if (downloadElements.length > 0) {{
388
- downloadElements[0].textContent = `${{formatBytes(downloadBps)}}/s`;
389
- }}
390
-
391
-
392
- updateSummary();
393
- }}
394
 
395
  function updateSummary() {{
396
  const now = Date.now();
@@ -403,13 +377,9 @@ htmlTemplate = f"""
403
  const isOnline = (now - serverData.lastSeen) < 10000;
404
  const serverCard = document.getElementById(`server-${{serverId}}`);
405
 
406
- //Update status dot
407
  if (serverCard) {{
408
  const statusDot = serverCard.querySelector('.status-dot');
409
- if(statusDot){{
410
- statusDot.classList.remove('status-online', 'status-offline');
411
- statusDot.classList.add(isOnline ? 'status-online' : 'status-offline');
412
- }}
413
  }}
414
 
415
  if (isOnline) {{
@@ -423,8 +393,8 @@ htmlTemplate = f"""
423
  document.getElementById('totalServers').textContent = metricsManager.servers.size;
424
  document.getElementById('onlineServers').textContent = online;
425
  document.getElementById('offlineServers').textContent = offline;
426
- document.getElementById('totalUpload').textContent = `${{formatBytes(totalUpload)}}/s`;
427
- document.getElementById('totalDownload').textContent = `${{formatBytes(totalDownload)}}/s`;
428
  }}
429
 
430
  function formatBytes(bytes) {{
@@ -435,35 +405,8 @@ htmlTemplate = f"""
435
  return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
436
  }}
437
 
438
-
439
-
440
- // MutationObserver to wait for elements to be added to the DOM
441
- function waitForElement(selector, callback) {{
442
- const element = document.querySelector(selector);
443
- if (element) {{
444
- callback();
445
- return;
446
- }}
447
-
448
- const observer = new MutationObserver(mutations => {{
449
- const element = document.querySelector(selector);
450
- if (element) {{
451
- observer.disconnect();
452
- callback();
453
- }}
454
- }});
455
-
456
- observer.observe(document.body, {{
457
- childList: true,
458
- subtree: true
459
- }});
460
- }}
461
-
462
-
463
- initialize(); // Initial load
464
-
465
  setInterval(updateSummary, 2000);
466
-
467
  setInterval(async () => {{
468
  metricsManager.disconnectAll();
469
  await initialize();
@@ -474,25 +417,13 @@ htmlTemplate = f"""
474
  </html>
475
  """
476
 
477
- USERNAME = os.environ.get("USERNAME", "yangtb24") # Default username
478
-
479
- def fetch_instances(username):
480
- try:
481
- response = requests.get(f"https://huggingface.co/api/spaces?author={username}")
482
- response.raise_for_status()
483
- user_instances = response.json()
484
- return [{"id": instance["id"].split('/')[1], "owner": username} for instance in user_instances]
485
- except requests.exceptions.RequestException as e:
486
- print(f"Error fetching instances: {e}")
487
- return []
488
-
489
  @app.route('/')
490
  def index():
491
- return render_template_string(htmlTemplate, username=USERNAME)
492
 
493
  @app.route('/instances')
494
  def get_instances():
495
- instances = fetch_instances(USERNAME)
496
  return jsonify(instances)
497
 
498
  @app.route('/metrics/<username>/<instance_id>')
@@ -504,35 +435,38 @@ def stream_metrics(username, instance_id):
504
  response = requests.get(url, stream=True, headers={"Accept": "text/event-stream"}, timeout=15)
505
  response.raise_for_status()
506
 
507
- buffer = ""
508
  for chunk in response.iter_content(chunk_size=1024, decode_unicode=True):
509
  if chunk:
510
- buffer += chunk
511
- while "\n\n" in buffer:
512
- event_data, buffer = buffer.split("\n\n", 1)
513
- lines = event_data.split("\n")
514
- event_type = "message"
515
- data_lines = []
516
- for line in lines:
517
- if line.startswith("event:"):
518
- event_type = line.split(":", 1)[1].strip()
519
- elif line.startswith("data:"):
520
- data_lines.append(line.split(":", 1)[1].strip())
521
-
522
- if event_type == "metric":
523
- # Correctly yield the JSON data:
524
- yield f"event: {event_type}\ndata: {json.dumps(json.loads(''.join(data_lines)))}\n\n"
525
-
526
-
527
  except requests.exceptions.RequestException as e:
528
  print(f"Request Exception: {e}")
529
- yield f"event: error\ndata: Connection error: {e}\\n\\n"
530
  except Exception as e:
531
  print(f"An error occurred: {e}")
532
- yield f"event: error\ndata: An error occurred: {e}\\n\\n"
533
 
534
  return Response(generate(), mimetype='text/event-stream')
535
 
 
 
 
 
 
 
 
 
 
 
 
 
536
  if __name__ == '__main__':
537
  app.run(debug=True, host='0.0.0.0', port=7860)
538
 
 
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 {
 
203
  <meta charset="UTF-8">
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
+ <!-- Use a CDN for Font Awesome (more reliable) -->
207
+ <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" />
208
+ <style>
209
+ {lightModeStyle} /* Include styles directly in a <style> tag */
210
+ </style>
211
  </head>
212
  <body>
213
  <div class="container">
 
224
  <div id="servers" class="stats-container">
225
  </div>
226
  </div>
 
227
 
228
  <script>
229
  const username = '{{username}}';
 
247
  this.spaceIds = new Map();
248
  }}
249
 
250
+ connect(instanceId, username) {
251
  if (this.eventSources.has(instanceId)) return;
252
 
253
+ try {
254
+ const eventSource = new EventSource(`/metrics/${username}/${instanceId}`);
255
 
256
  this.spaceIds.set(instanceId, instanceId);
257
  this.instanceOwners.set(instanceId, username);
258
+ this.servers.set(instanceId, { lastSeen: 0, uploadBps: 0, downloadBps: 0 });
259
 
260
+ eventSource.onopen = () => {
261
+ console.log(`EventSource connected: ${username}/${instanceId}`);
262
+ };
263
 
264
+ eventSource.addEventListener("metric", (event) => {
265
+ try {
266
  const data = JSON.parse(event.data);
267
+ this.servers.set(instanceId, {
268
  lastSeen: Date.now(),
269
  uploadBps: data.tx_bps,
270
  downloadBps: data.rx_bps,
271
+ });
272
+ updateServerCard(data, instanceId);
273
 
274
+ } catch (error) {
275
+ console.error(`解析数据失败 (${instanceId}):`, error);
276
+ }
277
+ });
278
 
279
+ eventSource.onerror = (error) => {
280
+ console.error(`EventSource error (${instanceId}):`, error);
 
 
 
 
 
281
  eventSource.close();
282
  this.eventSources.delete(instanceId);
283
+ };
284
 
285
  this.eventSources.set(instanceId, eventSource);
286
+ } catch (error) {
287
+ console.error(`连接失败 (${username}/${instanceId}):`, error);
288
+ }
289
+ }
290
 
291
  disconnectAll() {{
292
  this.eventSources.forEach(es => es.close());
 
304
  }}
305
 
306
 
307
+ function updateServerCard(data, spaceId) {
308
+ const serverId = data.replica;
309
+ const serverElement = document.getElementById(`server-${serverId}`);
310
+ const owner = metricsManager.instanceOwners.get(spaceId);
311
+
312
+ if (!serverElement) {
313
+ const card = document.createElement('div');
314
+ card.id = `server-${serverId}`;
315
+ card.className = 'server-card';
316
+ card.innerHTML = `
317
+ <div class="server-header">
318
+ <div class="server-name">
319
+ <div class="status-dot status-online"></div>
320
+ <!-- Removed the placeholder SVG, Font Awesome will handle it -->
321
+ <div>${serverId} (${owner}/${spaceId})</div>
322
+ </div>
323
+ </div>
324
+ <div class="metric-grid">
325
+ <div class="metric-item">
326
+ <div class="metric-label">CPU</div>
327
+ <div class="progress-bar-container">
328
+ <div class="cpu-progress-bar"></div>
 
 
 
 
 
329
  </div>
330
+ <div class="metric-value cpu-usage">0%</div>
331
+ </div>
332
+ <div class="metric-item">
333
+ <div class="metric-label">内存</div>
334
+ <div class="progress-bar-container">
335
+ <div class="memory-progress-bar"></div>
336
  </div>
337
+ <div class="metric-value memory-usage">0%</div>
338
+ </div>
339
+ <div class="metric-item">
340
+ <div class="metric-label">上传</div>
341
+ <div class="metric-value upload">0 KB/s</div>
342
+ </div>
343
+ <div class="metric-item">
344
+ <div class="metric-label">下载</div>
345
+ <div class="metric-value download">0 KB/s</div>
346
+ </div>
347
+ </div>
348
+ `;
349
+ document.getElementById('servers').appendChild(card);
350
+ }
351
+
352
+ const card = document.getElementById(`server-${serverId}`);
353
+ const cpuUsage = data.cpu_usage_pct;
354
+ const memoryUsage = (data.memory_used_bytes / data.memory_total_bytes) * 100;
355
+ const uploadBps = data.tx_bps;
356
+ const downloadBps = data.rx_bps;
357
+
358
+ card.querySelector('.cpu-usage').textContent = `${cpuUsage.toFixed(2)}%`;
359
+ card.querySelector('.cpu-progress-bar').style.width = `${cpuUsage}%`;
360
+
361
+ card.querySelector('.memory-usage').textContent = `${memoryUsage.toFixed(2)}%`;
362
+ card.querySelector('.memory-progress-bar').style.width = `${memoryUsage}%`;
363
+
364
+ card.querySelector('.upload').textContent = `${formatBytes(uploadBps)}/s`;
365
+ card.querySelector('.download').textContent = `${formatBytes(downloadBps)}/s`;
366
+ updateSummary();
367
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
368
 
369
  function updateSummary() {{
370
  const now = Date.now();
 
377
  const isOnline = (now - serverData.lastSeen) < 10000;
378
  const serverCard = document.getElementById(`server-${{serverId}}`);
379
 
 
380
  if (serverCard) {{
381
  const statusDot = serverCard.querySelector('.status-dot');
382
+ statusDot.className = `status-dot status-${{isOnline ? 'online' : 'offline'}}`;
 
 
 
383
  }}
384
 
385
  if (isOnline) {{
 
393
  document.getElementById('totalServers').textContent = metricsManager.servers.size;
394
  document.getElementById('onlineServers').textContent = online;
395
  document.getElementById('offlineServers').textContent = offline;
396
+ document.getElementById('totalUpload').textContent = `${formatBytes(totalUpload)}/s`;
397
+ document.getElementById('totalDownload').textContent = `${formatBytes(totalDownload)}/s`;
398
  }}
399
 
400
  function formatBytes(bytes) {{
 
405
  return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
406
  }}
407
 
408
+ initialize();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
409
  setInterval(updateSummary, 2000);
 
410
  setInterval(async () => {{
411
  metricsManager.disconnectAll();
412
  await initialize();
 
417
  </html>
418
  """
419
 
 
 
 
 
 
 
 
 
 
 
 
 
420
  @app.route('/')
421
  def index():
422
+ return render_template_string(htmlTemplate, username=os.environ.get("USERNAME", "yangtb24"))
423
 
424
  @app.route('/instances')
425
  def get_instances():
426
+ instances = fetch_instances(os.environ.get("USERNAME", "yangtb24"))
427
  return jsonify(instances)
428
 
429
  @app.route('/metrics/<username>/<instance_id>')
 
435
  response = requests.get(url, stream=True, headers={"Accept": "text/event-stream"}, timeout=15)
436
  response.raise_for_status()
437
 
 
438
  for chunk in response.iter_content(chunk_size=1024, decode_unicode=True):
439
  if chunk:
440
+ # Simplfied event parsing, as we now *know* we're getting valid JSON
441
+ if chunk.startswith("event: metric"):
442
+ try:
443
+ data_str = chunk.split("data:", 1)[1].strip()
444
+ data_json = json.loads(data_str)
445
+ yield f"event: metric\ndata: {json.dumps(data_json)}\n\n"
446
+ except (IndexError, json.JSONDecodeError) as e:
447
+ print(f"Error parsing SSE chunk: {e}")
448
+ continue # Skip to the next chunk on error
 
 
 
 
 
 
 
 
449
  except requests.exceptions.RequestException as e:
450
  print(f"Request Exception: {e}")
451
+ yield f"event: error\ndata: Connection error: {e}\n\n"
452
  except Exception as e:
453
  print(f"An error occurred: {e}")
454
+ yield f"event: error\ndata: An error occurred: {e}\n\n"
455
 
456
  return Response(generate(), mimetype='text/event-stream')
457
 
458
+
459
+
460
+ def fetch_instances(username):
461
+ try:
462
+ response = requests.get(f"https://huggingface.co/api/spaces?author={username}")
463
+ response.raise_for_status() # Raise HTTPError for bad responses (4xx or 5xx)
464
+ user_instances = response.json()
465
+ return [{"id": instance["id"].split('/')[1], "owner": username} for instance in user_instances]
466
+ except requests.exceptions.RequestException as e:
467
+ print(f"Error fetching instances: {e}")
468
+ return []
469
+
470
  if __name__ == '__main__':
471
  app.run(debug=True, host='0.0.0.0', port=7860)
472