def js_common(request: Request):
return FileResponse(os.path.join(settings.FRONTEND_DIR, "js/common.js"))
+# JS API
+def js_api(request: Request):
+ return FileResponse(os.path.join(settings.FRONTEND_DIR, "js/api.js"))
+
# JS Services
def js_services(request: Request):
return FileResponse(os.path.join(settings.FRONTEND_DIR, "js/services.js"))
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"])
+ app.add_api_route("/js/api.js", js_api, methods=["GET"])
app.add_api_route("/js/services.js", js_services, methods=["GET"])
app.add_api_route("/favicon.ico", favicon, methods=["GET"])
// Import common js
import { loadModals, showToast, sortTable, initSortableTable, resetSorting } from './common.js';
import { reloadDNS, reloadDHCP } from './services.js';
+import { apiMap, fetchData } from './api.js';
// -----------------------------
// State variables
// -----------------------------
+let allAliases = [];
+let viewAliases = [];
let editingAliasId = null;
const sortState = { sortDirection: {}, lastSort: null };
// -----------------------------
// Load all aliases into the table
// -----------------------------
-async function loadAliases() {
- let aliases = [];
+async function loadAliases(refresh = true) {
const loader = document.getElementById("loader");
const container = document.getElementById("devices-container");
const dataTable = document.getElementById("dataTable");
// hide table during loading to avoid flickering and show loader
dataTable.classList.add("d-none");
- try {
- // Show loader
- loader.style.display = "block";
-
- // Fetch data
- const res = await fetch(`/api/aliases`, {
- headers: { Accept: 'application/json' },
- });
-
- // 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 JSON
- let data;
+ if(refresh) {
try {
- data = await res.json();
- aliases = Array.isArray(data) ? data : (Array.isArray(data?.data) ? data.data : []);
+ // Show loader
+ loader.style.display = "block";
- } catch {
- throw new Error('Invalid JSON payload');
- }
+ // Fetch devices
+ allAliases = await fetchData(apiMap.aliases);
+ viewAliases = [...allAliases];
- // Check JSON errors
- if (!res.ok) {
- const serverMsg = data?.detail?.message?.trim();
- const base = `Error loading aliases`;
- const err = new Error(serverMsg ? `${base}: ${serverMsg}` : base);
- err.status = res.status;
- throw err;
+ } catch (err) {
+ console.error(err?.message || "Error loading aliases");
+ showToast(err?.message || "Error loading aliase", false);
+ allAliases = [];
+ // hide loader and show table
+ loader.style.display = "none";
+ dataTable.classList.remove("d-none");
}
-
- } catch (err) {
- console.error(err?.message || "Error loading aliases");
- showToast(err?.message || "Error loading aliase", false);
- aliases = [];
- // hide loader and show table
- loader.style.display = "none";
- dataTable.classList.remove("d-none");
}
// DOM Reference
tbody.innerHTML = "";
// if no aliases, show an empty row
- if (!aliases.length) {
+ if (!allAliases.length) {
const trEmpty = document.createElement("tr");
const tdEmpty = document.createElement("td");
tdEmpty.colSpan = 7;
// fragment per performance
const frag = document.createDocumentFragment();
- aliases.forEach(h => {
+ allAliases.forEach(h => {
const id = Number(h.id);
const tr = document.createElement("tr");
// -----------------------------
async function clearSearch() {
const input = document.getElementById("searchInput");
- input.value = "";
- input.blur();
- await loadAliases();
+ if (input) {
+ input.value = "";
+ input.blur();
+ }
+ viewAliases = [...allAliases];
+ await loadAliases(false);
}
// -----------------------------
// Escape management when focus is in the input
input.addEventListener("keydown", (e) => {
if (e.key === "Escape") {
- e.preventDefault(); // evita side-effect (es. chiusure di modali del browser)
- e.stopPropagation(); // evita che arrivi al listener globale
+ e.preventDefault(); // evita side-effect (es. chiusure di modali del browser)
+ e.stopPropagation(); // evita che arrivi al listener globale
resetSorting(sortState);
- clearSearch(); // svuota input e ricarica tabella (come definito nella tua funzione)
+ clearSearch(); // svuota input e ricarica tabella (come definito nella tua funzione)
filterAliases(''); // ripristina tabella
}
});
--- /dev/null
+// -----------------------------
+// API Endpoints
+// -----------------------------
+export const apiMap = {
+ hosts: {
+ url: "/api/hosts",
+ name: "Hosts"
+ },
+ aliases: {
+ url: "/api/aliases",
+ name: "Aliases"
+ },
+ leases: {
+ url: "/api/dhcp/leases",
+ name: "Leases"
+ },
+ devices: {
+ url: "/api/devices",
+ name: "Devices"
+ }
+};
+
+// -----------------------------
+// Fetch Data functions
+// -----------------------------
+export async function fetchData(api) {
+ let items = [];
+
+ // Fetch data
+ const res = await fetch(api.url, {
+ headers: { Accept: 'application/json' },
+ });
+
+ // 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 JSON
+ let data;
+ try {
+ data = await res.json();
+ items = Array.isArray(data) ? data : (Array.isArray(data?.data) ? data.data : []);
+ } catch {
+ throw new Error('Invalid JSON payload');
+ }
+
+ // Check JSON errors
+ if (!res.ok) {
+ const serverMsg = data?.detail?.message?.trim();
+ const base = `Error loading ${api.name}`;
+ const err = new Error(serverMsg ? `${base}: ${serverMsg}` : base);
+ err.status = res.status;
+ throw err;
+ }
+ return items;
+}
\ No newline at end of file
// Import common js
import { loadModals, isValidIPv4, isValidIPv6, isValidMAC, showToast, sortTable, initSortableTable, resetSorting } from './common.js';
import { reloadDNS, reloadDHCP } from './services.js';
+import { apiMap, fetchData } from './api.js';
// -----------------------------
// State variables
// -----------------------------
+let allDevices = [];
+let viewDevices = [];
let editingHostId = null;
const sortState = { sortDirection: {}, lastSort: null };
// -----------------------------
// Load all devices into the table
// -----------------------------
-async function loadDevices() {
- let devices = [];
+async function loadDevices(refresh = true) {
const loader = document.getElementById("loader");
const container = document.getElementById("devices-container");
const dataTable = document.getElementById("dataTable");
// hide table during loading to avoid flickering and show loader
dataTable.classList.add("d-none");
- try {
- // Show loader
- loader.style.display = "block";
-
- // Fetch data
- const res = await fetch(`/api/devices`, {
- headers: { Accept: 'application/json' },
- });
-
- // 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 JSON
- let data;
+ if(refresh) {
try {
- data = await res.json();
- devices = Array.isArray(data) ? data : (Array.isArray(data?.data) ? data.data : []);
+ // Show loader
+ loader.style.display = "block";
- } catch {
- throw new Error('Invalid JSON payload');
- }
+ // Fetch devices
+ allDevices = await fetchData(apiMap.devices);
+ viewDevices = [...allDevices];
- // Check JSON errors
- if (!res.ok) {
- const serverMsg = data?.detail?.message?.trim();
- const base = `Error loading devices`;
- const err = new Error(serverMsg ? `${base}: ${serverMsg}` : base);
- err.status = res.status;
- throw err;
+ } catch (err) {
+ console.error(err?.message || "Error loading devices");
+ showToast(err?.message || "Error loading devices", false);
+ allDevices = [];
+ // hide loader and show table
+ loader.style.display = "none";
+ dataTable.classList.remove("d-none");
}
-
- } catch (err) {
- console.error(err?.message || "Error loading devices");
- showToast(err?.message || "Error loading devices", false);
- devices = [];
- // hide loader and show table
- loader.style.display = "none";
- dataTable.classList.remove("d-none");
}
// DOM Reference
tbody.innerHTML = "";
// if no devices, show an empty row
- if (!devices.length) {
+ if (!allDevices.length) {
const trEmpty = document.createElement("tr");
const tdEmpty = document.createElement("td");
tdEmpty.colSpan = 7;
// fragment per performance
const frag = document.createDocumentFragment();
- devices.forEach(d => {
+ allDevices.forEach(d => {
//const mixedId = d.id;
//const id = mixedId.slice(2);
// -----------------------------
async function clearSearch() {
const input = document.getElementById("searchInput");
- input.value = "";
- input.blur();
- await loadDevices();
+ if (input) {
+ input.value = "";
+ input.blur();
+ }
+ viewDevices = [...allDevices];
+ await loadDevices(false);
}
// -----------------------------
// Import common js
import { loadModals, isValidIPv4, isValidIPv6, isValidMAC, showToast, sortTable, initSortableTable, resetSorting } from './common.js';
import { reloadDNS, reloadDHCP } from './services.js';
+import { apiMap, fetchData } from './api.js';
// -----------------------------
// State variables
// -----------------------------
+let allHosts = [];
+let viewHosts = [];
let editingHostId = null;
const sortState = { sortDirection: {}, lastSort: null };
// -----------------------------
// Load all hosts into the table
// -----------------------------
-async function loadHosts() {
- let hosts = [];
+async function loadHosts(refresh = true) {
const loader = document.getElementById("loader");
const container = document.getElementById("devices-container");
const dataTable = document.getElementById("dataTable");
// hide table during loading to avoid flickering and show loader
dataTable.classList.add("d-none");
- try {
- // Show loader
- loader.style.display = "block";
-
- // Fetch data
- const res = await fetch(`/api/hosts`, {
- headers: { Accept: 'application/json' },
- });
-
- // 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 JSON
- let data;
+ if(refresh) {
try {
- data = await res.json();
- hosts = Array.isArray(data) ? data : (Array.isArray(data?.data) ? data.data : []);
+ // Show loader
+ loader.style.display = "block";
- } catch {
- throw new Error('Invalid JSON payload');
- }
+ // Fetch hosts
+ allHosts = await fetchData(apiMap.hosts);
+ viewHosts = [...allHosts];
- // Check JSON errors
- if (!res.ok) {
- const serverMsg = data?.detail?.message?.trim();
- const base = `Error loading hosts`;
- const err = new Error(serverMsg ? `${base}: ${serverMsg}` : base);
- err.status = res.status;
- throw err;
+ } catch (err) {
+ console.error(err?.message || "Error loading hosts");
+ showToast(err?.message || "Error loading hosts", false);
+ allHosts = [];
+ // hide loader and show table
+ loader.style.display = "none";
+ dataTable.classList.remove("d-none");
}
-
- } catch (err) {
- console.error(err?.message || "Error loading hosts");
- showToast(err?.message || "Error loading hosts", false);
- hosts = [];
- // hide loader and show table
- loader.style.display = "none";
- dataTable.classList.remove("d-none");
}
// DOM Reference
tbody.innerHTML = "";
// if no hosts, show an empty row
- if (!hosts.length) {
+ if (!allHosts.length) {
const trEmpty = document.createElement("tr");
const tdEmpty = document.createElement("td");
tdEmpty.colSpan = 7;
// fragment per performance
const frag = document.createDocumentFragment();
- hosts.forEach(h => {
+ allHosts.forEach(h => {
const id = Number(h.id);
const tr = document.createElement("tr");
// -----------------------------
async function clearSearch() {
const input = document.getElementById("searchInput");
- input.value = "";
- input.blur();
- await loadHosts();
+ if (input) {
+ input.value = "";
+ input.blur();
+ }
+ viewHosts = [...allHosts];
+ await loadHosts(false);
}
// -----------------------------
// Escape management when focus is in the input
input.addEventListener("keydown", (e) => {
if (e.key === "Escape") {
- e.preventDefault(); // evita side-effect (es. chiusure di modali del browser)
- e.stopPropagation(); // evita che arrivi al listener globale
+ e.preventDefault(); // evita side-effect (es. chiusure di modali del browser)
+ e.stopPropagation(); // evita che arrivi al listener globale
resetSorting(sortState);
- clearSearch(); // svuota input e ricarica tabella (come definito nella tua funzione)
- filterHosts(''); // ripristina tabella
+ clearSearch(); // svuota input e ricarica tabella (come definito nella tua funzione)
+ filterHosts(''); // ripristina tabella
}
});
}
// Import common js
import { loadModals, isValidIPv4, isValidIPv6, isValidMAC, showToast, sortTable, initSortableTable, resetSorting } from './common.js';
import { reloadDNS, reloadDHCP } from './services.js';
+import { apiMap, fetchData } from './api.js';
// -----------------------------
// State variables
// -----------------------------
+let allLeases = [];
+let viewLeases = [];
const sortState = { sortDirection: {}, lastSort: null };
// -----------------------------
// Load all leases into the table
// -----------------------------
-async function loadLeases() {
- let leases = [];
+async function loadLeases(refresh = true) {
const loader = document.getElementById("loader");
const container = document.getElementById("devices-container");
const dataTable = document.getElementById("dataTable");
// hide table during loading to avoid flickering and show loader
dataTable.classList.add("d-none");
- try {
- // Show loader
- loader.style.display = "block";
-
- // Fetch data
- const res = await fetch(`/api/dhcp/leases`, {
- headers: { Accept: 'application/json' },
- });
-
- // 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 JSON
- let data;
+ if(refresh) {
try {
- data = await res.json();
- leases = Array.isArray(data) ? data : (Array.isArray(data?.data) ? data.data : []);
+ // Show loader
+ loader.style.display = "block";
- } catch {
- throw new Error('Invalid JSON payload');
- }
+ // Fetch leases
+ allLeases = await fetchData(apiMap.leases);
+ viewLeases = [...allLeases];
- // Check JSON errors
- if (!res.ok) {
- const serverMsg = data?.detail?.message?.trim();
- const base = `Error loading leases`;
- const err = new Error(serverMsg ? `${base}: ${serverMsg}` : base);
- err.status = res.status;
- throw err;
+ } catch (err) {
+ console.error(err?.message || "Error loading leases");
+ showToast(err?.message || "Error loading leases", false);
+ allLeases = [];
+ // hide loader and show table
+ loader.style.display = "none";
+ dataTable.classList.remove("d-none");
}
-
- } catch (err) {
- console.error(err?.message || "Error loading leases");
- showToast(err?.message || "Error loading leases", false);
- leases = [];
- // hide loader and show table
- loader.style.display = "none";
- dataTable.classList.remove("d-none");
}
// DOM Reference
tbody.innerHTML = "";
// if no leases, show an empty row
- if (!leases.length) {
+ if (!allLeases.length) {
const trEmpty = document.createElement("tr");
const tdEmpty = document.createElement("td");
tdEmpty.colSpan = 7;
// fragment per performance
const frag = document.createDocumentFragment();
- leases.forEach(l => {
+ allLeases.forEach(l => {
const id = Number(l.id);
const tr = document.createElement("tr");
// -----------------------------
async function clearSearch() {
const input = document.getElementById("searchInput");
- input.value = "";
- input.blur();
- await loadLeases();
+ if (input) {
+ input.value = "";
+ input.blur();
+ }
+ viewLeases = [...allLeases];
+ await loadLeases(false);
}
// -----------------------------
// Escape management when focus is in the input
input.addEventListener("keydown", (e) => {
if (e.key === "Escape") {
- e.preventDefault(); // evita side-effect (es. chiusure di modali del browser)
- e.stopPropagation(); // evita che arrivi al listener globale
+ e.preventDefault(); // evita side-effect (es. chiusure di modali del browser)
+ e.stopPropagation(); // evita che arrivi al listener globale
resetSorting(sortState);
- clearSearch(); // svuota input e ricarica tabella (come definito nella tua funzione)
- filterLeases(''); // ripristina tabella
+ clearSearch(); // svuota input e ricarica tabella (come definito nella tua funzione)
+ filterLeases(''); // ripristina tabella
}
});
}
// Button event delegation (Enter, Space)
const isEnter = e.key === 'Enter';
- const isSpace = e.key === ' ' || e.key === 'Spacebar';
+ const isSpace = e.key === ' ';
if (!isEnter && !isSpace) return;
const el = e.target.closest('[data-action]');