</div>
<!-- Bottoni -->
- <div class="col-12 col-md-auto d-flex gap-2 flex-wrap">
- <button class="btn btn-primary" title="Add Alias" aria-label="Add Alias"
+ <div class="col-12 col-md-auto d-flex align-items-center gap-2 flex-wrap">
+
+ <!-- Add alias -->
+ <button
+ class="btn btn-primary"
+ title="Add Alias"
+ aria-label="Add Alias"
data-bs-toggle="modal" data-bs-target="#addAliasModal">
- <i class="bi bi-plus-lg"></i><span class="label"> Add Alias</span>
+ <i class="bi bi-plus-lg"></i>
+ <span class="label"> Add Alias</span>
</button>
- <button class="btn btn-primary" title="Reload DNS (BIND)" aria-label="Reload DNS"
+
+ <!-- Separator -->
+ <div class="vr mx-2"></div>
+
+ <!-- Reload DNS -->
+ <button
+ class="btn btn-primary"
+ title="Reload DNS (BIND)"
+ aria-label="Reload DNS"
data-action="reloadDns">
- <i class="bi bi-arrow-repeat"></i><span class="label"> Reload DNS</span>
+ <i class="bi bi-arrow-repeat"></i>
+ <span class="label"> Reload DNS</span>
</button>
- <button class="btn btn-primary" title="Reload DHCP (Kea)" aria-label="Reload DHCP"
+
+ <!-- Reload DNS -->
+ <button class="btn btn-primary"
+ title="Reload DHCP (Kea)"
+ aria-label="Reload DHCP"
data-action="reloadDhcp">
- <i class="bi bi-arrow-repeat"></i><span class="label"> Reload DHCP</span>
+ <i class="bi bi-arrow-repeat"></i>
+ <span class="label"> Reload DHCP</span>
</button>
</div>
</div>
transform: translateY(-1px);
}
+/* ================================
+ Spin
+ ================================ */
+.spin {
+ display: inline-block;
+ animation: spin 1s linear infinite;
+}
+
+@keyframes spin {
+ from { transform: rotate(0deg); }
+ to { transform: rotate(360deg); }
+}
+
/* ================================
Responsive
================================ */
</div>
<!-- Bottoni -->
- <div class="col-12 col-md-auto d-flex gap-2 flex-wrap">
- <button class="btn btn-primary" title="Add Host" aria-label="Add Host"
+ <div class="col-12 col-md-auto d-flex align-items-center gap-2 flex-wrap">
+
+ <!-- Add host -->
+ <button
+ class="btn btn-primary"
+ title="Add Host"
+ aria-label="Add Host"
data-bs-toggle="modal" data-bs-target="#addHostModal">
- <i class="bi bi-plus-lg"></i><span class="label"> Add Host</span>
+ <i class="bi bi-plus-lg"></i>
+ <span class="label"> Add Host</span>
</button>
- <button class="btn btn-primary" title="Reload DNS (BIND)" aria-label="Reload DNS"
+
+ <!-- Separator -->
+ <div class="vr mx-2"></div>
+
+ <!-- Reload DNS -->
+ <button
+ class="btn btn-primary"
+ title="Reload DNS (BIND)"
+ aria-label="Reload DNS"
data-action="reloadDns">
- <i class="bi bi-arrow-repeat"></i><span class="label"> Reload DNS</span>
+ <i class="bi bi-arrow-repeat"></i>
+ <span class="label"> Reload DNS</span>
</button>
- <button class="btn btn-primary" title="Reload DHCP (Kea)" aria-label="Reload DHCP"
+
+ <!-- Reload DNS -->
+ <button class="btn btn-primary"
+ title="Reload DHCP (Kea)"
+ aria-label="Reload DHCP"
data-action="reloadDhcp">
- <i class="bi bi-arrow-repeat"></i><span class="label"> Reload DHCP</span>
+ <i class="bi bi-arrow-repeat"></i>
+ <span class="label"> Reload DHCP</span>
</button>
</div>
</div>
</div>
<!-- Bottoni -->
- <div class="col-12 col-md-auto d-flex gap-2 flex-wrap">
- <button class="btn btn-primary" title="Add Host" aria-label="Add Host"
+ <div class="col-12 col-md-auto d-flex align-items-center gap-2 flex-wrap">
+
+ <!-- Add host -->
+ <button
+ class="btn btn-primary"
+ title="Add Host"
+ aria-label="Add Host"
data-bs-toggle="modal" data-bs-target="#addHostModal">
- <i class="bi bi-plus-lg"></i><span class="label"> Add Host</span>
+ <i class="bi bi-plus-lg"></i>
+ <span class="label"> Add Host</span>
</button>
- <button class="btn btn-primary" title="Reload DNS (BIND)" aria-label="Reload DNS"
+
+ <!-- Separator -->
+ <div class="vr mx-2"></div>
+
+ <!-- Reload DNS -->
+ <button
+ class="btn btn-primary"
+ title="Reload DNS (BIND)"
+ aria-label="Reload DNS"
data-action="reloadDns">
- <i class="bi bi-arrow-repeat"></i><span class="label"> Reload DNS</span>
+ <i class="bi bi-arrow-repeat"></i>
+ <span class="label"> Reload DNS</span>
</button>
- <button class="btn btn-primary" title="Reload DHCP (Kea)" aria-label="Reload DHCP"
+
+ <!-- Reload DNS -->
+ <button class="btn btn-primary"
+ title="Reload DHCP (Kea)"
+ aria-label="Reload DHCP"
data-action="reloadDhcp">
- <i class="bi bi-arrow-repeat"></i><span class="label"> Reload DHCP</span>
+ <i class="bi bi-arrow-repeat"></i>
+ <span class="label"> Reload DHCP</span>
</button>
</div>
</div>
// Import common js
-import { loadModals, showToast, sortTable, initSortableTable, resetSorting, filterTable, clearSearch } from './common.js';
+import { loadModals, showToast, sortTable, initSortableTable, resetSorting, filterTable, clearSearch, handleReload } from './common.js';
// Import services
import { serviceReloadDNS, serviceReloadDHCP, serviceGetAliases, serviceGetAlias, serviceCreateAlias, serviceUpdateAlias, serviceDeleteAlias } from './services.js';
// handled by bootstrap modal show event
},
// Reload DNS
- reloadDns: async () => {
- try {
- const result = await serviceReloadDNS();
- const msg = (typeof result === 'object' && result?.message)
- ? result.message
- : 'DNS reload successfully';
- showToast(msg, true);
- } catch (err) {
- showToast(err?.message || "Error reloading DNS", false);
- }
+ reloadDns: async (e, el) => {
+ await handleReload(
+ el,
+ serviceReloadDNS,
+ "DNS reload successfully",
+ "Error reloading DNS",
+ "Reloading DNS..."
+ );
},
// Reload DHCP
- reloadDhcp: async () => {
- try {
- const result = await serviceReloadDHCP();
- const msg = (typeof result === 'object' && result?.message)
- ? result.message
- : 'DHCP reload successfully';
- showToast(msg, true);
- } catch (err) {
- showToast(err?.message || "Error reloading DHCP", false);
- }
+ reloadDhcp: async (e, el) => {
+ await handleReload(
+ el,
+ serviceReloadDHCP,
+ "DHCP reload successfully",
+ "Error reloading DHCP",
+ "Reloading DHCP..."
+ );
},
};
modal.show();
});
}
+
+/**
+ * Generic handler for reload actions with UI feedback.
+ * Disables the button, shows a spinner, executes the async service,
+ * then restores the button and shows a toast message.
+ *
+ * @param {HTMLElement} button
+ * @param {Function} serviceFn
+ * @param {string} defaultSuccessMsg
+ * @param {string} defaultErrorMsg
+ * @param {string} [workingText]
+ */
+/**
+ * Generic handler for reload actions with UI feedback.
+ */
+export async function handleReload(
+ button,
+ serviceFn,
+ defaultSuccessMsg,
+ defaultErrorMsg,
+ workingText = "Working...",
+ keepDisabled = false
+) {
+
+ if (!button || button.disabled) return;
+
+ button.disabled = true;
+
+ const originalHTML = button.innerHTML;
+
+ // spinner + testo
+ button.innerHTML = `
+ <i class="bi bi-arrow-repeat spin"></i>
+ <span>${workingText}</span>
+ `;
+
+ try {
+ const result = await serviceFn();
+
+ const msg =
+ (result && typeof result === "object" && result.message)
+ ? result.message
+ : defaultSuccessMsg;
+
+ showToast(msg, true);
+
+ } catch (err) {
+ showToast(err?.message || defaultErrorMsg, false);
+
+ } finally {
+ if (!keepDisabled) {
+ button.disabled = false;
+ button.innerHTML = originalHTML;
+ }
+ }
+}
// Import common js
-import { loadModals, isValidIPv4, isValidIPv6, isValidMAC, showToast, sortTable, initSortableTable, resetSorting, filterTable, clearSearch } from './common.js';
+import { loadModals, isValidIPv4, isValidIPv6, isValidMAC, showToast, sortTable, initSortableTable, resetSorting, filterTable, clearSearch, handleReload } from './common.js';
// Import services
import { serviceReloadDNS, serviceReloadDHCP, serviceGetDHCPLeases, serviceGetDHCPLease, serviceDeleteDHCPLease, serviceGetDevices, serviceGetHost, serviceCreateHost, serviceUpdateHost, serviceDeleteHost } from './services.js';
// handled by bootstrap modal show event
},
// Reload DNS
- reloadDns: async () => {
- try {
- const result = await serviceReloadDNS();
- const msg = (typeof result === 'object' && result?.message)
- ? result.message
- : 'DNS reload successfully';
- showToast(msg, true);
- } catch (err) {
- showToast(err?.message || "Error reloading DNS", false);
- }
+ reloadDns: async (e, el) => {
+ await handleReload(
+ el,
+ serviceReloadDNS,
+ "DNS reload successfully",
+ "Error reloading DNS",
+ "Reloading DNS..."
+ );
},
// Reload DHCP
- reloadDhcp: async () => {
- try {
- const result = await serviceReloadDHCP();
- const msg = (typeof result === 'object' && result?.message)
- ? result.message
- : 'DHCP reload successfully';
- showToast(msg, true);
- } catch (err) {
- showToast(err?.message || "Error reloading DHCP", false);
- }
+ reloadDhcp: async (e, el) => {
+ await handleReload(
+ el,
+ serviceReloadDHCP,
+ "DHCP reload successfully",
+ "Error reloading DHCP",
+ "Reloading DHCP..."
+ );
},
};
// Import common js
-import { loadModals, isValidIPv4, isValidIPv6, isValidMAC, showToast, sortTable, initSortableTable, resetSorting, filterTable, clearSearch } from './common.js';
+import { loadModals, isValidIPv4, isValidIPv6, isValidMAC, showToast, sortTable, initSortableTable, resetSorting, filterTable, clearSearch, handleReload } from './common.js';
// Import services
import { serviceReloadDNS, serviceReloadDHCP, serviceGetHosts, serviceGetHost, serviceCreateHost, serviceUpdateHost, serviceDeleteHost } from './services.js';
// handled by bootstrap modal show event
},
// Reload DNS
- reloadDns: async () => {
- try {
- const result = await serviceReloadDNS();
- const msg = (typeof result === 'object' && result?.message)
- ? result.message
- : 'DNS reload successfully';
- showToast(msg, true);
- } catch (err) {
- showToast(err?.message || "Error reloading DNS", false);
- }
+ reloadDns: async (e, el) => {
+ await handleReload(
+ el,
+ serviceReloadDNS,
+ "DNS reload successfully",
+ "Error reloading DNS",
+ "Reloading DNS..."
+ );
},
// Reload DHCP
- reloadDhcp: async () => {
- try {
- const result = await serviceReloadDHCP();
- const msg = (typeof result === 'object' && result?.message)
- ? result.message
- : 'DHCP reload successfully';
- showToast(msg, true);
- } catch (err) {
- showToast(err?.message || "Error reloading DHCP", false);
- }
+ reloadDhcp: async (e, el) => {
+ await handleReload(
+ el,
+ serviceReloadDHCP,
+ "DHCP reload successfully",
+ "Error reloading DHCP",
+ "Reloading DHCP..."
+ );
},
};
// Import common js
-import { loadModals, isValidIPv4, isValidIPv6, isValidMAC, showToast, sortTable, initSortableTable, resetSorting, filterTable, clearSearch } from './common.js';
+import { loadModals, isValidIPv4, isValidIPv6, isValidMAC, showToast, sortTable, initSortableTable, resetSorting, filterTable, clearSearch, handleReload } from './common.js';
// Import services
import { serviceReloadDNS, serviceReloadDHCP, serviceGetDHCPLeases, serviceDeleteDHCPLease, serviceGetDHCPLease, serviceCreateHost} from './services.js';
// handled by bootstrap modal show event
},
// Reload DNS
- reloadDns: async () => {
- try {
- const result = await serviceReloadDNS();
- const msg = (typeof result === 'object' && result?.message)
- ? result.message
- : 'DNS reload successfully';
- showToast(msg, true);
- } catch (err) {
- showToast(err?.message || "Error reloading DNS", false);
- }
+ reloadDns: async (e, el) => {
+ await handleReload(
+ el,
+ serviceReloadDNS,
+ "DNS reload successfully",
+ "Error reloading DNS",
+ "Reloading DNS..."
+ );
},
// Reload DHCP
- reloadDhcp: async () => {
- try {
- const result = await serviceReloadDHCP();
- const msg = (typeof result === 'object' && result?.message)
- ? result.message
- : 'DHCP reload successfully';
- showToast(msg, true);
- } catch (err) {
- showToast(err?.message || "Error reloading DHCP", false);
- }
+ reloadDhcp: async (e, el) => {
+ await handleReload(
+ el,
+ serviceReloadDHCP,
+ "DHCP reload successfully",
+ "Error reloading DHCP",
+ "Reloading DHCP..."
+ );
},
};
// Import common js
-import { loadModals, showToast, clearSearch, showConfirmModal } from './common.js';
+import { loadModals, showToast, clearSearch, showConfirmModal, handleReload } from './common.js';
// Import services
import { serviceGetConfigs, serviceGetConfig, serviceUpdateConfig, serviceResetConfig, serviceRestartApp, serviceIsAlive } from './services.js';
});
}
-// -----------------------------
-// Overlay
-// -----------------------------
-function showRestartOverlay() {
- document.getElementById("restartOverlay")
- ?.classList.remove("d-none");
-}
-
// -----------------------------
// Polling to check if the system restarted properly
// -----------------------------
-function startReconnectPolling() {
+function startReconnectPolling(button, originalHtmlButton) {
- // overlay (opzionale ma consigliato)
- showRestartOverlay();
+ if (!button) return;
+
+ const maxAttempts = 30; // 1 minuto
+ let attempts = 0;
const interval = setInterval(async () => {
+ attempts++;
+ if (attempts > maxAttempts) {
+ clearInterval(interval);
+ showToast("Server did not come back online", false);
+ button.innerHTML = originalHtmlButton;
+ button.disabled = false;
+ return;
+ }
+
try {
// prova endpoint leggero
const isUp = await serviceIsAlive();
setTimeout(() => location.reload(), 500);
- const btn = document.getElementById("restartBtn");
- btn?.removeAttribute("disabled");
}
} catch (err) {
console.log("Waiting for server...");
// -----------------------------
// Restart application
// -----------------------------
-async function restartApp() {
-
+async function restartApp(button) {
const confirmed = await showConfirmModal("Restart the application?");
if (!confirmed) return;
- const btn = document.getElementById("restartBtn");
- btn?.setAttribute("disabled", "true");
-
- try {
- await serviceRestartApp();
- showToast("Application is restarting...", true);
+ const originalHtmlButton = button.innerHTML;
- // wait restart
- startReconnectPolling();
+ const ok = await handleReload(
+ button,
+ serviceRestartApp,
+ "Application is restarting...",
+ "Error restarting application",
+ "Restarting...",
+ true
+ );
- } catch (err) {
- console.error(err?.message || "Error restarting application");
- showToast(err?.message || "Error restarting application", false);
- btn?.removeAttribute("disabled");
+ if (ok !== false) {
+ startReconnectPolling(button, originalHtmlButton);
}
}
edit: () => {
// handled by bootstrap modal show event
},
+ // Reload DHCP
+ restartApp: async (e, el) => {
+ await restartApp(el)
+ },
}
// -----------------------------
?.addEventListener("click", expandAllGroups);
document.getElementById("collapseAllBtn")
?.addEventListener("click", collapseAllGroups);
- document.getElementById("restartBtn")
- ?.addEventListener("click", restartApp);
}
// -----------------------------
</div>
<!-- Bottoni -->
- <div class="col-12 col-md-auto d-flex gap-2 flex-wrap">
- <button class="btn btn-primary" title="Reload DNS (BIND)" aria-label="Reload DNS"
+ <div class="col-12 col-md-auto d-flex align-items-center gap-2 flex-wrap">
+
+ <!-- Add host -->
+ <button
+ class="btn btn-primary"
+ title="Add Host"
+ aria-label="Add Host"
+ data-bs-toggle="modal" data-bs-target="#addHostModal">
+ <i class="bi bi-plus-lg"></i>
+ <span class="label"> Add Host</span>
+ </button>
+
+ <!-- Separator -->
+ <div class="vr mx-2"></div>
+
+ <!-- Reload DNS -->
+ <button
+ class="btn btn-primary"
+ title="Reload DNS (BIND)"
+ aria-label="Reload DNS"
data-action="reloadDns">
- <i class="bi bi-arrow-repeat"></i><span class="label"> Reload DNS</span>
+ <i class="bi bi-arrow-repeat"></i>
+ <span class="label"> Reload DNS</span>
</button>
- <button class="btn btn-primary" title="Reload DHCP (Kea)" aria-label="Reload DHCP"
+
+ <!-- Reload DNS -->
+ <button class="btn btn-primary"
+ title="Reload DHCP (Kea)"
+ aria-label="Reload DHCP"
data-action="reloadDhcp">
- <i class="bi bi-arrow-repeat"></i><span class="label"> Reload DHCP</span>
+ <i class="bi bi-arrow-repeat"></i>
+ <span class="label"> Reload DHCP</span>
</button>
</div>
</div>
<div class="vr mx-2"></div>
<!-- Restart -->
- <button id="restartBtn"
- class="btn btn-outline-danger d-flex align-items-center gap-2"
+ <button class="btn btn-outline-danger d-flex align-items-center gap-2"
title="Restart system"
- aria-label="Restart system">
+ aria-label="Restart system"
+ data-action="restartApp">
<i class="bi bi-arrow-repeat"></i>
<span>Restart</span>
</button>