import ipaddress
import logging
import os
+import re
+import sqlite3
# Import local modules
from backend.db.db import get_db, register_init
# Import Settings
# Import Log
from log.log import get_logger
+# Regex for MAC check
+MAC_RE = re.compile(r"^([0-9A-Fa-f]{2}([:\-])){5}([0-9A-Fa-f]{2})$")
+
+# -----------------------------
+# Check Data Input
+# -----------------------------
+def validate_data(data: dict) -> dict:
+ if "name" not in data:
+ raise ValueError("Missing required field: name")
+
+ name = str(data["name"]).strip()
+ if not name:
+ raise ValueError("Field 'name' cannot be empty")
+
+ ipv4 = data.get("ipv4")
+ if ipv4:
+ try:
+ ipaddress.IPv4Address(ipv4)
+ except ValueError:
+ raise ValueError(f"Invalid IPv4 address: {ipv4}")
+
+ ipv6 = data.get("ipv6")
+ if ipv6:
+ try:
+ ipaddress.IPv6Address(ipv6)
+ except ValueError:
+ raise ValueError(f"Invalid IPv6 address: {ipv6}")
+
+ mac = data.get("mac")
+ if mac and not MAC_RE.match(mac):
+ raise ValueError(f"Invalid MAC address: {mac}")
+
+ note = data.get("note")
+
+ # Normalizzazione boolean per DB (0/1)
+ ssl_enabled = int(bool(data.get("ssl_enabled", 0)))
+
+ return {
+ "name": name,
+ "ipv4": ipv4,
+ "ipv6": ipv6,
+ "mac": mac,
+ "note": note,
+ "ssl_enabled": ssl_enabled,
+ }
+
+# -----------------------------
+# Sorting hosts
+# -----------------------------
+def ipv4_sort_key(h: dict):
+ v = (h.get('ipv4') or '').strip()
+ if not v:
+ # no ip at the end
+ return (1, 0)
+ try:
+ return (0, int(ipaddress.IPv4Address(v)))
+ except ValueError:
+ return (0, float('inf'))
+
# -----------------------------
# SELECT ALL HOSTS
# -----------------------------
conn = get_db()
cur = conn.execute("SELECT * FROM hosts")
rows = [dict(r) for r in cur.fetchall()]
- # Sort by IPv4
- rows.sort(key=lambda h: ipaddress.IPv4Address(h['ipv4']))
+ rows.sort(key=ipv4_sort_key)
return rows
# -----------------------------
return dict(row) if row else None
# -----------------------------
-# INSERT HOST
+# ADD HOST
# -----------------------------
def add_host(data: dict):
+
+ # Validate input
+ cleaned = validate_data(data)
+
conn = get_db()
- cur = conn.execute(
- "INSERT INTO hosts (name, ipv4, ipv6, mac, note, ssl_enabled) VALUES (?, ?, ?, ?, ?, ?)",
- (
- data["name"],
- data.get("ipv4"),
- data.get("ipv6"),
- data.get("mac"),
- data.get("note"),
- data.get("ssl_enabled", 0)
+ try:
+ cur = conn.execute(
+ "INSERT INTO hosts (name, ipv4, ipv6, mac, note, ssl_enabled) VALUES (?, ?, ?, ?, ?, ?)",
+ (
+ cleaned["name"],
+ cleaned["ipv4"],
+ cleaned["ipv6"],
+ cleaned["mac"],
+ cleaned["note"],
+ cleaned["ssl_enabled"],
+ )
)
- )
- conn.commit()
- last_id = cur.lastrowid
- return last_id
+ conn.commit()
+ return cur.lastrowid
+
+ except sqlite3.IntegrityError as e:
+ conn.rollback()
+ return -1
+
+ except Exception as e:
+ conn.rollback()
+ raise
# -----------------------------
# UPDATE HOST
# -----------------------------
-def update_host(host_id: int, data: dict):
+def update_host(host_id: int, data: dict) -> bool:
+
+ # Validate input
+ cleaned = validate_data(data)
+
conn = get_db()
- conn.execute(
- "UPDATE hosts SET name=?, ipv4=?, ipv6=?, mac=?, note=?, ssl_enabled=? WHERE id=?",
- (
- data["name"],
- data.get("ipv4"),
- data.get("ipv6"),
- data.get("mac"),
- data.get("note"),
- data.get("ssl_enabled", 0),
- host_id
+ try:
+ cur = conn.execute(
+ """
+ UPDATE hosts
+ SET name=?, ipv4=?, ipv6=?, mac=?, note=?, ssl_enabled=?
+ WHERE id=?
+ """,
+ (
+ cleaned["name"],
+ cleaned["ipv4"],
+ cleaned["ipv6"],
+ cleaned["mac"],
+ cleaned["note"],
+ cleaned["ssl_enabled"],
+ host_id,
+ )
)
- )
- conn.commit()
- return True
+ conn.commit()
+ return cur.rowcount > 0
+
+ except Exception:
+ conn.rollback()
+ raise
# -----------------------------
# DELETE HOST
# -----------------------------
-def delete_host(host_id: int):
+def delete_host(host_id: int) -> bool:
+
+ # Validate input
+ if host_id is None:
+ raise ValueError("host_id cannot be None")
+
conn = get_db()
- conn.execute("DELETE FROM hosts WHERE id = ?", (host_id,))
- conn.commit()
- return True
+ try:
+ cur = conn.execute(
+ "DELETE FROM hosts WHERE id = ?",
+ (host_id,)
+ )
+ conn.commit()
+
+ return cur.rowcount > 0
+
+ except Exception:
+ conn.rollback()
+ raise
# -----------------------------
# Initialize Hosts DB Table
# import standard modules
from contextlib import asynccontextmanager
-from fastapi import FastAPI, Request
+from fastapi import FastAPI, Request, HTTPException, status
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse, RedirectResponse, JSONResponse, Response
import logging
if path.startswith("/api"):
if not is_logged_in(request):
logger.error("API access denied - not logged in")
- return JSONResponse({"error": "Unauthorized"}, status_code=401)
+ raise HTTPException(
+ status_code=status.HTTP_401_UNAUTHORIZED,
+ detail={
+ "error": "Unauthorized"
+ },
+ )
response = await call_next(request)
# Sliding expiration
apply_session(response, username=None, token=token)
# backend/routes/backup.py
# import standard modules
-from fastapi import APIRouter, Request, Response
+from fastapi import APIRouter, Request, Response, HTTPException, status
from fastapi.responses import FileResponse, JSONResponse, RedirectResponse
import asyncio
import json
for h in hosts:
f.write(json.dumps(h, ensure_ascii=False) + "\n")
- except Exception as err:
- error = True
- message = str(err).strip()
-
- if error:
- code = "BACKUP_ERROR"
- # default del messaggio se vuoto o None
- if not message:
- message = "BACKUP error"
- status = "failure"
- #http_status = 500
- else:
- code = "BACKUP_OK"
- message = "BACKUP executed successfully"
- status = "success"
- #http_status = 200
+ took_ms = (time.monotonic_ns() - start_ns) / 1_000_000
+ return JSONResponse(
+ status_code=status.HTTP_200_OK,
+ content={
+ "code": "BACKUP_OK",
+ "status": "success",
+ "message": "BACKUP executed successfully",
+ "took_ms": took_ms,
+ },
+ )
- took_ms = (time.monotonic_ns() - start_ns) / 1_000_000
-
- payload = {
- "code": code,
- "status": status,
- "message": message,
- "details": {
- "took_ms": took_ms
- }
- }
- return JSONResponse(content=payload)
- #return JSONResponse(content=payload, status_code=http_status)
+ except Exception as err:
+ took_ms = (time.monotonic_ns() - start_ns) / 1_000_000
+ logger = get_logger("hosts")
+ logger.exception("Error executing backup: %s", str(e).strip())
+ raise HTTPException(
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
+ detail={
+ "code": "BACKUP_ERROR",
+ "status": "failure",
+ "message": "Internal error executing backup",
+ "took_ms": took_ms,
+ },
+ )
# backend/routes/dhcp.py
# import standard modules
-from fastapi import APIRouter, Request, Response
+from fastapi import APIRouter, Request, Response, HTTPException, status
from fastapi.responses import FileResponse, JSONResponse, RedirectResponse
import asyncio
import csv
# RELOAD DHCP
took_ms = (time.monotonic_ns() - start_ns) / 1_000_000
- payload = {
- "code": "DHCP_RELOAD_OK",
- "status": "success",
- "message": "DHCP configuration reload successfully",
- "details": {"took_ms": took_ms}
- }
- return JSONResponse(content=payload, status_code=200)
+ return JSONResponse(
+ status_code=status.HTTP_200_OK,
+ content={
+ "code": "DHCP_RELOAD_OK",
+ "status": "success",
+ "message": "DHCP configuration reload successfully",
+ "took_ms": took_ms,
+ },
+ )
except Exception as err:
- get_logger("dhcp").exception("Error reloading DHCP: %s", str(err).strip())
+ logger = get_logger("dhcp")
+ logger.exception("Error reloading DHCP: %s", str(err).strip())
took_ms = (time.monotonic_ns() - start_ns) / 1_000_000
-
- payload = {
- "code": "DHCP_RELOAD_ERROR",
- "status": "failure",
- "message": "Error reloading DHCP",
- "details": {"took_ms": took_ms, "error": str(err).strip()}
- }
- return JSONResponse(content=payload, status_code=500)
+ raise HTTPException(
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
+ detail={
+ "code": "DHCP_RELOAD_ERROR",
+ "status": "failure",
+ "message": "Internal error reloading DHCP",
+ "took_ms": took_ms,
+ },
+ )
# ---------------------------------------------------------
# Get Leases
try:
path = Path(settings.DHCP4_LEASES_FILE)
if not path.exists():
- return JSONResponse(
- content={"code": "DHCP_LEASES_NOT_FOUND",
- "status": "failure",
- "message": f"DHCP leases file not found: {str(path)}",
- "details": {}},
- status_code=404
+ raise HTTPException(
+ status_code=status.HTTP_404_NOT_FOUND,
+ detail={
+ "code": "DHCP_LEASES_NOT_FOUND",
+ "status": "failure",
+ "message": "File not found: " + str(path),
+ },
)
def _to_int(v: str):
with path.open("r", encoding="utf-8", newline="") as f:
reader = csv.DictReader(f)
if not reader.fieldnames:
- return JSONResponse(content={"total": 0, "items": []}, status_code=200)
+ return JSONResponse(
+ status_code=status.HTTP_200_OK,
+ content={
+ "total": 0, "items": []
+ },
+ )
for raw in reader:
rec = { _norm(k): (v if v is not None else "") for k, v in raw.items() }
}
items.append(item)
- return JSONResponse(content={"total": len(items), "items": items}, status_code=200)
+ return JSONResponse(
+ status_code=status.HTTP_200_OK,
+ content={
+ "total": len(items),
+ "items": items
+ },
+ )
except Exception as err:
- get_logger("dhcp").exception("Error reading DHCP leases: %s", str(err).strip())
- payload = {
- "code": "DHCP_LEASES_ERROR",
- "status": "failure",
- "message": "Error reading DHCP leases",
- "details": {"error": str(err).strip()}
- }
- return JSONResponse(content=payload, status_code=500)
+ logger = get_logger("dhcp")
+ logger.exception("Error reading DHCP leases: %s", str(err).strip())
+ raise HTTPException(
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
+ detail={
+ "code": "DHCP_LEASES_ERROR",
+ "status": "failure",
+ "message": "Internal error reading DHCP leases",
+ },
+ )
# backend/routes/dns.py
# import standard modules
-from fastapi import APIRouter, Request, Response
+from fastapi import APIRouter, Request, Response, HTTPException, status
from fastapi.responses import FileResponse, JSONResponse, RedirectResponse
import asyncio
import json
# RELOAD DNS
took_ms = (time.monotonic_ns() - start_ns) / 1_000_000
- payload = {
- "code": "DNS_RELOAD_OK",
- "status": "success",
- "message": "DNS configuration reload successfully",
- "details": {"took_ms": took_ms}
- }
- return JSONResponse(content=payload, status_code=200)
+ return JSONResponse(
+ status_code=status.HTTP_200_OK,
+ content={
+ "code": "DNS_RELOAD_OK",
+ "status": "success",
+ "message": "DNS configuration reload successfully",
+ "took_ms": took_ms,
+ },
+ )
except Exception as err:
- get_logger("dns").exception("Error reloading DNS: %s", str(err).strip())
+ logger = get_logger("dns")
+ logger.exception("Error reloading DNS: %s", str(err).strip())
took_ms = (time.monotonic_ns() - start_ns) / 1_000_000
- payload = {
- "code": "DNS_RELOAD_ERROR",
- "status": "failure",
- "message": "Error reloading DNS",
- "details": {"took_ms": took_ms, "error": str(err).strip()}
- }
- return JSONResponse(content=payload, status_code=500)
+ raise HTTPException(
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
+ detail={
+ "code": "DNS_RELOAD_ERROR",
+ "status": "failure",
+ "message": "Internal error reloading DNS",
+ "took_ms": took_ms,
+ },
+ )
# backend/routes/hosts.py
# import standard modules
-from fastapi import APIRouter, Request, Response
-from fastapi.responses import FileResponse, RedirectResponse
-import os
+from fastapi import APIRouter, Request, Response, HTTPException, status
+from fastapi.responses import FileResponse, JSONResponse, RedirectResponse
import ipaddress
+import time
+import os
# Import local modules
from backend.db.hosts import (
get_hosts,
)
# Import Settings
from settings.settings import settings
+# Import Logging
+from log.log import setup_logging, get_logger
# Create Router
router = APIRouter()
return FileResponse(os.path.join(settings.FRONTEND_DIR, "js/hosts.js"))
# ---------------------------------------------------------
-# API ENDPOINTS
+# Get Hosts
# ---------------------------------------------------------
-@router.get("/api/hosts")
+@router.get("/api/hosts", status_code=status.HTTP_200_OK, responses={
+ 200: {"description": "Hosts found"},
+ 500: {"description": "Internal server error"},
+})
def api_get_hosts(request: Request):
- return get_hosts()
+ try:
+ hosts = get_hosts()
+ return hosts or []
-@router.post("/api/hosts")
-def api_add_host(request: Request, data: dict):
- name = data.get("name", "").strip()
- ipv4 = data.get("ipv4")
- ipv6 = data.get("ipv6")
- if not name:
- return {"error": "Name is required"}
- if ipv4:
- try:
- ipaddress.IPv4Address(ipv4)
- except:
- return {"error": "Invalid IPv4 format"}
- if ipv6:
- try:
- ipaddress.IPv6Address(ipv6)
- except:
- return {"error": "Invalid IPv6 format"}
- return {"id": add_host(data)}
-
-@router.get("/api/hosts/{host_id}")
+ except Exception as e:
+ logger = get_logger("hosts")
+ logger.exception("Error getting list host %s", str(e).strip())
+ raise HTTPException(
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
+ detail={
+ "code": "HOSTS_GET_ERROR",
+ "status": "failure",
+ "message": "Internal error getting host",
+ },
+ )
+
+# ---------------------------------------------------------
+# Get Host
+# ---------------------------------------------------------
+@router.get("/api/hosts/{host_id}", status_code=status.HTTP_200_OK, responses={
+ 200: {"description": "Host found"},
+ 404: {"description": "Host not found"},
+ 500: {"description": "Internal server error"},
+})
def api_get_host(request: Request, host_id: int):
- return get_host(host_id) or {}
-@router.put("/api/hosts/{host_id}")
+ try:
+ host = get_host(host_id)
+ if not host: # None or empty dict
+ raise HTTPException(
+ status_code=status.HTTP_404_NOT_FOUND,
+ detail={
+ "code": "HOST_NOT_FOUND",
+ "status": "failure",
+ "message": "Host not found",
+ "host_id": host_id,
+ },
+ )
+ return host
+
+ except Exception as e:
+ logger = get_logger("hosts")
+ logger.exception("Error adding host %s: %s", host_id, str(e).strip())
+ took_ms = (time.monotonic_ns() - start_ns) / 1_000_000
+ raise HTTPException(
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
+ detail={
+ "code": "HOST_GET_ERROR",
+ "status": "failure",
+ "message": "Internal error getting host",
+ "took_ms": took_ms,
+ },
+ )
+
+# ---------------------------------------------------------
+# Add Hosts
+# ---------------------------------------------------------
+@router.post("/api/hosts", status_code=status.HTTP_200_OK, responses={
+ 200: {"description": "Host added"},
+ 409: {"description": "Host already present"},
+ 500: {"description": "Internal server error"},
+})
+def api_add_host(request: Request, data: dict):
+
+ # Inizializzazioni
+ start_ns = time.monotonic_ns()
+
+ try:
+ host_id = add_host(data)
+ if(host_id > 0):
+ took_ms = (time.monotonic_ns() - start_ns) / 1_000_000
+ return JSONResponse(
+ status_code=status.HTTP_200_OK,
+ content={
+ "code": "HOST_ADDED",
+ "status": "success",
+ "message": "Host added successfully",
+ "host_id": host_id,
+ "took_ms": took_ms,
+ },
+ )
+
+ # Already present
+ took_ms = (time.monotonic_ns() - start_ns) / 1_000_000
+ raise HTTPException(
+ status_code=status.HTTP_409_CONFLICT,
+ detail={
+ "code": "HOST_ALREADY_PRESENT",
+ "status": "failure",
+ "message": "Host already present",
+ "took_ms": took_ms,
+ },
+ )
+
+ except HTTPException as httpe:
+ raise httpe
+
+ except Exception as e:
+ logger = get_logger("hosts")
+ logger.exception("Error adding host: %s", str(e).strip())
+ took_ms = (time.monotonic_ns() - start_ns) / 1_000_000
+ raise HTTPException(
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
+ detail={
+ "code": "HOST_ADD_ERROR",
+ "status": "failure",
+ "message": "Internal error adding host",
+ "took_ms": took_ms,
+ },
+ )
+
+# ---------------------------------------------------------
+# Update Host
+# ---------------------------------------------------------
+@router.put("/api/hosts/{host_id}", status_code=status.HTTP_200_OK, responses={
+ 200: {"description": "Host updated"},
+ 404: {"description": "Host not found"},
+ 500: {"description": "Internal server error"},
+})
def api_update_host(request: Request, data: dict, host_id: int):
- name = data.get("name", "").strip()
- ipv4 = data.get("ipv4")
- ipv6 = data.get("ipv6")
- if not name:
- return {"error": "Name is required"}
- if ipv4:
- try:
- ipaddress.IPv4Address(ipv4)
- except:
- return {"error": "Invalid IPv4 format"}
- if ipv6:
- try:
- ipaddress.IPv6Address(ipv6)
- except:
- return {"error": "Invalid IPv6 format"}
- update_host(host_id, data)
- return {"status": "ok"}
-
-@router.delete("/api/hosts/{host_id}")
+
+ # Inizializzazioni
+ start_ns = time.monotonic_ns()
+
+ try:
+ updated = update_host(host_id, data)
+ if updated:
+ took_ms = (time.monotonic_ns() - start_ns) / 1_000_000
+ return JSONResponse(
+ status_code=status.HTTP_200_OK,
+ content={
+ "code": "HOST_UPDATED",
+ "status": "success",
+ "message": "Host updated successfully",
+ "host_id": host_id,
+ "took_ms": took_ms,
+ },
+ )
+
+ # Not Found
+ took_ms = (time.monotonic_ns() - start_ns) / 1_000_000
+ raise HTTPException(
+ status_code=status.HTTP_404_NOT_FOUND,
+ detail={
+ "code": "HOST_NOT_FOUND",
+ "status": "failure",
+ "message": "Host not found",
+ "host_id": host_id,
+ "took_ms": took_ms,
+ },
+ )
+
+ except Exception as e:
+ logger = get_logger("hosts")
+ logger.exception("Error updating host %s: %s", host_id, str(e).strip())
+ took_ms = (time.monotonic_ns() - start_ns) / 1_000_000
+ raise HTTPException(
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
+ detail={
+ "code": "HOST_UPDATE_ERROR",
+ "status": "failure",
+ "message": "Internal error updating host",
+ "host_id": host_id,
+ "took_ms": took_ms,
+ },
+ )
+
+# ---------------------------------------------------------
+# Delete
+# ---------------------------------------------------------
+@router.delete("/api/hosts/{host_id}", status_code=status.HTTP_200_OK, responses={
+ 200: {"description": "Host deleted"},
+ 404: {"description": "Host not found"},
+ 500: {"description": "Internal server error"},
+})
def api_delete_host(request: Request, host_id: int):
- delete_host(host_id)
- return {"status": "ok"}
+
+ # Inizializzazioni
+ start_ns = time.monotonic_ns()
+
+ try:
+ deleted = delete_host(host_id)
+ if deleted:
+ took_ms = (time.monotonic_ns() - start_ns) / 1_000_000
+ return JSONResponse(
+ status_code=status.HTTP_200_OK,
+ content={
+ "code": "HOST_DELETED",
+ "status": "success",
+ "message": "Host deleted successfully",
+ "details": {"took_ms": took_ms, "host_id": host_id,},
+ },
+ )
+
+ # Not Found
+ took_ms = (time.monotonic_ns() - start_ns) / 1_000_000
+ raise HTTPException(
+ status_code=status.HTTP_404_NOT_FOUND,
+ detail={
+ "code": "HOST_NOT_FOUND",
+ "status": "failure",
+ "message": "Host not found",
+ "host_id": host_id,
+ "took_ms": took_ms,
+ },
+ )
+
+ except Exception as e:
+ logger = get_logger("hosts")
+ logger.exception("Error deleting host %s: %s", host_id, str(e).strip())
+ took_ms = (time.monotonic_ns() - start_ns) / 1_000_000
+ raise HTTPException(
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
+ detail={
+ "code": "HOST_DELETE_ERROR",
+ "status": "failure",
+ "message": "Internal error deleting host",
+ "host_id": host_id,
+ "took_ms": took_ms,
+ },
+ )
# backend/routes/login.py
# import standard modules
-from fastapi import APIRouter, Request, Response, HTTPException
+from fastapi import APIRouter, Request, Response, HTTPException, status
from fastapi.responses import FileResponse, RedirectResponse
import os
import time
attempts = [t for t in attempts if now - t < settings.LOGIN_WINDOW_SECONDS]
if len(attempts) >= settings.LOGIN_MAX_ATTEMPTS:
- raise HTTPException(status_code=429, detail="Too many login attempts")
+ raise HTTPException(
+ status_code=status.HTTP_409_CONFLICT,
+ detail={
+ "error": "Too many login attempts"
+ },
+ )
# registra nuovo tentativo
attempts.append(now)
async function loadHosts() {
let hosts = [];
try {
- const res = await fetch("/api/hosts");
- if (!res.ok) {
- const err = new Error(`Error loading hosts: ${res.status} ${res.statusText}`);
+ // 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 content-type to avoid parsing errors
- const contentType = res.headers.get("content-type") || "";
- if (!contentType.includes("application/json")) {
- throw new Error("Answer is not JSON");
+
+ // Check JSON
+ let data;
+ try {
+ data = await res.json();
+ hosts = Array.isArray(data) ? data : (Array.isArray(data?.data) ? data.data : []);
+
+ } catch {
+ throw new Error('Invalid JSON payload');
}
- // parse data
- const data = await res.json();
- hosts = Array.isArray(data) ? data : (Array.isArray(data?.data) ? data.data : []);
- // debug log
- //console.log("Hosts:", hosts);
+ // 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");
const tbody = document.querySelector("#hosts-table tbody");
if (!tbody) {
console.warn('Element "#hosts-table tbody" not found in DOM.');
- return;
+ return;
}
// Svuota la tabella
if (!hosts.length) {
const trEmpty = document.createElement("tr");
const tdEmpty = document.createElement("td");
- tdEmpty.colSpan = 6;
+ tdEmpty.colSpan = 7;
tdEmpty.textContent = "No hosts available.";
tdEmpty.style.textAlign = "center";
trEmpty.appendChild(tdEmpty);
// Clear form first
clearAddHostForm();
- try {
- const res = await fetch(`/api/hosts/${id}`);
- if (!res.ok) throw new Error(`Fetch failed for host ${id}: ${res.status}`);
-
- const host = await res.json();
+ // Fetch host
+ const res = await fetch(`/api/hosts/${id}`, {
+ headers: { Accept: 'application/json' },
+ });
- // Store the ID of the host being edited
- editingHostId = id;
+ // Check content-type to avoid parsing errors
+ const contentType = res.headers.get("content-type") || "";
+ if (!contentType.includes("application/json")) {
+ const err = new Error(`Fetch failed for host ${id}: ${res.statusText}`);
+ err.status = res.status;
+ throw err;
+ }
- // Pre-fill the form fields
- document.getElementById("hostName").value = host.name ?? "";
- document.getElementById("hostIPv4").value = host.ipv4 ?? "";
- document.getElementById("hostIPv6").value = host.ipv6 ?? "";
- document.getElementById("hostMAC").value = host.mac ?? "";
- document.getElementById("hostNote").value = host.note ?? "";
- document.getElementById("hostSSL").checked = !!host.ssl_enabled;
+ // Check JSON
+ let host;
+ try {
+ host = await res.json();
+ } catch {
+ throw new Error(`Fetch failed for host ${id}: Invalid JSON payload`);
+ }
- } catch(err) {
+ // Check JSON errors
+ if (!res.ok) {
+ const serverMsg = host?.detail?.message?.trim();
+ const base = `Fetch failed for host ${id}`;
+ const err = new Error(serverMsg ? `${base}: ${serverMsg}` : base);
+ err.status = res.status;
throw err;
}
+
+ // Store the ID of the host being edited
+ editingHostId = id;
+
+ // Pre-fill the form fields
+ document.getElementById("hostName").value = host.name ?? "";
+ document.getElementById("hostIPv4").value = host.ipv4 ?? "";
+ document.getElementById("hostIPv6").value = host.ipv6 ?? "";
+ document.getElementById("hostMAC").value = host.mac ?? "";
+ document.getElementById("hostNote").value = host.note ?? "";
+ document.getElementById("hostSSL").checked = !!host.ssl_enabled;
}
// -----------------------------
return false;
}
- try {
- if (editingHostId !== null) {
- // UPDATE EXISTING HOST
- const res = await fetch(`/api/hosts/${editingHostId}`, {
- method: "PUT",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify(hostData)
- });
- if (res.ok) {
- showToast("Host updated successfully");
- } else {
- throw new Error(`Update failed: ${res.status}`);
- }
- } else {
- // CREATE NEW HOST
- const res = await fetch("/api/hosts", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify(hostData)
- });
- if (res.ok) {
- showToast("Host added successfully", true);
- } else {
- throw new Error(`Create failed: ${res.status}`);
- }
+ if (editingHostId !== null) {
+ // UPDATE EXISTING HOST
+ const res = await fetch(`/api/hosts/${editingHostId}`, {
+ method: 'PUT',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(hostData)
+ });
+
+ // Success without JSON
+ if (res.status === 204) {
+ showToast('Host updated successfully', true);
+ return true;
}
- } catch (err) {
- console.error("Error saving host:", err);
- throw err;
- }
- return true;
-}
-// -----------------------------
-// DELETE HOST
-// -----------------------------
-async function deleteHost(id) {
- try {
- const res = await fetch(`/api/hosts/${id}`, { method: "DELETE" });
+ // 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();
+ } catch {
+ throw new Error('Invalid JSON payload');
+ }
+
+ // Check JSON errors
if (!res.ok) {
- throw new Error("Delete failed");
+ const serverMsg = data?.detail?.message?.trim();
+ const base = `Error updating host`;
+ const err = new Error(serverMsg ? `${base}: ${serverMsg}` : base);
+ err.status = res.status;
+ throw err;
}
- showToast("Host removed successfully");
- } catch (err) {
- console.error("Error deleting host:", err);
- throw err;
+ // Success
+ showToast(data?.message || 'Host updated successfully', true);
+ return true;
+
+ } else {
+ // CREATE NEW HOST
+ const res = await fetch(`/api/hosts`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(hostData)
+ });
+
+ // Success without JSON
+ if (res.status === 204) {
+ showToast('Host created successfully', true);
+ 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 JSON
+ let data;
+ try {
+ data = await res.json();
+ } catch {
+ throw new Error('Invalid JSON payload');
+ }
+
+ // Check JSON errors
+ if (!res.ok) {
+ const serverMsg = data?.detail?.message?.trim();
+ const base = `Error adding host`;
+ const err = new Error(serverMsg ? `${base}: ${serverMsg}` : base);
+ err.status = res.status;
+ throw err;
+ }
+
+ // Success
+ showToast(data?.message || 'Host created successfully', true);
+ return true
}
- await loadHosts();
}
// -----------------------------
async function handleAddHostSubmit(e) {
// Prevent default form submission
e.preventDefault();
+
// Retrieve form data
const hostData = {
name: document.getElementById('hostName').value.trim(),
// close modal and reload hosts
closeAddHostModal();
await loadHosts();
+ return true
}
+
} catch (err) {
- console.error("Error saving host:", err);
- showToast("Error saving host", false);
+ console.error(err?.message || "Error saving host");
+ showToast(err?.message || "Error saving host", false);
}
+
return false;
}
async function handleDeleteHost(e, el) {
// Prevent default action
e.preventDefault();
+
// Get host ID
const id = Number(el.dataset.hostId);
if (!Number.isFinite(id)) {
// Execute delete
try {
- deleteHost(id);
+ // Fetch data
+ const res = await fetch(`/api/hosts/${id}`, {
+ method: 'DELETE',
+ 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();
+ } catch {
+ throw new Error('Invalid JSON payload');
+ }
+
+ // Check JSON errors
+ if (!res.ok) {
+ const serverMsg = data?.detail?.message?.trim();
+ const base = `Error deleting host`;
+ const err = new Error(serverMsg ? `${base}: ${serverMsg}` : base);
+ err.status = res.status;
+ throw err;
+ }
+
+ // Success
+ showToast(data?.message || 'Host deleted successfully', true);
+
+ // Reload hosts
+ await loadHosts();
+ return true;
+
} catch (err) {
- console.error("Error deleting host:", err);
- showToast("Error deleting host", false);
+ console.error(err?.message || "Error deleting host");
+ showToast(err?.message || "Error deleting host", false);
}
+
+ return false;
}
// -----------------------------
// -----------------------------
async function handleReloadDNS() {
try {
- const res = await fetch(`/api/dns/reload`, { method: "POST" });
- if (!res.ok) {
- const err = new Error(`Error reloading DNS: ${res.status} ${res.statusText}`);
- err.status = res.status;
- throw err;
+ // Fetch data
+ const res = await fetch(`/api/dns/reload`, {
+ method: 'POST',
+ headers: { 'Accept': 'application/json' },
+ });
+
+ // Success without JSON
+ if (res.status === 204) {
+ showToast('DNS reload successfully', true);
+ return true;
}
- // check content-type to avoid parsing errors
+
+ // Check content-type to avoid parsing errors
const contentType = res.headers.get("content-type") || "";
if (!contentType.includes("application/json")) {
- throw new Error("Answer is not JSON");
+ const err = new Error(`${res.status}: ${res.statusText}`);
+ err.status = res.status;
+ throw err;
+ }
+
+ // Check JSON
+ let data;
+ try {
+ data = await res.json();
+ } catch {
+ throw new Error('Invalid JSON payload');
}
- const data = await res.json();
- if(data.code !== "DNS_RELOAD_OK"){
- const err = new Error(`Error reloading DNS: ${data.message}`);
- err.status = data.code;
+ // 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;
}
- //console.info("DNS Reload:", data);
- showToast(data.message, true);
+ // 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 false;
}
// -----------------------------
// -----------------------------
async function handleReloadDHCP() {
try {
- const res = await fetch(`/api/dhcp/reload`, { method: "POST" });
- if (!res.ok) {
- const err = new Error(`Error reloading DHCP: ${res.status} ${res.statusText}`);
- err.status = res.status;
- throw err;
+ // Fetch data
+ const res = await fetch(`/api/dhcp/reload`, {
+ method: 'POST',
+ headers: { 'Accept': 'application/json' },
+ });
+
+ // Success without JSON
+ if (res.status === 204) {
+ showToast('DHCP reload successfully', true);
+ return true;
}
- // check content-type to avoid parsing errors
+
+ // Check content-type to avoid parsing errors
const contentType = res.headers.get("content-type") || "";
if (!contentType.includes("application/json")) {
- throw new Error("Answer is not JSON");
+ const err = new Error(`${res.status}: ${res.statusText}`);
+ 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');
}
- const data = await res.json();
- if(data.code !== "DHCP_RELOAD_OK"){
- const err = new Error(`Error reloading DHCP: ${data.message}`);
- err.status = data.code;
+ // 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;
}
- //console.info("DHCP Reload:", data);
- showToast(data.message, true);
+ // Success
+ showToast(data?.message || 'DHCP reload successfully', true);
+ return true;
} catch (err) {
console.error(err?.message || "Error reloading DHCP");
showToast(err?.message || "Error reloading DHCP", false);
}
+
+ return false;
}
// -----------------------------
try {
await editHost(id);
} catch (err) {
- console.error("Error loading host for edit:", err);
- showToast("Error loading host for edit", false);
+ showToast(err?.message || "Error loading host for edit", false);
// Close modal
const closeOnShown = () => {
closeAddHostModal(lastTriggerEl);