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';
|