Update public/index.html
Browse files- public/index.html +219 -97
public/index.html
CHANGED
|
@@ -630,7 +630,63 @@
|
|
| 630 |
.loading-text {
|
| 631 |
font-size: 0.9375rem;
|
| 632 |
}
|
| 633 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 634 |
/* 动画 */
|
| 635 |
@keyframes fadeIn {
|
| 636 |
from { opacity: 0; transform: translateY(20px); }
|
|
@@ -752,6 +808,10 @@
|
|
| 752 |
min-width: 0;
|
| 753 |
padding: 8px 10px;
|
| 754 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 755 |
}
|
| 756 |
</style>
|
| 757 |
</head>
|
|
@@ -1218,17 +1278,40 @@
|
|
| 1218 |
}
|
| 1219 |
|
| 1220 |
// 获取实例列表
|
| 1221 |
-
|
| 1222 |
-
|
| 1223 |
-
|
| 1224 |
-
|
| 1225 |
-
|
| 1226 |
-
|
| 1227 |
-
|
| 1228 |
-
|
| 1229 |
-
|
| 1230 |
-
|
| 1231 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1232 |
|
| 1233 |
// 指标管理器
|
| 1234 |
class MetricsManager {
|
|
@@ -1386,91 +1469,120 @@
|
|
| 1386 |
}
|
| 1387 |
|
| 1388 |
// 创建服务器卡片
|
| 1389 |
-
|
| 1390 |
-
|
| 1391 |
-
|
| 1392 |
-
|
| 1393 |
-
|
| 1394 |
-
|
| 1395 |
-
|
| 1396 |
-
|
| 1397 |
-
|
| 1398 |
-
|
| 1399 |
-
|
| 1400 |
-
|
| 1401 |
-
|
| 1402 |
-
|
| 1403 |
-
|
| 1404 |
-
|
| 1405 |
-
|
| 1406 |
-
|
| 1407 |
-
|
| 1408 |
-
|
| 1409 |
-
|
| 1410 |
-
|
| 1411 |
-
|
| 1412 |
-
|
| 1413 |
-
|
| 1414 |
-
|
| 1415 |
-
|
| 1416 |
-
|
| 1417 |
-
|
| 1418 |
-
|
| 1419 |
-
|
| 1420 |
-
|
| 1421 |
-
|
| 1422 |
-
|
| 1423 |
-
|
| 1424 |
-
|
| 1425 |
-
|
| 1426 |
-
|
| 1427 |
-
|
| 1428 |
-
|
| 1429 |
-
|
| 1430 |
-
|
| 1431 |
-
|
| 1432 |
-
|
| 1433 |
-
|
| 1434 |
-
|
| 1435 |
-
|
| 1436 |
-
|
| 1437 |
-
|
| 1438 |
-
|
| 1439 |
-
|
| 1440 |
-
|
| 1441 |
-
|
| 1442 |
-
|
| 1443 |
-
|
| 1444 |
-
|
| 1445 |
-
|
| 1446 |
-
|
| 1447 |
-
|
| 1448 |
-
|
| 1449 |
-
|
| 1450 |
-
|
| 1451 |
-
|
| 1452 |
-
|
| 1453 |
-
|
| 1454 |
-
|
| 1455 |
-
|
| 1456 |
-
|
| 1457 |
-
|
| 1458 |
-
|
| 1459 |
-
|
| 1460 |
-
|
| 1461 |
-
|
| 1462 |
-
|
| 1463 |
-
|
| 1464 |
-
|
| 1465 |
-
|
| 1466 |
-
|
| 1467 |
-
|
| 1468 |
-
|
| 1469 |
-
|
| 1470 |
-
|
| 1471 |
-
|
| 1472 |
-
|
| 1473 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1474 |
|
| 1475 |
// 更新服务器卡片
|
| 1476 |
function updateServerCard(data, instanceId, isSleep = false) {
|
|
@@ -1678,6 +1790,16 @@
|
|
| 1678 |
document.getElementById('totalDownload').textContent = `${formatBytes(totalDownload)}/s`;
|
| 1679 |
}
|
| 1680 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1681 |
// 格式化字节数
|
| 1682 |
function formatBytes(bytes) {
|
| 1683 |
if (bytes === 0) return '0 B';
|
|
|
|
| 630 |
.loading-text {
|
| 631 |
font-size: 0.9375rem;
|
| 632 |
}
|
| 633 |
+
.instance-url {
|
| 634 |
+
display: flex;
|
| 635 |
+
align-items: center;
|
| 636 |
+
background: var(--network-background);
|
| 637 |
+
border-radius: var(--border-radius-sm);
|
| 638 |
+
padding: 10px 12px;
|
| 639 |
+
margin: 15px 0;
|
| 640 |
+
font-size: 0.875rem;
|
| 641 |
+
border: 1px solid var(--metric-border);
|
| 642 |
+
overflow: hidden;
|
| 643 |
+
}
|
| 644 |
+
.url-link {
|
| 645 |
+
color: var(--primary-color);
|
| 646 |
+
text-decoration: none;
|
| 647 |
+
white-space: nowrap;
|
| 648 |
+
overflow: hidden;
|
| 649 |
+
text-overflow: ellipsis;
|
| 650 |
+
flex: 1;
|
| 651 |
+
display: flex;
|
| 652 |
+
align-items: center;
|
| 653 |
+
gap: 6px;
|
| 654 |
+
}
|
| 655 |
+
.url-link:hover {
|
| 656 |
+
text-decoration: underline;
|
| 657 |
+
}
|
| 658 |
+
.btn-icon {
|
| 659 |
+
background: transparent;
|
| 660 |
+
border: none;
|
| 661 |
+
color: var(--secondary-text);
|
| 662 |
+
width: 28px;
|
| 663 |
+
height: 28px;
|
| 664 |
+
border-radius: 4px;
|
| 665 |
+
display: flex;
|
| 666 |
+
align-items: center;
|
| 667 |
+
justify-content: center;
|
| 668 |
+
cursor: pointer;
|
| 669 |
+
transition: all var(--transition-fast);
|
| 670 |
+
}
|
| 671 |
+
.btn-icon:hover {
|
| 672 |
+
background: var(--action-button-bg);
|
| 673 |
+
color: var(--text-color);
|
| 674 |
+
}
|
| 675 |
+
.instance-info {
|
| 676 |
+
margin-bottom: 15px;
|
| 677 |
+
font-size: 0.8125rem;
|
| 678 |
+
color: var(--secondary-text);
|
| 679 |
+
}
|
| 680 |
+
.info-item {
|
| 681 |
+
display: flex;
|
| 682 |
+
align-items: center;
|
| 683 |
+
gap: 6px;
|
| 684 |
+
}
|
| 685 |
+
.info-item i {
|
| 686 |
+
font-size: 0.9375rem;
|
| 687 |
+
opacity: 0.8;
|
| 688 |
+
}
|
| 689 |
+
|
| 690 |
/* 动画 */
|
| 691 |
@keyframes fadeIn {
|
| 692 |
from { opacity: 0; transform: translateY(20px); }
|
|
|
|
| 808 |
min-width: 0;
|
| 809 |
padding: 8px 10px;
|
| 810 |
}
|
| 811 |
+
.instance-url {
|
| 812 |
+
padding: 8px 10px;
|
| 813 |
+
font-size: 0.8125rem;
|
| 814 |
+
}
|
| 815 |
}
|
| 816 |
</style>
|
| 817 |
</head>
|
|
|
|
| 1278 |
}
|
| 1279 |
|
| 1280 |
// 获取实例列表
|
| 1281 |
+
async function fetchInstances() {
|
| 1282 |
+
try {
|
| 1283 |
+
const response = await fetch('/api/proxy/spaces');
|
| 1284 |
+
const instances = await response.json();
|
| 1285 |
+
|
| 1286 |
+
// 过滤掉敏感信息
|
| 1287 |
+
return instances.map(instance => {
|
| 1288 |
+
// 创建一个新对象,只包含需要的字段
|
| 1289 |
+
const safeInstance = {
|
| 1290 |
+
repo_id: instance.repo_id,
|
| 1291 |
+
name: instance.name,
|
| 1292 |
+
owner: instance.owner,
|
| 1293 |
+
username: instance.username,
|
| 1294 |
+
url: instance.url,
|
| 1295 |
+
status: instance.status,
|
| 1296 |
+
last_modified: instance.last_modified,
|
| 1297 |
+
created_at: instance.created_at,
|
| 1298 |
+
sdk: instance.sdk,
|
| 1299 |
+
tags: instance.tags,
|
| 1300 |
+
private: instance.private,
|
| 1301 |
+
app_port: instance.app_port
|
| 1302 |
+
};
|
| 1303 |
+
|
| 1304 |
+
// 确保删除任何敏感字段
|
| 1305 |
+
delete safeInstance.token;
|
| 1306 |
+
|
| 1307 |
+
return safeInstance;
|
| 1308 |
+
});
|
| 1309 |
+
} catch (error) {
|
| 1310 |
+
console.error("获取实例列表失败:", error);
|
| 1311 |
+
showToast('获取实例列表失败,请刷新页面重试', 'error');
|
| 1312 |
+
return [];
|
| 1313 |
+
}
|
| 1314 |
+
}
|
| 1315 |
|
| 1316 |
// 指标管理器
|
| 1317 |
class MetricsManager {
|
|
|
|
| 1469 |
}
|
| 1470 |
|
| 1471 |
// 创建服务器卡片
|
| 1472 |
+
function createServerCard(instance) {
|
| 1473 |
+
const instanceId = instance.repo_id;
|
| 1474 |
+
|
| 1475 |
+
// 存储安全的实例信息(确保不包含token)
|
| 1476 |
+
const safeInstance = {...instance};
|
| 1477 |
+
delete safeInstance.token;
|
| 1478 |
+
instanceMap.set(instanceId, safeInstance);
|
| 1479 |
+
|
| 1480 |
+
// 创建卡片元素
|
| 1481 |
+
const card = document.createElement('div');
|
| 1482 |
+
card.id = `instance-${instanceId}`;
|
| 1483 |
+
card.className = 'server-card';
|
| 1484 |
+
|
| 1485 |
+
// 设置状态样式
|
| 1486 |
+
let statusClass = 'status-offline';
|
| 1487 |
+
let statusText = '离线';
|
| 1488 |
+
|
| 1489 |
+
if (instance.status.toLowerCase() === 'running') {
|
| 1490 |
+
statusClass = 'status-online';
|
| 1491 |
+
statusText = '在线';
|
| 1492 |
+
} else if (instance.status.toLowerCase() === 'sleeping') {
|
| 1493 |
+
statusClass = 'status-sleep';
|
| 1494 |
+
statusText = '休眠';
|
| 1495 |
+
}
|
| 1496 |
+
|
| 1497 |
+
// 格式化最后修改时间
|
| 1498 |
+
const lastModified = new Date(instance.last_modified).toLocaleString('zh-CN', {
|
| 1499 |
+
year: 'numeric',
|
| 1500 |
+
month: '2-digit',
|
| 1501 |
+
day: '2-digit',
|
| 1502 |
+
hour: '2-digit',
|
| 1503 |
+
minute: '2-digit'
|
| 1504 |
+
});
|
| 1505 |
+
|
| 1506 |
+
// 设置卡片内容
|
| 1507 |
+
card.innerHTML = `
|
| 1508 |
+
<div class="server-header">
|
| 1509 |
+
<div class="server-name">
|
| 1510 |
+
<i class="ri-server-line"></i>
|
| 1511 |
+
<div>
|
| 1512 |
+
${instance.name}
|
| 1513 |
+
<div class="server-id">${instance.repo_id}</div>
|
| 1514 |
+
</div>
|
| 1515 |
+
</div>
|
| 1516 |
+
<div class="status-indicator">
|
| 1517 |
+
<span class="status-dot ${statusClass}"></span>
|
| 1518 |
+
<span>${statusText}</span>
|
| 1519 |
+
</div>
|
| 1520 |
+
</div>
|
| 1521 |
+
|
| 1522 |
+
<div class="instance-url">
|
| 1523 |
+
<a href="${instance.url}" target="_blank" class="url-link">
|
| 1524 |
+
<i class="ri-external-link-line"></i> ${instance.url}
|
| 1525 |
+
</a>
|
| 1526 |
+
<button class="btn-icon copy-url" onclick="copyToClipboard('${instance.url}')" title="复制URL">
|
| 1527 |
+
<i class="ri-file-copy-line"></i>
|
| 1528 |
+
</button>
|
| 1529 |
+
</div>
|
| 1530 |
+
|
| 1531 |
+
<div class="instance-info">
|
| 1532 |
+
<div class="info-item">
|
| 1533 |
+
<i class="ri-time-line"></i> 最后更新: ${lastModified}
|
| 1534 |
+
</div>
|
| 1535 |
+
</div>
|
| 1536 |
+
|
| 1537 |
+
<div class="metric-grid">
|
| 1538 |
+
<div class="metric-item">
|
| 1539 |
+
<div class="metric-label">
|
| 1540 |
+
<i class="ri-cpu-line"></i> CPU
|
| 1541 |
+
</div>
|
| 1542 |
+
<div class="metric-value cpu-usage">N/A</div>
|
| 1543 |
+
</div>
|
| 1544 |
+
<div class="metric-item">
|
| 1545 |
+
<div class="metric-label">
|
| 1546 |
+
<i class="ri-hard-drive-2-line"></i> 内存
|
| 1547 |
+
</div>
|
| 1548 |
+
<div class="metric-value memory-usage">N/A</div>
|
| 1549 |
+
</div>
|
| 1550 |
+
<div class="metric-item">
|
| 1551 |
+
<div class="metric-label">
|
| 1552 |
+
<i class="ri-upload-2-line"></i> 上传
|
| 1553 |
+
</div>
|
| 1554 |
+
<div class="metric-value upload">N/A</div>
|
| 1555 |
+
</div>
|
| 1556 |
+
<div class="metric-item">
|
| 1557 |
+
<div class="metric-label">
|
| 1558 |
+
<i class="ri-download-2-line"></i> 下载
|
| 1559 |
+
</div>
|
| 1560 |
+
<div class="metric-value download">N/A</div>
|
| 1561 |
+
</div>
|
| 1562 |
+
</div>
|
| 1563 |
+
|
| 1564 |
+
<div class="action-buttons" style="display: ${isLoggedIn ? 'flex' : 'none'};">
|
| 1565 |
+
<button class="btn" onclick="showConfirmDialog('restart', '${instance.repo_id}', '确认重启', '您确定要重启实例 ${instance.name} (${instance.repo_id}) 吗?')">
|
| 1566 |
+
<i class="ri-restart-line"></i> 重启
|
| 1567 |
+
</button>
|
| 1568 |
+
<button class="btn" onclick="showConfirmDialog('rebuild', '${instance.repo_id}', '确认重建', '您确定要重建实例 ${instance.name} (${instance.repo_id}) 吗?这将重新构建整个实例。')">
|
| 1569 |
+
<i class="ri-building-line"></i> 重建
|
| 1570 |
+
</button>
|
| 1571 |
+
</div>
|
| 1572 |
+
`;
|
| 1573 |
+
|
| 1574 |
+
// 记录服务器状态
|
| 1575 |
+
serverStatus.set(instanceId, {
|
| 1576 |
+
lastSeen: Date.now(),
|
| 1577 |
+
isOnline: instance.status.toLowerCase() === 'running',
|
| 1578 |
+
isSleep: instance.status.toLowerCase() === 'sleeping',
|
| 1579 |
+
data: null,
|
| 1580 |
+
status: instance.status
|
| 1581 |
+
});
|
| 1582 |
+
|
| 1583 |
+
return card;
|
| 1584 |
+
}
|
| 1585 |
+
|
| 1586 |
|
| 1587 |
// 更新服务器卡片
|
| 1588 |
function updateServerCard(data, instanceId, isSleep = false) {
|
|
|
|
| 1790 |
document.getElementById('totalDownload').textContent = `${formatBytes(totalDownload)}/s`;
|
| 1791 |
}
|
| 1792 |
|
| 1793 |
+
// 添加在 updateSummary 函数之后
|
| 1794 |
+
function copyToClipboard(text) {
|
| 1795 |
+
navigator.clipboard.writeText(text).then(() => {
|
| 1796 |
+
showToast('URL已复制到剪贴板', 'success');
|
| 1797 |
+
}).catch(err => {
|
| 1798 |
+
console.error('复制失败:', err);
|
| 1799 |
+
showToast('复制失败,请手动复制', 'error');
|
| 1800 |
+
});
|
| 1801 |
+
}
|
| 1802 |
+
|
| 1803 |
// 格式化字节数
|
| 1804 |
function formatBytes(bytes) {
|
| 1805 |
if (bytes === 0) return '0 B';
|