i0110 commited on
Commit
ab5afa3
·
verified ·
1 Parent(s): 2e0d9e6

Update public/index.html

Browse files
Files changed (1) hide show
  1. public/index.html +142 -36
public/index.html CHANGED
@@ -280,12 +280,86 @@
280
  grid-template-columns: 1fr 1fr;
281
  }
282
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
283
  </style>
284
  </head>
285
  <body>
286
  <div class="container">
287
  <div class="overview">
288
- <div class="overview-title">📊 系统概览</div>
 
 
 
289
  <div class="theme-toggle">
290
  主题:
291
  <button onclick="toggleTheme('system')" title="跟随系统">
@@ -316,6 +390,15 @@
316
  <div id="servers" class="stats-container">
317
  </div>
318
  </div>
 
 
 
 
 
 
 
 
 
319
 
320
  <script>
321
  // 主题切换功能
@@ -354,11 +437,64 @@
354
 
355
  initTheme();
356
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
357
  async function getUsernames() {
358
  try {
359
  const response = await fetch('/api/config');
360
  const config = await response.json();
361
- console.log('Usernames from API:', config.usernames);
362
  const usernamesList = config.usernames ? config.usernames.split(',').map(name => name.trim()).filter(name => name) : [];
363
  document.getElementById('totalUsers').textContent = usernamesList.length;
364
  return usernamesList;
@@ -373,7 +509,6 @@
373
  try {
374
  const response = await fetch('/api/proxy/spaces');
375
  const instances = await response.json();
376
- console.log(`Found ${instances.length} spaces`);
377
  return instances;
378
  } catch (error) {
379
  console.error("获取实例列表失败:", error);
@@ -389,12 +524,10 @@
389
 
390
  async connect(instanceId, username) {
391
  if (this.eventSources.has(instanceId)) {
392
- console.log(`已经连接到实例 ${instanceId} 的监控数据,跳过重复连接`);
393
  return;
394
  }
395
 
396
  try {
397
- console.log(`尝试连接监控数据: /api/proxy/live-metrics/${username}/${instanceId.split('/')[1]}`);
398
  const eventSource = new EventSource(
399
  `/api/proxy/live-metrics/${username}/${instanceId.split('/')[1]}`
400
  );
@@ -404,35 +537,26 @@
404
  eventSource.addEventListener("metric", (event) => {
405
  try {
406
  const data = JSON.parse(event.data);
407
- console.log(`收到监控数据 (${instanceId}):`, data);
408
  updateServerCard(data, instanceId);
409
  } catch (error) {
410
- console.error(`解析数据失败 (${instanceId}):`, error, event.data);
411
  }
412
  });
413
 
414
  eventSource.onerror = (error) => {
415
- console.error(`EventSource 错误 (${instanceId}):`, error);
416
  eventSource.close();
417
  this.eventSources.delete(instanceId);
418
- // 不设置为 sleep,通过 status 判断状态
419
  updateServerCard(null, instanceId, false);
420
  };
421
 
422
- eventSource.onopen = () => {
423
- console.log(`成功打开 EventSource 连接 (${instanceId})`);
424
- };
425
-
426
  this.eventSources.set(instanceId, eventSource);
427
- console.log(`已创建 EventSource 连接 (${instanceId}),当前总连接数: ${this.eventSources.size}`);
428
  } catch (error) {
429
  console.error(`连接失败 (${username}/${instanceId}):`, error);
430
- updateServerCard(null, instanceId, false); // 不设置为 sleep,通过 status 判断状态
431
  }
432
  }
433
 
434
  disconnectAll() {
435
- console.log(`断开所有监控数据连接 (${this.eventSources.size} 个)`);
436
  this.eventSources.forEach(es => es.close());
437
  this.eventSources.clear();
438
  }
@@ -443,16 +567,12 @@
443
  const serverStatus = new Map();
444
 
445
  async function initialize() {
446
- await getUsernames(); // 先获取用户数并更新
447
  const instances = await fetchInstances();
448
  instances.forEach(instance => {
449
  renderInstanceCard(instance);
450
- // 只连接状态为 running 的实例
451
  if (instance.status.toLowerCase() === 'running') {
452
- console.log(`实例 ${instance.repo_id} 状态为 running,尝试连接监控数据`);
453
  metricsManager.connect(instance.repo_id, instance.owner);
454
- } else {
455
- console.log(`实例 ${instance.repo_id} 状态为 ${instance.status},不连接监控数据`);
456
  }
457
  });
458
  updateSummary();
@@ -483,11 +603,9 @@
483
  }
484
 
485
  const userServers = userGroup.querySelector('.user-servers');
486
- // 使用完整 repo_id 作为卡片 ID
487
  const cardId = `instance-${instanceId}`;
488
  let card = document.getElementById(cardId);
489
  if (!card) {
490
- console.log(`渲染卡片,instanceId: ${instanceId}, cardId: ${cardId}`);
491
  card = document.createElement('div');
492
  card.id = cardId;
493
  card.className = 'server-card';
@@ -523,14 +641,13 @@
523
  <div class="metric-value download">N/A</div>
524
  </div>
525
  </div>
526
- <div class="action-buttons">
527
  <button class="action-button" onclick="restartSpace('${instance.repo_id}')">重启</button>
528
  <button class="action-button" onclick="rebuildSpace('${instance.repo_id}')">重建</button>
529
  </div>
530
  `;
531
  userServers.appendChild(card);
532
  }
533
- // 根据状态设置初始显示
534
  const statusDot = card.querySelector('.status-dot');
535
  const initialStatus = instance.status.toLowerCase();
536
  if (initialStatus === 'running') {
@@ -544,19 +661,16 @@
544
  }
545
 
546
  function updateServerCard(data, instanceId, isSleep = false) {
547
- // 卡片 ID 使用完整 repo_id
548
  const cardId = `instance-${instanceId}`;
549
  let card = document.getElementById(cardId);
550
  const instance = instanceMap.get(instanceId);
551
 
552
  if (!card && instance) {
553
- console.log(`卡片 ${cardId} 不存在,重新渲染`);
554
  renderInstanceCard(instance);
555
  card = document.getElementById(cardId);
556
  }
557
 
558
  if (card) {
559
- console.log(`更新卡片显示,instanceId: ${instanceId}, cardId: ${cardId}`);
560
  const statusDot = card.querySelector('.status-dot');
561
  let upload = 'N/A', download = 'N/A', cpuUsage = 'N/A', memoryUsage = 'N/A';
562
  let isOnline = false;
@@ -569,9 +683,7 @@
569
  statusDot.className = 'status-dot status-online';
570
  isOnline = true;
571
  isSleep = false;
572
- console.log(`更新监控数据 (${instanceId}): CPU=${cpuUsage}, 内存=${memoryUsage}, 上传=${upload}, 下载=${download}`);
573
  } else {
574
- // 如果没有监控数据,根据实例状态设置图标
575
  const currentStatus = instance?.status.toLowerCase() || 'unknown';
576
  if (currentStatus === 'running') {
577
  statusDot.className = 'status-dot status-online';
@@ -586,7 +698,6 @@
586
  isOnline = false;
587
  isSleep = false;
588
  }
589
- console.log(`无监控数据,基于状态更新 (${instanceId}): 状态=${currentStatus}`);
590
  }
591
 
592
  card.querySelector('.cpu-usage').textContent = cpuUsage;
@@ -596,14 +707,11 @@
596
 
597
  serverStatus.set(instanceId, { lastSeen: Date.now(), isOnline, isSleep, data: data || null, status: instance?.status || 'unknown' });
598
  updateSummary();
599
- } else {
600
- console.error(`卡片 ${cardId} 未找到,无法更新显示`);
601
  }
602
  }
603
 
604
  async function restartSpace(repoId) {
605
  try {
606
- console.log(`尝试重启 Space: ${repoId}`);
607
  const encodedRepoId = encodeURIComponent(repoId);
608
  const response = await fetch(`/api/proxy/restart/${encodedRepoId}`, { method: 'POST' });
609
  const result = await response.json();
@@ -621,7 +729,6 @@
621
 
622
  async function rebuildSpace(repoId) {
623
  try {
624
- console.log(`尝试重建 Space: ${repoId}`);
625
  const encodedRepoId = encodeURIComponent(repoId);
626
  const response = await fetch(`/api/proxy/rebuild/${encodedRepoId}`, { method: 'POST' });
627
  const result = await response.json();
@@ -644,7 +751,6 @@
644
  let totalDownload = 0;
645
 
646
  serverStatus.forEach((status, instanceId) => {
647
- const now = Date.now();
648
  const isRecentlyOnline = status.isOnline || status.status.toLowerCase() === 'running';
649
  if (isRecentlyOnline) {
650
  online++;
 
280
  grid-template-columns: 1fr 1fr;
281
  }
282
  }
283
+ .login-overlay {
284
+ position: fixed;
285
+ top: 0;
286
+ left: 0;
287
+ width: 100%;
288
+ height: 100%;
289
+ background: rgba(0, 0, 0, 0.7);
290
+ display: flex;
291
+ align-items: center;
292
+ justify-content: center;
293
+ z-index: 1000;
294
+ }
295
+ .login-box {
296
+ background: var(--card-background);
297
+ padding: 30px;
298
+ border-radius: 10px;
299
+ border: 1px solid var(--card-border);
300
+ width: 300px;
301
+ text-align: center;
302
+ }
303
+ .login-box h2 {
304
+ margin-bottom: 20px;
305
+ color: var(--text-color);
306
+ }
307
+ .login-box input {
308
+ width: 100%;
309
+ padding: 10px;
310
+ margin: 10px 0;
311
+ border: 1px solid var(--metric-border);
312
+ border-radius: 5px;
313
+ background: var(--metric-background);
314
+ color: var(--text-color);
315
+ }
316
+ .login-box button {
317
+ width: 100%;
318
+ padding: 10px;
319
+ background: var(--action-button-bg);
320
+ border: none;
321
+ border-radius: 5px;
322
+ color: var(--text-color);
323
+ cursor: pointer;
324
+ transition: background 0.2s ease;
325
+ }
326
+ .login-box button:hover {
327
+ background: var(--action-button-hover);
328
+ }
329
+ .login-error {
330
+ color: #f44336;
331
+ margin-top: 10px;
332
+ font-size: 14px;
333
+ }
334
+ .logout-button {
335
+ background: var(--action-button-bg);
336
+ border: none;
337
+ color: var(--text-color);
338
+ padding: 6px 12px;
339
+ border-radius: 4px;
340
+ cursor: pointer;
341
+ font-size: 13px;
342
+ transition: background 0.2s ease;
343
+ margin-left: auto;
344
+ }
345
+ .logout-button:hover {
346
+ background: var(--action-button-hover);
347
+ }
348
+ .header-container {
349
+ display: flex;
350
+ justify-content: space-between;
351
+ align-items: center;
352
+ margin-bottom: 20px;
353
+ }
354
  </style>
355
  </head>
356
  <body>
357
  <div class="container">
358
  <div class="overview">
359
+ <div class="header-container">
360
+ <div class="overview-title">📊 系统概览</div>
361
+ <button class="logout-button" id="logoutButton" style="display: none;" onclick="logout()">登出</button>
362
+ </div>
363
  <div class="theme-toggle">
364
  主题:
365
  <button onclick="toggleTheme('system')" title="跟随系统">
 
390
  <div id="servers" class="stats-container">
391
  </div>
392
  </div>
393
+ <div id="loginOverlay" class="login-overlay" style="display: none;">
394
+ <div class="login-box">
395
+ <h2>登录</h2>
396
+ <input type="text" id="username" placeholder="用户名">
397
+ <input type="password" id="password" placeholder="密码">
398
+ <button onclick="login()">登录</button>
399
+ <div id="loginError" class="login-error" style="display: none;"></div>
400
+ </div>
401
+ </div>
402
 
403
  <script>
404
  // 主题切换功能
 
437
 
438
  initTheme();
439
 
440
+ // 登录状态管理
441
+ function checkLoginStatus() {
442
+ const isLoggedIn = localStorage.getItem('isLoggedIn') === 'true';
443
+ document.getElementById('loginOverlay').style.display = isLoggedIn ? 'none' : 'flex';
444
+ document.getElementById('logoutButton').style.display = isLoggedIn ? 'block' : 'none';
445
+ if (isLoggedIn) {
446
+ updateActionButtons(true);
447
+ } else {
448
+ updateActionButtons(false);
449
+ }
450
+ }
451
+
452
+ function login() {
453
+ const username = document.getElementById('username').value;
454
+ const password = document.getElementById('password').value;
455
+ const loginError = document.getElementById('loginError');
456
+
457
+ // 模拟后端验证 (前端硬编码验证,实际环境中应通过接口验证)
458
+ fetch('/api/config').then(response => response.json()).then(config => {
459
+ // 这里仅做演示,实际环境中不应将用户凭据硬编码在前端
460
+ const adminUsername = 'admin'; // 默认值,实际应从环境变量获取,但这里无法直接获取后端环境变量
461
+ const adminPassword = 'password'; // 默认值,实际应从环境变量获取
462
+ if (username === adminUsername && password === adminPassword) {
463
+ localStorage.setItem('isLoggedIn', 'true');
464
+ loginError.style.display = 'none';
465
+ checkLoginStatus();
466
+ } else {
467
+ loginError.textContent = '用户名或密码错误';
468
+ loginError.style.display = 'block';
469
+ }
470
+ }).catch(err => {
471
+ loginError.textContent = '登录系统错误,请稍后重试';
472
+ loginError.style.display = 'block';
473
+ console.error('获取配置失败,无法验证登录:', err);
474
+ });
475
+ }
476
+
477
+ function logout() {
478
+ localStorage.removeItem('isLoggedIn');
479
+ checkLoginStatus();
480
+ }
481
+
482
+ function updateActionButtons(isLoggedIn) {
483
+ const cards = document.querySelectorAll('.server-card');
484
+ cards.forEach(card => {
485
+ const buttons = card.querySelector('.action-buttons');
486
+ if (buttons) {
487
+ buttons.style.display = isLoggedIn ? 'flex' : 'none';
488
+ }
489
+ });
490
+ }
491
+
492
+ window.onload = checkLoginStatus;
493
+
494
  async function getUsernames() {
495
  try {
496
  const response = await fetch('/api/config');
497
  const config = await response.json();
 
498
  const usernamesList = config.usernames ? config.usernames.split(',').map(name => name.trim()).filter(name => name) : [];
499
  document.getElementById('totalUsers').textContent = usernamesList.length;
500
  return usernamesList;
 
509
  try {
510
  const response = await fetch('/api/proxy/spaces');
511
  const instances = await response.json();
 
512
  return instances;
513
  } catch (error) {
514
  console.error("获取实例列表失败:", error);
 
524
 
525
  async connect(instanceId, username) {
526
  if (this.eventSources.has(instanceId)) {
 
527
  return;
528
  }
529
 
530
  try {
 
531
  const eventSource = new EventSource(
532
  `/api/proxy/live-metrics/${username}/${instanceId.split('/')[1]}`
533
  );
 
537
  eventSource.addEventListener("metric", (event) => {
538
  try {
539
  const data = JSON.parse(event.data);
 
540
  updateServerCard(data, instanceId);
541
  } catch (error) {
542
+ console.error(`解析数据失败 (${instanceId}):`, error);
543
  }
544
  });
545
 
546
  eventSource.onerror = (error) => {
 
547
  eventSource.close();
548
  this.eventSources.delete(instanceId);
 
549
  updateServerCard(null, instanceId, false);
550
  };
551
 
 
 
 
 
552
  this.eventSources.set(instanceId, eventSource);
 
553
  } catch (error) {
554
  console.error(`连接失败 (${username}/${instanceId}):`, error);
555
+ updateServerCard(null, instanceId, false);
556
  }
557
  }
558
 
559
  disconnectAll() {
 
560
  this.eventSources.forEach(es => es.close());
561
  this.eventSources.clear();
562
  }
 
567
  const serverStatus = new Map();
568
 
569
  async function initialize() {
570
+ await getUsernames();
571
  const instances = await fetchInstances();
572
  instances.forEach(instance => {
573
  renderInstanceCard(instance);
 
574
  if (instance.status.toLowerCase() === 'running') {
 
575
  metricsManager.connect(instance.repo_id, instance.owner);
 
 
576
  }
577
  });
578
  updateSummary();
 
603
  }
604
 
605
  const userServers = userGroup.querySelector('.user-servers');
 
606
  const cardId = `instance-${instanceId}`;
607
  let card = document.getElementById(cardId);
608
  if (!card) {
 
609
  card = document.createElement('div');
610
  card.id = cardId;
611
  card.className = 'server-card';
 
641
  <div class="metric-value download">N/A</div>
642
  </div>
643
  </div>
644
+ <div class="action-buttons" style="display: none;">
645
  <button class="action-button" onclick="restartSpace('${instance.repo_id}')">重启</button>
646
  <button class="action-button" onclick="rebuildSpace('${instance.repo_id}')">重建</button>
647
  </div>
648
  `;
649
  userServers.appendChild(card);
650
  }
 
651
  const statusDot = card.querySelector('.status-dot');
652
  const initialStatus = instance.status.toLowerCase();
653
  if (initialStatus === 'running') {
 
661
  }
662
 
663
  function updateServerCard(data, instanceId, isSleep = false) {
 
664
  const cardId = `instance-${instanceId}`;
665
  let card = document.getElementById(cardId);
666
  const instance = instanceMap.get(instanceId);
667
 
668
  if (!card && instance) {
 
669
  renderInstanceCard(instance);
670
  card = document.getElementById(cardId);
671
  }
672
 
673
  if (card) {
 
674
  const statusDot = card.querySelector('.status-dot');
675
  let upload = 'N/A', download = 'N/A', cpuUsage = 'N/A', memoryUsage = 'N/A';
676
  let isOnline = false;
 
683
  statusDot.className = 'status-dot status-online';
684
  isOnline = true;
685
  isSleep = false;
 
686
  } else {
 
687
  const currentStatus = instance?.status.toLowerCase() || 'unknown';
688
  if (currentStatus === 'running') {
689
  statusDot.className = 'status-dot status-online';
 
698
  isOnline = false;
699
  isSleep = false;
700
  }
 
701
  }
702
 
703
  card.querySelector('.cpu-usage').textContent = cpuUsage;
 
707
 
708
  serverStatus.set(instanceId, { lastSeen: Date.now(), isOnline, isSleep, data: data || null, status: instance?.status || 'unknown' });
709
  updateSummary();
 
 
710
  }
711
  }
712
 
713
  async function restartSpace(repoId) {
714
  try {
 
715
  const encodedRepoId = encodeURIComponent(repoId);
716
  const response = await fetch(`/api/proxy/restart/${encodedRepoId}`, { method: 'POST' });
717
  const result = await response.json();
 
729
 
730
  async function rebuildSpace(repoId) {
731
  try {
 
732
  const encodedRepoId = encodeURIComponent(repoId);
733
  const response = await fetch(`/api/proxy/rebuild/${encodedRepoId}`, { method: 'POST' });
734
  const result = await response.json();
 
751
  let totalDownload = 0;
752
 
753
  serverStatus.forEach((status, instanceId) => {
 
754
  const isRecentlyOnline = status.isOnline || status.status.toLowerCase() === 'running';
755
  if (isRecentlyOnline) {
756
  online++;