Files
haproxy-dashboard/static/js/main.js
Mateusz Gruszczyński 3c8b9b062d redactor
2025-11-01 21:25:53 +01:00

96 lines
3.9 KiB
JavaScript

// Bootstrap helpers
(() => {
'use strict';
// 1) Show Flask flash messages as Bootstrap toasts
const flashes = document.getElementById('_flash_msgs');
if (flashes) {
try{
const msgs = JSON.parse(flashes.dataset.msgs || "[]");
const stack = document.getElementById('toast-stack');
msgs.forEach((m) => {
const el = document.createElement('div');
el.className = 'toast align-items-center text-bg-primary border-0';
el.role = 'alert'; el.ariaLive = 'assertive'; el.ariaAtomic = 'true';
el.innerHTML = `<div class="d-flex">
<div class="toast-body"><i class="bi bi-info-circle me-2"></i>${m}</div>
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Zamknij"></button>
</div>`;
stack.appendChild(el);
const t = new bootstrap.Toast(el, { delay: 3500 });
t.show();
});
}catch(e){ console.warn('Toast parse error', e); }
}
// 2) Client-side validation (Bootstrap)
const forms = document.querySelectorAll('form');
Array.from(forms).forEach(form => {
form.addEventListener('submit', event => {
if (!form.checkValidity()) {
event.preventDefault();
event.stopPropagation();
} else {
// 3) Micro-loader on submit
const submitBtn = form.querySelector('[type="submit"]');
if (submitBtn && !submitBtn.dataset.loading) {
submitBtn.dataset.loading = '1';
submitBtn.disabled = true;
const original = submitBtn.innerHTML;
submitBtn.dataset.original = original;
submitBtn.innerHTML = `<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> Przetwarzanie...`;
}
}
form.classList.add('was-validated');
}, false);
});
// 4) Auto-accordion for long forms:
// If a form contains multiple H2/H3 sections, group content between them.
const targetForms = document.querySelectorAll('form');
targetForms.forEach(form => {
const headers = Array.from(form.querySelectorAll(':scope h2, :scope h3')).filter(h => h.textContent.trim().length>0);
if (headers.length >= 2) {
const acc = document.createElement('div');
acc.className = 'accordion my-3'; acc.id = 'autoAccordion-' + Math.random().toString(36).slice(2);
let startIdx = 0;
headers.forEach((h, i) => {
const itemId = acc.id + '-item-' + i;
const headerId = itemId + '-hdr';
const bodyId = itemId + '-body';
const item = document.createElement('div'); item.className = 'accordion-item bg-body';
const btnText = h.textContent.trim();
// gather content until next header or end
const chunk = [];
let el = h.nextElementSibling;
while (el && !headers.includes(el)) {
chunk.push(el);
el = el.nextElementSibling;
}
h.remove();
const bodyInner = document.createElement('div');
chunk.forEach(c => bodyInner.appendChild(c));
item.innerHTML = `
<h2 class="accordion-header" id="${headerId}">
<button class="accordion-button ${i>0 ? 'collapsed' : ''}" type="button" data-bs-toggle="collapse" data-bs-target="#${bodyId}" aria-expanded="${i===0?'true':'false'}" aria-controls="${bodyId}">
${btnText}
</button>
</h2>
<div id="${bodyId}" class="accordion-collapse collapse ${i===0?'show':''}" aria-labelledby="${headerId}" data-bs-parent="#${acc.id}">
<div class="accordion-body"></div>
</div>`;
item.querySelector('.accordion-body').appendChild(bodyInner);
acc.appendChild(item);
});
// Insert at top of form
form.prepend(acc);
}
});
// Focus first control
const first = document.querySelector('form input, form select, form textarea');
if (first) first.focus({preventScroll:true});
})();