feat: Store accordion state in cookies
- Accordion state (collapsed/expanded) saved to cookies per group - Chevron icon toggles correctly (up/down) - State persists across page reloads for 30 days - First accordion group opens by default
This commit is contained in:
parent
3872d1df30
commit
4a8a2d66fb
|
|
@ -79,18 +79,23 @@
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
{% for groupName, group in groups %}
|
{% for groupName, group in groups %}
|
||||||
|
{% set groupSlug = groupName|lower|replace({' ': '_', ' ': ''}) %}
|
||||||
<div class="card mb-3 border-0 shadow-sm">
|
<div class="card mb-3 border-0 shadow-sm">
|
||||||
<div class="card-header text-white py-2" data-bs-toggle="collapse" data-bs-target="#group-{{ loop.index }}" style="cursor: pointer; background-color: {{ group.color|default('#6c757d') }};">
|
<div class="card-header text-white py-2 accordion-header"
|
||||||
|
data-bs-toggle="collapse"
|
||||||
|
data-bs-target="#group-{{ loop.index }}"
|
||||||
|
data-group="{{ groupSlug }}"
|
||||||
|
style="cursor: pointer; background-color: {{ group.color|default('#6c757d') }};">
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
<h5 class="mb-0">
|
<h5 class="mb-0">
|
||||||
<i class="{{ group.icon|default('fa-server') }}"></i>
|
<i class="{{ group.icon|default('fa-server') }}"></i>
|
||||||
{{ groupName }}
|
{{ groupName }}
|
||||||
<span class="badge bg-light text-dark ms-2">{{ group.servers|length }}</span>
|
<span class="badge bg-light text-dark ms-2">{{ group.servers|length }}</span>
|
||||||
</h5>
|
</h5>
|
||||||
<i class="fas fa-chevron-down"></i>
|
<i class="fas accordion-icon"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="group-{{ loop.index }}" class="collapse show">
|
<div id="group-{{ loop.index }}" class="collapse" data-group="{{ groupSlug }}">
|
||||||
<div class="card-body p-2">
|
<div class="card-body p-2">
|
||||||
<div class="row g-2">
|
<div class="row g-2">
|
||||||
{% for server in group.servers %}
|
{% for server in group.servers %}
|
||||||
|
|
@ -193,37 +198,98 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<!-- AJAX автообновление -->
|
<!-- AJAX автообновление + Accordion состояние -->
|
||||||
<script>
|
<script>
|
||||||
(function() {
|
(function() {
|
||||||
let updateInterval = null;
|
const COOKIE_NAME = 'dashboard_accordion';
|
||||||
|
|
||||||
|
// Получить состояние accordion из cookies
|
||||||
|
function getAccordionState() {
|
||||||
|
const cookies = document.cookie.split(';');
|
||||||
|
for (let c of cookies) {
|
||||||
|
c = c.trim();
|
||||||
|
if (c.startsWith(COOKIE_NAME + '=')) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(decodeURIComponent(c.substring(COOKIE_NAME.length + 1)));
|
||||||
|
} catch(e) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сохранить состояние accordion в cookies
|
||||||
|
function saveAccordionState(state) {
|
||||||
|
document.cookie = COOKIE_NAME + '=' + encodeURIComponent(JSON.stringify(state)) +
|
||||||
|
'; path=/; max-age=' + (30 * 24 * 60 * 60); // 30 дней
|
||||||
|
}
|
||||||
|
|
||||||
|
// Инициализация accordion из cookies
|
||||||
|
function initAccordionFromCookies() {
|
||||||
|
const state = getAccordionState();
|
||||||
|
document.querySelectorAll('.accordion-header').forEach(header => {
|
||||||
|
const groupId = header.dataset.group;
|
||||||
|
const targetId = header.dataset.bsTarget;
|
||||||
|
const target = document.querySelector(targetId);
|
||||||
|
|
||||||
|
if (target && state[groupId] === 'open') {
|
||||||
|
target.classList.add('show');
|
||||||
|
header.querySelector('.accordion-icon').className = 'fas fa-chevron-up';
|
||||||
|
} else {
|
||||||
|
header.querySelector('.accordion-icon').className = 'fas fa-chevron-down';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обработчик клика на accordion header
|
||||||
|
function setupAccordionHandlers() {
|
||||||
|
const state = getAccordionState();
|
||||||
|
|
||||||
|
document.querySelectorAll('.accordion-header').forEach(header => {
|
||||||
|
const groupId = header.dataset.group;
|
||||||
|
const targetId = header.dataset.bsTarget;
|
||||||
|
const target = document.querySelector(targetId);
|
||||||
|
const icon = header.querySelector('.accordion-icon');
|
||||||
|
|
||||||
|
// Клик переключает состояние
|
||||||
|
header.addEventListener('click', function() {
|
||||||
|
const isOpen = target.classList.contains('show');
|
||||||
|
if (isOpen) {
|
||||||
|
target.classList.remove('show');
|
||||||
|
icon.className = 'fas fa-chevron-down';
|
||||||
|
state[groupId] = 'closed';
|
||||||
|
} else {
|
||||||
|
target.classList.add('show');
|
||||||
|
icon.className = 'fas fa-chevron-up';
|
||||||
|
state[groupId] = 'open';
|
||||||
|
}
|
||||||
|
saveAccordionState(state);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// AJAX обновление данных
|
||||||
function updateDashboard() {
|
function updateDashboard() {
|
||||||
fetch('/api/dashboard/stats')
|
fetch('/api/dashboard/stats')
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
data.forEach(server => {
|
data.forEach(server => {
|
||||||
// CPU
|
|
||||||
const cpuVal = document.getElementById('cpu-val-' + server.id);
|
const cpuVal = document.getElementById('cpu-val-' + server.id);
|
||||||
if (cpuVal && server.metrics.cpu_load) {
|
if (cpuVal && server.metrics.cpu_load) {
|
||||||
cpuVal.textContent = server.metrics.cpu_load.value + '%';
|
cpuVal.textContent = server.metrics.cpu_load.value + '%';
|
||||||
}
|
}
|
||||||
|
|
||||||
// RAM
|
|
||||||
const ramVal = document.getElementById('ram-val-' + server.id);
|
const ramVal = document.getElementById('ram-val-' + server.id);
|
||||||
if (ramVal && server.metrics.ram_used) {
|
if (ramVal && server.metrics.ram_used) {
|
||||||
ramVal.textContent = server.metrics.ram_used.value + '%';
|
ramVal.textContent = server.metrics.ram_used.value + '%';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disk
|
|
||||||
const diskVal = document.getElementById('disk-val-' + server.id);
|
const diskVal = document.getElementById('disk-val-' + server.id);
|
||||||
if (diskVal && server.metrics.disk) {
|
if (diskVal && server.metrics.disk) {
|
||||||
diskVal.textContent = server.metrics.disk.value + '%';
|
diskVal.textContent = server.metrics.disk.value + '%';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Updated time
|
|
||||||
const updatedAt = document.getElementById('updated-at-' + server.id);
|
const updatedAt = document.getElementById('updated-at-' + server.id);
|
||||||
if (updatedAt) {
|
if (updatedAt && server.updated_at) {
|
||||||
updatedAt.textContent = server.updated_at.split(' ')[1].substring(0, 5);
|
updatedAt.textContent = server.updated_at.split(' ')[1].substring(0, 5);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -231,9 +297,11 @@
|
||||||
.catch(err => console.log('Dashboard update error:', err));
|
.catch(err => console.log('Dashboard update error:', err));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Запускаем обновление каждые 30 секунд
|
// Запуск при загрузке страницы
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
updateInterval = setInterval(updateDashboard, 30000);
|
initAccordionFromCookies();
|
||||||
|
setupAccordionHandlers();
|
||||||
|
setInterval(updateDashboard, 30000);
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue