# ------------------------------------------------------------------------------
# Homepage
def home(request: Request):
- return FileResponse(os.path.join(settings.FRONTEND_DIR, "hosts.html"))
+ return FileResponse(os.path.join(settings.FRONTEND_DIR, "index.html"))
+
+# Homepage JS
+def js_home(request: Request):
+ return FileResponse(os.path.join(settings.FRONTEND_DIR, "js/index.js"))
# CSS variables
def css_variables(request: Request):
# Route per file del frontend
app.add_api_route("/", home, methods=["GET"])
+ app.add_api_route("/home", home, methods=["GET"])
+ app.add_api_route("/index.html", home, methods=["GET"])
+ app.add_api_route("/js/index.js", js_home, methods=["GET"])
app.add_api_route("/css/variables.css", css_variables, methods=["GET"])
app.add_api_route("/css/layout.css", css_layout, methods=["GET"])
app.add_api_route("/js/common.js", js_common, methods=["GET"])
return {
"code": "BACKUP_OK" if not errors else "BACKUP_ERROR",
"status": "success" if not errors else "failure",
- "message": "BACKUP executed successfully" if not errors else "Some operations failed",
+ "message": "Backup executed successfully" if not errors else "Some operations failed",
"took_ms": took_ms,
"results": result,
}
return {
"code": "RESTORE_OK" if not errors else "RESTORE_ERROR",
"status": "success" if not errors else "failure",
- "message": "RESTORE executed successfully" if not errors else "Some operations failed",
+ "message": "Restore executed successfully" if not errors else "Some operations failed",
"took_ms": took_ms,
"results": result,
}
<!-- Menu -->
<div class="collapse navbar-collapse" id="menuNav">
<div class="navbar-nav ms-auto gap-2">
+ <a href="/home" id="homeBtn" class="btn btn-primary" aria-current="page">Dashboard</a>
<a href="/hosts" id="hostsBtn" class="btn btn-primary" aria-current="page">Hostname</a>
<a href="/aliases" id="aliasesBtn" class="btn btn-primary active" aria-current="page">Alias</a>
<button id="logoutBtn" class="btn btn-primary">Logout</button>
<div class="col-12 col-md-auto">
<h2 class="mb-0 d-flex align-items-center gap-2 lh-1">
<span class="title-icon">đź–§</span>
- <!--<i class="bi bi-hdd-network"></i>-->
<span class="section-title">Aliases List</span>
</h2>
</div>
<!-- Menu -->
<div class="collapse navbar-collapse" id="menuNav">
<div class="navbar-nav ms-auto gap-2">
+ <a href="/home" id="homeBtn" class="btn btn-primary" aria-current="page">Dashboard</a>
<a href="/hosts" id="hostsBtn" class="btn btn-primary active" aria-current="page">Hostname</a>
<a href="/aliases" id="aliasesBtn" class="btn btn-primary" aria-current="page">Alias</a>
<button id="logoutBtn" class="btn btn-primary">Logout</button>
<div class="col-12 col-md-auto">
<h2 class="mb-0 d-flex align-items-center gap-2 lh-1">
<span class="title-icon">đź–§</span>
- <!--<i class="bi bi-hdd-network"></i>-->
<span class="section-title">Host List</span>
</h2>
</div>
// Action Handlers
// -----------------------------
const actionHandlers = {
- async delete(e, el) { handleDeleteAlias(e, el); },
-
- // edit is handled by bootstrap modal show event
- edit(e, el) {
- // no-op o log
- },
-
- // Reload DNS
- async reloadDns(e, el) { reloadDNS(); },
-
- // Reload DHCP
- async reloadDhcp(e, el) { reloadDHCP(); },
+ // Delete alias
+ delete: (e, el) => {
+ handleDeleteAlias(e, el);
+ },
+ // Edit alias
+ edit: () => {
+ // handled by bootstrap modal show event
+ },
+ // Reload DNS
+ reloadDns: async () => {
+ try {
+ const result = await reloadDNS();
+ const msg = (typeof result === 'object' && result?.message)
+ ? result.message
+ : 'DNS reload successfully';
+ showToast(msg, true);
+ } catch (err) {
+ showToast(err?.message || "Error reloading DNS", false);
+ }
+ },
+ // Reload DHCP
+ reloadDhcp: async () => {
+ try {
+ const result = await reloadDHCP();
+ const msg = (typeof result === 'object' && result?.message)
+ ? result.message
+ : 'DHCP reload successfully';
+ showToast(msg, true);
+ } catch (err) {
+ showToast(err?.message || "Error reloading DHCP", false);
+ }
+ },
};
// -----------------------------
// Action Handlers
// -----------------------------
const actionHandlers = {
- async delete(e, el) { handleDeleteHost(e, el); },
-
- // edit is handled by bootstrap modal show event
- edit(e, el) {
- // no-op o log
- },
-
- // Reload DNS
- async reloadDns(e, el) { reloadDNS(); },
-
- // Reload DHCP
- async reloadDhcp(e, el) { reloadDHCP(); },
+ // Delete host
+ delete: (e, el) => {
+ handleDeleteHost(e, el);
+ },
+ // Edit host
+ edit: () => {
+ // handled by bootstrap modal show event
+ },
+ // Reload DNS
+ reloadDns: async () => {
+ try {
+ const result = await reloadDNS();
+ const msg = (typeof result === 'object' && result?.message)
+ ? result.message
+ : 'DNS reload successfully';
+ showToast(msg, true);
+ } catch (err) {
+ showToast(err?.message || "Error reloading DNS", false);
+ }
+ },
+ // Reload DHCP
+ reloadDhcp: async () => {
+ try {
+ const result = await reloadDHCP();
+ const msg = (typeof result === 'object' && result?.message)
+ ? result.message
+ : 'DHCP reload successfully';
+ showToast(msg, true);
+ } catch (err) {
+ showToast(err?.message || "Error reloading DHCP", false);
+ }
+ },
};
// -----------------------------
--- /dev/null
+// -------------------------------------------------------
+// IMPORT
+// -------------------------------------------------------
+import { showToast } from './common.js';
+import { apiCheck, reloadDNS, reloadDHCP, doBackup, doRestore } from './services.js';
+
+// -------------------------------------------------------
+// RESTORE MODAL OPEN/CLOSE
+// -------------------------------------------------------
+function openRestoreModal() {
+ const modal = document.getElementById('restoreModal');
+ const input = document.getElementById('restoreBackupId');
+ if (!modal) return;
+
+ modal.style.display = 'flex';
+ if (input) {
+ input.value = input.value?.trim() ?? '';
+ setTimeout(() => input.focus(), 50);
+ }
+}
+
+function closeRestoreModal() {
+ const modal = document.getElementById('restoreModal');
+ if (modal) modal.style.display = 'none';
+}
+
+// -------------------------------------------------------
+// Action Handlers
+// -------------------------------------------------------
+const actionHandlers = {
+ // Backup
+ startBackup: async (e, el) => {
+ const btn = el;
+
+ if (!btn) return;
+
+ const label = btn.querySelector('.label');
+ const originalLabel = label?.textContent ?? '';
+
+ btn.disabled = true;
+ label.textContent = ' Exporting…';
+
+ try {
+ const result = await doBackup();
+ const msg = (typeof result === 'object' && result?.message)
+ ? result.message
+ : 'Backup compleated successfully';
+ showToast(msg, true);
+ } catch (err) {
+ showToast(err?.message || "Error performing backup", false);
+ } finally {
+ label.textContent = originalLabel;
+ btn.disabled = false;
+ }
+ },
+ // Restore
+ startRestore: async (e, el) => {
+ const btn = el;
+ const input = document.getElementById('restoreBackupId');
+ const modal = document.getElementById('restoreModal');
+
+ if (!btn || !input) return;
+
+ const id = input.value.trim();
+ if (!id) {
+ showToast('Specify a Backup ID', false);
+ input.focus();
+ return;
+ }
+
+ const label = btn.querySelector('.label');
+ const originalLabel = label?.textContent ?? '';
+
+ btn.disabled = true;
+ label.textContent = ' Restoring…';
+
+ try {
+ const result = await doRestore();
+ const msg = (typeof result === 'object' && result?.message)
+ ? result.message
+ : 'Restore completed successfully';
+ showToast(msg, true);
+ // Close modal and reset input
+ if (modal) modal.style.display = 'none';
+ input.value = '';
+ } catch (err) {
+ showToast(err?.message || "Error performing restore", false);
+ } finally {
+ label.textContent = originalLabel;
+ btn.disabled = false;
+ }
+ },
+ openRestoreModal, // managed by boostrap
+ closeRestoreModal, // managed by boostrap
+ // Reload DNS
+ reloadDns: async () => {
+ try {
+ const result = await reloadDNS();
+ const msg = (typeof result === 'object' && result?.message)
+ ? result.message
+ : 'DNS reload successfully';
+ showToast(msg, true);
+ } catch (err) {
+ showToast(err?.message || "Error reloading DNS", false);
+ }
+ },
+ // Reload DHCP
+ reloadDhcp: async () => {
+ try {
+ const result = await reloadDHCP();
+ const msg = (typeof result === 'object' && result?.message)
+ ? result.message
+ : 'DHCP reload successfully';
+ showToast(msg, true);
+ } catch (err) {
+ showToast(err?.message || "Error reloading DHCP", false);
+ }
+ },
+ // Check API status
+ apiCheck: async () => {
+ const result = await apiCheck();
+ if(result) {
+ showToast('API status updated succesfully', true);
+ } else {
+ showToast('Error updating API status', false);
+ }
+ }
+};
+
+// -------------------------------------------------------
+// Global Click Delegation
+// -------------------------------------------------------
+document.addEventListener('click', async (e) => {
+ const el = e.target.closest('[data-action]');
+ if (!el) return;
+
+ const action = el.dataset.action;
+ const handler = actionHandlers[action];
+ if (!handler) return;
+
+ try {
+ await handler(e, el);
+ } catch (err) {
+ console.error(err?.message || 'Action error');
+ showToast(err?.message || 'Action error', false);
+ }
+});
+
+// -------------------------------------------------------
+// MODAL: ESC + BACKDROP CLOSE
+// -------------------------------------------------------
+document.addEventListener('keydown', (e) => {
+ if (e.key === 'Escape') closeRestoreModal();
+});
+
+const restoreModal = document.getElementById('restoreModal');
+if (restoreModal) {
+ restoreModal.addEventListener('click', (e) => {
+ if (e.target === restoreModal) closeRestoreModal();
+ });
+}
+
+// -------------------------------------------------------
+// KICKOFF
+// -------------------------------------------------------
+apiCheck();
}
// Login Success
- // Redirect to main page (hosts)
- location.assign('/hosts');
+ // Redirect to main page
+ location.assign('/home');
return;
} catch (err) {
-import { showToast } from './common.js';
+// -------------------------------------------------------
+// API Health Check
+// -------------------------------------------------------
+export async function apiCheck() {
+ const pill = document.getElementById('api-pill');
+ if (!pill) return;
+
+ try {
+ const r = await fetch('/api/health');
+ if (r.ok) {
+ pill.textContent = 'API OK';
+ pill.classList.remove('btn-outline-primary');
+ pill.classList.add('btn-primary');
+ return true;
+ } else {
+ pill.textContent = `API ${r.status}`;
+ return false;
+ }
+ } catch {
+ pill.textContent = 'API OFFLINE';
+ return false;
+ }
+}
// -----------------------------
// Reload DNS
// -----------------------------
export async function reloadDNS() {
+ let res;
try {
// Fetch data
- const res = await fetch(`/api/dns/reload`, {
+ res = await fetch('/api/dns/reload', {
method: 'POST',
headers: { 'Accept': 'application/json' },
});
+ } catch (err) {
+ const msg = 'Network error while reloading DNS' + (err?.message ? `: ${err.message}` : '');
+ throw new Error(msg, { cause: err });
+ }
- // Success without JSON
- if (res.status === 204) {
- showToast('DNS reload successfully', true);
- return true;
- }
+ // Success without JSON
+ if (res.status === 204) {
+ return true;
+ }
- // Check content-type to avoid parsing errors
- const contentType = res.headers.get("content-type") || "";
- if (!contentType.includes("application/json")) {
- const err = new Error(`${res.status}: ${res.statusText}`);
- err.status = res.status;
- throw err;
- }
+ // Check content-type to avoid parsing errors
+ const contentType = res.headers.get("content-type") || "";
+ if (!contentType.includes("application/json")) {
+ const err = new Error(`Error reloading DNS: ${res.status}: ${res.statusText || 'Unexpected response'}`);
+ err.status = res.status;
+ throw err;
+ }
- // Check JSON
- let data;
- try {
- data = await res.json();
- } catch {
- throw new Error('Invalid JSON payload');
- }
+ // Check JSON
+ let data = {};
+ try {
+ data = await res.json();
+ } catch {
+ throw new Error('Error reloading DNS: Invalid JSON payload');
+ }
- // Check JSON errors
- if (!res.ok) {
- const serverMsg = data?.detail?.message?.trim();
- const base = `Error reloading DNS`;
- const err = new Error(serverMsg ? `${base}: ${serverMsg}` : base);
- err.status = res.status;
- throw err;
- }
+ // Check JSON errors
+ if (!res.ok) {
+ const serverMsg =
+ data?.detail?.message?.trim()
+ || (typeof data?.detail === 'string' ? data.detail.trim() : '')
+ || data?.message?.trim()
+ || data?.error?.message?.trim()
+ || (typeof data?.error === 'string' ? data.error.trim() : '');
+ const err = new Error('Error reloading DNS' + (serverMsg ? `: ${serverMsg}` : ''));
+ err.status = res.status;
+ throw err;
+ }
+ if (res.ok && (data.status === 'success' || data.code === 'DNS_RELOAD_OK')) {
// Success
- showToast(data?.message || 'DNS reload successfully', true);
- return true;
-
- } catch (err) {
- console.error(err?.message || "Error reloading DNS");
- showToast(err?.message || "Error reloading DNS", false);
+ return data?.message ? { message: data.message } : true;
+ } else {
+ // Failed with JSON error message
+ return data?.message ? { message: data.message } : false;
}
-
- return false;
}
// -----------------------------
// Reload DHCP action
// -----------------------------
export async function reloadDHCP() {
+ let res;
try {
// Fetch data
- const res = await fetch(`/api/dhcp/reload`, {
+ res = await fetch(`/api/dhcp/reload`, {
method: 'POST',
headers: { 'Accept': 'application/json' },
});
+ } catch (err) {
+ const msg = 'Network error while reloading DHCP' + (err?.message ? `: ${err.message}` : '');
+ throw new Error(msg, { cause: err });
+ }
- // Success without JSON
- if (res.status === 204) {
- showToast('DHCP reload successfully', true);
- return true;
- }
+ // Success without JSON
+ if (res.status === 204) {
+ return true;
+ }
- // Check content-type to avoid parsing errors
- const contentType = res.headers.get("content-type") || "";
- if (!contentType.includes("application/json")) {
- const err = new Error(`${res.status}: ${res.statusText}`);
- err.status = res.status;
- throw err;
- }
+ // Check content-type to avoid parsing errors
+ const contentType = res.headers.get("content-type") || "";
+ if (!contentType.includes("application/json")) {
+ const err = new Error(`Error reloading DHCP: ${res.status}: ${res.statusText || 'Unexpected response'}`);
+ err.status = res.status;
+ throw err;
+ }
- // Check JSON
- let data;
- try {
- data = await res.json();
- } catch {
- throw new Error('Error reloading DHCP: Invalid JSON payload');
- }
+ // Check JSON
+ let data = {};
+ try {
+ data = await res.json();
+ } catch {
+ throw new Error('Error reloading DHCP: Invalid JSON payload');
+ }
- // Check JSON errors
- if (!res.ok) {
- const serverMsg = data?.detail?.message?.trim();
- const base = `Error reloadin DHCP`;
- const err = new Error(serverMsg ? `${base}: ${serverMsg}` : base);
- err.status = res.status;
- throw err;
- }
+ // Check JSON errors
+ if (!res.ok) {
+ const serverMsg =
+ data?.detail?.message?.trim()
+ || (typeof data?.detail === 'string' ? data.detail.trim() : '')
+ || data?.message?.trim()
+ || data?.error?.message?.trim()
+ || (typeof data?.error === 'string' ? data.error.trim() : '');
+ const err = new Error('Error reloading DHCP' + (serverMsg ? `: ${serverMsg}` : ''));
+ err.status = res.status;
+ throw err;
+ }
+
+ if (res.ok && (data.status === 'success' || data.code === 'DHCP_RELOAD_OK')) {
+ // Success
+ return data?.message ? { message: data.message } : true;
+ } else {
+ // Failed with JSON error message
+ return data?.message ? { message: data.message } : false;
+ }
+}
+// -------------------------------------------------------
+// Execute Backup
+// -------------------------------------------------------
+export async function doBackup() {
+ let res;
+
+ try {
+ // Fetch data
+ res = await fetch(`/api/backup`, {
+ method: 'POST',
+ headers: { 'Accept': 'application/json' },
+ });
+
+ } catch {
+ const msg = 'Network error while performing backup' + (err?.message ? `: ${err.message}` : '');
+ throw new Error(msg, { cause: err });
+ }
+
+ // Success without JSON
+ if (res.status === 204) {
+ return true;
+ }
+
+ // Check content-type to avoid parsing errors
+ const contentType = res.headers.get("content-type") || "";
+ if (!contentType.includes("application/json")) {
+ const err = new Error(`Error performing backup: ${res.status}: ${res.statusText || 'Unexpected response'}`);
+ err.status = res.status;
+ throw err;
+ }
+
+ // Check JSON
+ let data = {};
+ try {
+ data = await res.json();
+ } catch {
+ throw new Error('Error performing backup: Invalid JSON payload');
+ }
+
+ // Check JSON errors
+ if (!res.ok) {
+ const serverMsg =
+ data?.detail?.message?.trim()
+ || (typeof data?.detail === 'string' ? data.detail.trim() : '')
+ || data?.message?.trim()
+ || data?.error?.message?.trim()
+ || (typeof data?.error === 'string' ? data.error.trim() : '');
+ const err = new Error('Error performing backup' + (serverMsg ? `: ${serverMsg}` : ''));
+ err.status = res.status;
+ throw err;
+ }
+
+ if (res.ok && (data.status === 'success' || data.code === 'BACKUP_OK')) {
// Success
- showToast(data?.message || 'DHCP reload successfully', true);
+ return data?.message ? { message: data.message } : true;
+ } else {
+ // Failed with JSON error message
+ return data?.message ? { message: data.message } : false;
+ }
+}
+
+// -------------------------------------------------------
+// Execute Restore
+// -------------------------------------------------------
+export async function doRestore(id) {
+ let res;
+
+ try {
+ // Fetch data
+ res = await fetch(`/api/restore`, {
+ method: 'POST',
+ headers: { 'Accept': 'application/json' },
+ body: JSON.stringify({ backup_id: id })
+ });
+
+ } catch {
+ const msg = 'Network error while performing restore' + (err?.message ? `: ${err.message}` : '');
+ throw new Error(msg, { cause: err });
+ }
+
+ // Success without JSON
+ if (res.status === 204) {
return true;
+ }
- } catch (err) {
- console.error(err?.message || "Error reloading DHCP");
- showToast(err?.message || "Error reloading DHCP", false);
+ // Check content-type to avoid parsing errors
+ const contentType = res.headers.get("content-type") || "";
+ if (!contentType.includes("application/json")) {
+ const err = new Error(`Error performing restore: ${res.status}: ${res.statusText || 'Unexpected response'}`);
+ err.status = res.status;
+ throw err;
+ }
+
+ // Check JSON
+ let data = {};
+ try {
+ data = await res.json();
+ } catch {
+ throw new Error('Error performing restore: Invalid JSON payload');
+ }
+
+ // Check JSON errors
+ if (!res.ok) {
+ const serverMsg =
+ data?.detail?.message?.trim()
+ || (typeof data?.detail === 'string' ? data.detail.trim() : '')
+ || data?.message?.trim()
+ || data?.error?.message?.trim()
+ || (typeof data?.error === 'string' ? data.error.trim() : '');
+ const err = new Error('Error performing restore' + (serverMsg ? `: ${serverMsg}` : ''));
+ err.status = res.status;
+ throw err;
}
- return false;
+ if (res.ok && (data.status === 'success' || data.code === 'RESTORE_OK')) {
+ // Success
+ return data?.message ? { message: data.message } : true;
+ } else {
+ // Failed with JSON error message
+ return data?.message ? { message: data.message } : false;
+ }
}