feat: Web服务和Docker监控区支持折叠/展开
- 所有分组标题(Web服务/自定义服务/归档服务等)可点击折叠 - Docker容器列表、镜像列表标题可点击折叠 - 点击标题切换 chevron 旋转动画(→↓) - 折叠状态持久化到 localStorage,刷新保持
This commit is contained in:
@@ -67,6 +67,9 @@
|
|||||||
.history-item.toggle { border-left-color: #8b5cf6; }
|
.history-item.toggle { border-left-color: #8b5cf6; }
|
||||||
.modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.5); z-index: 50; display: flex; align-items: center; justify-content: center; }
|
.modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.5); z-index: 50; display: flex; align-items: center; justify-content: center; }
|
||||||
.modal-content { max-width: 600px; width: 90%; max-height: 80vh; overflow: auto; }
|
.modal-content { max-width: 600px; width: 90%; max-height: 80vh; overflow: auto; }
|
||||||
|
.section-header { cursor: pointer; user-select: none; }
|
||||||
|
.section-header:hover { opacity: 0.85; }
|
||||||
|
.chevron { display: inline-block; transition: transform 0.2s ease; font-size: 14px; }
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="min-h-screen text-gray-100">
|
<body class="min-h-screen text-gray-100">
|
||||||
@@ -555,20 +558,20 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 容器列表 -->
|
<!-- 容器列表 -->
|
||||||
<h3 class="text-sm font-semibold text-gray-300 mb-2 flex items-center gap-2">
|
<h3 class="text-sm font-semibold text-gray-300 mb-2 flex items-center gap-2 section-header" onclick="toggleSection('docker-containers-grid', this.querySelector('.chevron'))">
|
||||||
<i class="ri-computer-line text-blue-400"></i> 容器列表
|
<i class="ri-arrow-down-s-line chevron"></i><i class="ri-computer-line text-blue-400"></i> 容器列表
|
||||||
<span id="dockerContainerCount" class="text-xs text-gray-500"></span>
|
<span id="dockerContainerCount" class="text-xs text-gray-500"></span>
|
||||||
</h3>
|
</h3>
|
||||||
<div id="dockerContainersList" class="space-y-2 mb-6">
|
<div id="docker-containers-grid" class="space-y-2 mb-6">
|
||||||
<div class="text-gray-500 text-sm py-4 text-center">加载中...</div>
|
<div class="text-gray-500 text-sm py-4 text-center">加载中...</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 镜像列表 -->
|
<!-- 镜像列表 -->
|
||||||
<h3 class="text-sm font-semibold text-gray-300 mb-2 flex items-center gap-2">
|
<h3 class="text-sm font-semibold text-gray-300 mb-2 flex items-center gap-2 section-header" onclick="toggleSection('docker-images-grid', this.querySelector('.chevron'))">
|
||||||
<i class="ri-stack-line text-purple-400"></i> 镜像列表
|
<i class="ri-arrow-down-s-line chevron"></i><i class="ri-stack-line text-purple-400"></i> 镜像列表
|
||||||
<span id="dockerImageCount" class="text-xs text-gray-500"></span>
|
<span id="dockerImageCount" class="text-xs text-gray-500"></span>
|
||||||
</h3>
|
</h3>
|
||||||
<div id="dockerImagesList" class="space-y-1">
|
<div id="docker-images-grid" class="space-y-1">
|
||||||
<div class="text-gray-500 text-sm py-4 text-center">加载中...</div>
|
<div class="text-gray-500 text-sm py-4 text-center">加载中...</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -935,6 +938,40 @@
|
|||||||
let thresholds = { cpu: 80, cpu_temp: 80, memory: 85, disk: 90, interval: 60 };
|
let thresholds = { cpu: 80, cpu_temp: 80, memory: 85, disk: 90, interval: 60 };
|
||||||
let desktopNotifyEnabled = false;
|
let desktopNotifyEnabled = false;
|
||||||
|
|
||||||
|
// ==================== 折叠/展开 ====================
|
||||||
|
function getCollapsedState() {
|
||||||
|
const stored = localStorage.getItem('collapsedSections');
|
||||||
|
return stored ? JSON.parse(stored) : {};
|
||||||
|
}
|
||||||
|
function saveCollapsedState(state) {
|
||||||
|
localStorage.setItem('collapsedSections', JSON.stringify(state));
|
||||||
|
}
|
||||||
|
function toggleSection(sectionId, chevronEl) {
|
||||||
|
const grid = document.getElementById(sectionId);
|
||||||
|
if (!grid) return;
|
||||||
|
const state = getCollapsedState();
|
||||||
|
if (grid.classList.contains('hidden')) {
|
||||||
|
grid.classList.remove('hidden');
|
||||||
|
if (chevronEl) chevronEl.style.transform = 'rotate(0deg)';
|
||||||
|
state[sectionId] = false;
|
||||||
|
} else {
|
||||||
|
grid.classList.add('hidden');
|
||||||
|
if (chevronEl) chevronEl.style.transform = 'rotate(-90deg)';
|
||||||
|
state[sectionId] = true;
|
||||||
|
}
|
||||||
|
saveCollapsedState(state);
|
||||||
|
}
|
||||||
|
function applyCollapsedState(sectionId, gridEl, chevronEl) {
|
||||||
|
const state = getCollapsedState();
|
||||||
|
if (state[sectionId]) {
|
||||||
|
gridEl.classList.add('hidden');
|
||||||
|
if (chevronEl) chevronEl.style.transform = 'rotate(-90deg)';
|
||||||
|
} else {
|
||||||
|
gridEl.classList.remove('hidden');
|
||||||
|
if (chevronEl) chevronEl.style.transform = 'rotate(0deg)';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function switchTab(tab) {
|
function switchTab(tab) {
|
||||||
currentTab = tab;
|
currentTab = tab;
|
||||||
document.querySelectorAll('.tab-btn').forEach(btn => {
|
document.querySelectorAll('.tab-btn').forEach(btn => {
|
||||||
@@ -1445,23 +1482,38 @@
|
|||||||
const archivedProjs = projs.filter(p => activeStatus[p.id] === false);
|
const archivedProjs = projs.filter(p => activeStatus[p.id] === false);
|
||||||
|
|
||||||
if (activeProjs.length > 0) {
|
if (activeProjs.length > 0) {
|
||||||
html += `<div class="mb-6"><h2 class="text-lg font-semibold text-gray-300 mb-3 flex items-center gap-2"><i class="${typeInfo.icon} text-${typeInfo.color}-400"></i>${typeInfo.name}<span class="text-sm text-gray-500">(${activeProjs.length})</span></h2><div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3">`;
|
const secId = 'section-web-active';
|
||||||
|
html += `<div class="mb-6"><h2 class="text-lg font-semibold text-gray-300 mb-3 flex items-center gap-2 section-header" onclick="toggleSection('${secId}', this.querySelector('.chevron'))"><i class="ri-arrow-down-s-line chevron"></i><i class="${typeInfo.icon} text-${typeInfo.color}-400"></i>${typeInfo.name}<span class="text-sm text-gray-500">(${activeProjs.length})</span></h2><div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3" id="${secId}">`;
|
||||||
activeProjs.forEach(p => { html += renderProjectCard(p, true); });
|
activeProjs.forEach(p => { html += renderProjectCard(p, true); });
|
||||||
html += '</div></div>';
|
html += '</div></div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (archivedProjs.length > 0) {
|
if (archivedProjs.length > 0) {
|
||||||
html += `<div class="mb-6 opacity-60"><h2 class="text-lg font-semibold text-gray-400 mb-3 flex items-center gap-2"><i class="ri-archive-line text-gray-400"></i>归档服务<span class="text-sm text-gray-500">(${archivedProjs.length})</span></h2><div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3">`;
|
const secId = 'section-web-archived';
|
||||||
|
html += `<div class="mb-6 opacity-60"><h2 class="text-lg font-semibold text-gray-400 mb-3 flex items-center gap-2 section-header" onclick="toggleSection('${secId}', this.querySelector('.chevron'))"><i class="ri-arrow-down-s-line chevron"></i><i class="ri-archive-line text-gray-400"></i>归档服务<span class="text-sm text-gray-500">(${archivedProjs.length})</span></h2><div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3" id="${secId}">`;
|
||||||
archivedProjs.forEach(p => { html += renderProjectCard(p, false); });
|
archivedProjs.forEach(p => { html += renderProjectCard(p, false); });
|
||||||
html += '</div></div>';
|
html += '</div></div>';
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
html += `<div class="mb-6"><h2 class="text-lg font-semibold text-gray-300 mb-3 flex items-center gap-2"><i class="${typeInfo.icon} text-${typeInfo.color}-400"></i>${typeInfo.name}<span class="text-sm text-gray-500">(${projs.length})</span></h2><div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3">`;
|
const secId = 'section-' + type;
|
||||||
|
html += `<div class="mb-6"><h2 class="text-lg font-semibold text-gray-300 mb-3 flex items-center gap-2 section-header" onclick="toggleSection('${secId}', this.querySelector('.chevron'))"><i class="ri-arrow-down-s-line chevron"></i><i class="${typeInfo.icon} text-${typeInfo.color}-400"></i>${typeInfo.name}<span class="text-sm text-gray-500">(${projs.length})</span></h2><div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3" id="${secId}">`;
|
||||||
projs.forEach(p => { html += renderProjectCard(p, true); });
|
projs.forEach(p => { html += renderProjectCard(p, true); });
|
||||||
html += '</div></div>';
|
html += '</div></div>';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
list.innerHTML = html;
|
list.innerHTML = html;
|
||||||
|
// 应用已保存的折叠状态
|
||||||
|
setTimeout(() => {
|
||||||
|
const state = getCollapsedState();
|
||||||
|
for (const [secId, collapsed] of Object.entries(state)) {
|
||||||
|
const grid = document.getElementById(secId);
|
||||||
|
const chevron = grid?.parentElement?.querySelector('.chevron');
|
||||||
|
if (grid && collapsed) {
|
||||||
|
grid.classList.add('hidden');
|
||||||
|
if (chevron) chevron.style.transform = 'rotate(-90deg)';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getActiveStatus() {
|
function getActiveStatus() {
|
||||||
@@ -2828,7 +2880,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderDockerContainers(containers) {
|
function renderDockerContainers(containers) {
|
||||||
const list = document.getElementById('dockerContainersList');
|
const list = document.getElementById('docker-containers-grid');
|
||||||
if (containers.length === 0) {
|
if (containers.length === 0) {
|
||||||
list.innerHTML = '<div class="text-gray-500 text-sm py-4 text-center">暂无容器</div>';
|
list.innerHTML = '<div class="text-gray-500 text-sm py-4 text-center">暂无容器</div>';
|
||||||
return;
|
return;
|
||||||
@@ -2860,7 +2912,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderDockerImages(images) {
|
function renderDockerImages(images) {
|
||||||
const list = document.getElementById('dockerImagesList');
|
const list = document.getElementById('docker-images-grid');
|
||||||
if (images.length === 0) {
|
if (images.length === 0) {
|
||||||
list.innerHTML = '<div class="text-gray-500 text-sm py-4 text-center">暂无镜像</div>';
|
list.innerHTML = '<div class="text-gray-500 text-sm py-4 text-center">暂无镜像</div>';
|
||||||
return;
|
return;
|
||||||
|
|||||||
Reference in New Issue
Block a user