yangtb24 commited on
Commit
6c57e5b
·
verified ·
1 Parent(s): 9e96e96

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +81 -87
app.py CHANGED
@@ -1,7 +1,7 @@
1
  from flask import Flask, render_template_string, jsonify, Response
2
  import requests
3
  import os
4
- import json # Import the json module
5
 
6
  app = Flask(__name__)
7
 
@@ -142,12 +142,10 @@ body {
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,7 +201,7 @@ htmlTemplate = f"""
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
- <style>{{lightModeStyle}}</style>
207
  </head>
208
  <body>
209
  <div class="container">
@@ -239,7 +237,7 @@ htmlTemplate = f"""
239
  class MetricsManager {{
240
  constructor() {{
241
  this.eventSources = new Map();
242
- this.servers = new Map(); // Store server data
243
  this.instanceOwners = new Map();
244
  this.spaceIds = new Map();
245
  }}
@@ -252,19 +250,23 @@ htmlTemplate = f"""
252
 
253
  this.spaceIds.set(instanceId, instanceId);
254
  this.instanceOwners.set(instanceId, username);
255
- this.servers.set(instanceId, {{ lastSeen: 0, uploadBps: 0, downloadBps: 0 }}); // Initialize server data
256
 
257
 
258
  eventSource.addEventListener("metric", (event) => {{
259
  try {{
260
  const data = JSON.parse(event.data);
261
- // console.log("Received data:", data); // Debugging line
262
  this.servers.set(instanceId, {{
263
  lastSeen: Date.now(),
264
  uploadBps: data.tx_bps,
265
  downloadBps: data.rx_bps,
266
  }});
267
- updateServerCard(data, instanceId);
 
 
 
 
 
268
 
269
  }} catch (error) {{
270
  console.error(`解析数据失败 (${{instanceId}}):`, error);
@@ -274,7 +276,7 @@ htmlTemplate = f"""
274
  eventSource.onerror = (error) => {{
275
  console.error(`EventSource 错误 (${{instanceId}}):`, error);
276
  eventSource.close();
277
- this.eventSources.delete(instanceId); // Remove on error
278
  }};
279
 
280
  this.eventSources.set(instanceId, eventSource);
@@ -290,7 +292,6 @@ htmlTemplate = f"""
290
  }}
291
 
292
  const metricsManager = new MetricsManager();
293
- //const servers = new Map(); // Moved to MetricsManager
294
 
295
  async function initialize() {{
296
  const instances = await fetchInstances();
@@ -306,13 +307,14 @@ htmlTemplate = f"""
306
  const owner = metricsManager.instanceOwners.get(spaceId);
307
 
308
  if (!serverElement) {{
 
309
  const card = document.createElement('div');
310
  card.id = `server-${{serverId}}`;
311
  card.className = 'server-card';
312
  card.innerHTML = `
313
  <div class="server-header">
314
  <div class="server-name">
315
- <div class="status-dot status-online"></div>
316
  <svg class="server-flag" width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
317
  <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"/>
318
  </svg>
@@ -345,25 +347,49 @@ htmlTemplate = f"""
345
  </div>
346
  `;
347
  document.getElementById('servers').appendChild(card);
 
348
  }}
349
 
350
- const card = document.getElementById(`server-${{serverId}}`);
351
- const cpuUsage = data.cpu_usage_pct;
352
- const memoryUsage = (data.memory_used_bytes / data.memory_total_bytes) * 100;
353
- const uploadBps = data.tx_bps;
354
- const downloadBps = data.rx_bps;
355
 
356
- card.querySelector('.cpu-usage').textContent = `${{cpuUsage.toFixed(2)}}%`;
357
- card.querySelector('.cpu-progress-bar').style.width = `${{cpuUsage}}%`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
358
 
359
- card.querySelector('.memory-usage').textContent = `${{memoryUsage.toFixed(2)}}%`;
360
- card.querySelector('.memory-progress-bar').style.width = `${{memoryUsage}}%`;
 
 
 
 
361
 
362
- card.querySelector('.upload').textContent = `${{formatBytes(uploadBps)}}/s`;
363
- card.querySelector('.download').textContent = `${{formatBytes(downloadBps)}}/s`;
364
 
365
- // servers.set(serverId, Date.now()); // NO LONGER NEEDED - handled in MetricsManager
366
- updateSummary();
367
  }}
368
 
369
  function updateSummary() {{
@@ -377,15 +403,19 @@ htmlTemplate = f"""
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) {{
386
  online++;
387
- totalUpload += serverData.uploadBps || 0; // Use serverData, handle null/undefined
388
- totalDownload += serverData.downloadBps || 0; // Use serverData, handle null/undefined
389
  }} else {{
390
  offline++;
391
  }}
@@ -406,6 +436,30 @@ htmlTemplate = f"""
406
  }}
407
 
408
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
409
  initialize(); // Initial load
410
 
411
  setInterval(updateSummary, 2000);
@@ -420,65 +474,5 @@ htmlTemplate = f"""
420
  </html>
421
  """
422
 
423
- USERNAME = os.environ.get("USERNAME", "yangtb24") # Default username
424
-
425
- def fetch_instances(username):
426
- try:
427
- response = requests.get(f"https://huggingface.co/api/spaces?author={username}")
428
- response.raise_for_status()
429
- user_instances = response.json()
430
- return [{"id": instance["id"].split('/')[1], "owner": username} for instance in user_instances]
431
- except requests.exceptions.RequestException as e:
432
- print(f"Error fetching instances: {e}")
433
- return []
434
-
435
- @app.route('/')
436
- def index():
437
- return render_template_string(htmlTemplate, username=USERNAME)
438
-
439
- @app.route('/instances')
440
- def get_instances():
441
- instances = fetch_instances(USERNAME)
442
- return jsonify(instances)
443
-
444
- @app.route('/metrics/<username>/<instance_id>')
445
- def stream_metrics(username, instance_id):
446
- url = f"https://api.hf.space/v1/{username}/{instance_id}/live-metrics/sse"
447
-
448
- def generate():
449
- try:
450
- response = requests.get(url, stream=True, headers={"Accept": "text/event-stream"}, timeout=15)
451
- response.raise_for_status()
452
-
453
- buffer = ""
454
- for chunk in response.iter_content(chunk_size=1024, decode_unicode=True):
455
- if chunk:
456
- buffer += chunk
457
- while "\n\n" in buffer:
458
- event_data, buffer = buffer.split("\n\n", 1)
459
- lines = event_data.split("\n")
460
- event_type = "message"
461
- data_lines = []
462
- for line in lines:
463
- if line.startswith("event:"):
464
- event_type = line.split(":", 1)[1].strip()
465
- elif line.startswith("data:"):
466
- data_lines.append(line.split(":", 1)[1].strip())
467
-
468
- if event_type == "metric":
469
- # Correctly yield the JSON data:
470
- yield f"event: {event_type}\ndata: {json.dumps(json.loads(''.join(data_lines)))}\n\n"
471
-
472
-
473
- except requests.exceptions.RequestException as e:
474
- print(f"Request Exception: {e}")
475
- yield f"event: error\ndata: Connection error: {e}\\n\\n"
476
- except Exception as e:
477
- print(f"An error occurred: {e}")
478
- yield f"event: error\ndata: An error occurred: {e}\\n\\n"
479
-
480
- return Response(generate(), mimetype='text/event-stream')
481
-
482
  if __name__ == '__main__':
483
  app.run(debug=True, host='0.0.0.0', port=7860)
484
-
 
1
  from flask import Flask, render_template_string, jsonify, Response
2
  import requests
3
  import os
4
+ import json
5
 
6
  app = Flask(__name__)
7
 
 
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
  <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">
 
237
  class MetricsManager {{
238
  constructor() {{
239
  this.eventSources = new Map();
240
+ this.servers = new Map();
241
  this.instanceOwners = new Map();
242
  this.spaceIds = new Map();
243
  }}
 
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);
 
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);
 
292
  }}
293
 
294
  const metricsManager = new MetricsManager();
 
295
 
296
  async function initialize() {{
297
  const instances = await fetchInstances();
 
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>
 
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() {{
 
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) {{
416
  online++;
417
+ totalUpload += serverData.uploadBps || 0;
418
+ totalDownload += serverData.downloadBps || 0;
419
  }} else {{
420
  offline++;
421
  }}
 
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);
 
474
  </html>
475
  """
476
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
477
  if __name__ == '__main__':
478
  app.run(debug=True, host='0.0.0.0', port=7860)