// Toast notification system function showToast(message, type = "info") { const container = document.querySelector(".toast") || createToastContainer(); const toast = document.createElement("div"); toast.className = `toast-message toast-${type}`; toast.textContent = message; container.appendChild(toast); setTimeout(() => { toast.style.opacity = "0"; toast.style.transform = "translateX(40px)"; toast.style.transition = "all 0.3s ease"; setTimeout(() => toast.remove(), 300); }, 5000); } function createToastContainer() { const el = document.createElement("div"); el.className = "toast"; document.body.appendChild(el); return el; } // Modal helpers function openModal(id) { const modal = document.getElementById(id); if (modal) modal.classList.add("show"); } function closeModal(id) { const modal = document.getElementById(id); if (modal) modal.classList.remove("show"); } // Confirm dialog function confirmAction(message, onConfirm) { const existing = document.getElementById("confirm-modal"); if (existing) existing.remove(); const modal = document.createElement("div"); modal.id = "confirm-modal"; modal.className = "modal"; modal.innerHTML = ` `; document.body.appendChild(modal); openModal("confirm-modal"); document.getElementById("confirm-cancel").onclick = () => closeModal("confirm-modal"); modal.addEventListener("click", (e) => { if (e.target === modal) closeModal("confirm-modal"); }); } function setupConfirmCallback(callback) { document.getElementById("confirm-ok")?.addEventListener("click", () => { closeModal("confirm-modal"); callback(); }); } // Auto-refresh with HTMX function startAutoRefresh(endpoint, target, interval) { const el = document.createElement("div"); el.setAttribute("hx-get", endpoint); el.setAttribute("hx-target", `#${target}`); el.setAttribute("hx-swap", "innerHTML"); el.setAttribute("hx-trigger", `every ${interval}s`); el.setAttribute("hx-swap-oob", "true"); document.body.appendChild(el); } // Time formatting function formatTime(seconds) { if (seconds < 60) return `${seconds}s`; if (seconds < 3600) return `${Math.floor(seconds / 60)}m ${seconds % 60}s`; if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ${Math.floor((seconds % 3600) / 60)}m`; return `${Math.floor(seconds / 86400)}d ${Math.floor((seconds % 86400) / 3600)}h`; } // Bytes formatting function formatBytes(bytes) { if (bytes === 0) return "0 B"; const units = ["B", "KB", "MB", "GB", "TB"]; const i = Math.floor(Math.log(bytes) / Math.log(1024)); return `${(bytes / Math.pow(1024, i)).toFixed(i > 0 ? 1 : 0)} ${units[i]}`; } // Form helpers function resetForm(formId) { const form = document.getElementById(formId); if (form) form.reset(); } function fillForm(formId, data) { const form = document.getElementById(formId); if (!form) return; for (const [key, value] of Object.entries(data)) { const input = form.querySelector(`[name="${key}"]`); if (input) input.value = value; } } // HTMX event handlers document.body.addEventListener("htmx:afterSwap", (evt) => { const toastHeader = evt.detail.xhr?.getResponseHeader("X-Toast"); if (toastHeader) { const parts = toastHeader.split(":"); const msg = parts.slice(1).join(":").trim(); showToast(msg, parts[0]?.trim() || "info"); } }); document.body.addEventListener("htmx:responseError", (evt) => { const status = evt.detail.xhr?.status || 0; showToast(`Request failed (${status})`, "error"); }); document.body.addEventListener("htmx:beforeRequest", (evt) => { const target = evt.target; const btn = target.closest(".btn"); if (btn) { btn.dataset.originalText = btn.textContent; btn.disabled = true; btn.textContent = "Loading..."; } }); document.body.addEventListener("htmx:afterRequest", (evt) => { const target = evt.target; const btn = target.closest(".btn"); if (btn && btn.dataset.originalText !== undefined) { btn.disabled = false; btn.textContent = btn.dataset.originalText; delete btn.dataset.originalText; } }); // Close on escape document.addEventListener("keydown", (e) => { if (e.key === "Escape") { document.querySelectorAll(".modal.show").forEach((m) => m.classList.remove("show")); } });