From: Giorgio Ravera Date: Wed, 18 Feb 2026 18:28:57 +0000 (+0100) Subject: Added aliases management X-Git-Url: http://git.giorgioravera.it/?a=commitdiff_plain;h=2c1a5a93a04caa75f66a83082da1cc898280f1f7;p=network-manager.git Added aliases management --- diff --git a/backend/db/aliases.py b/backend/db/aliases.py new file mode 100644 index 0000000..0cbaf2a --- /dev/null +++ b/backend/db/aliases.py @@ -0,0 +1,169 @@ +# backend/db/aliases.py + +# Import standard modules +import ipaddress +import logging +import os +import re +import sqlite3 +# Import local modules +from backend.db.db import get_db, register_init +# Import Settings +from settings.settings import settings +# Import Log +from log.log import get_logger + +# ----------------------------- +# Check Data Input +# ----------------------------- +def validate_data(data: dict) -> dict: + # Check name + 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") + + # Check target + if "target" not in data: + raise ValueError("Missing required field: target") + target = str(data["target"]).strip() + if not target: + raise ValueError("Field 'target' cannot be empty") + + # Check note + note = data.get("note") + + # Boolean normalization for DB (0/1) + ssl_enabled = int(bool(data.get("ssl_enabled", 0))) + + return { + "name": name, + "target": target, + "note": note, + "ssl_enabled": ssl_enabled, + } + +# ----------------------------- +# SELECT ALL ALIASES +# ----------------------------- +def get_aliases(): + conn = get_db() + cur = conn.execute("SELECT * FROM aliases ORDER BY target") + rows = [dict(r) for r in cur.fetchall()] + return rows + +# ----------------------------- +# SELECT SINGLE ALIAS +# ----------------------------- +def get_alias(alias_id: int): + conn = get_db() + cur = conn.execute("SELECT * FROM aliases WHERE id = ?", (alias_id,)) + row = cur.fetchone() + return dict(row) if row else None + +# ----------------------------- +# ADD ALIAS +# ----------------------------- +def add_alias(data: dict): + + # Validate input + cleaned = validate_data(data) + + conn = get_db() + try: + cur = conn.execute( + "INSERT INTO aliases (name, target, note, ssl_enabled) VALUES (?, ?, ?, ?)", + ( + cleaned["name"], + cleaned["target"], + cleaned["note"], + cleaned["ssl_enabled"], + ) + ) + conn.commit() + return cur.lastrowid + + except sqlite3.IntegrityError as e: + conn.rollback() + return -1 + + except Exception as e: + conn.rollback() + raise + +# ----------------------------- +# UPDATE ALIAS +# ----------------------------- +def update_alias(alias_id: int, data: dict) -> bool: + + # Validate input + cleaned = validate_data(data) + + conn = get_db() + try: + cur = conn.execute( + """ + UPDATE aliases + SET name=?, target=?, note=?, ssl_enabled=? + WHERE id=? + """, + ( + cleaned["name"], + cleaned["target"], + cleaned["note"], + cleaned["ssl_enabled"], + alias_id, + ) + ) + conn.commit() + return cur.rowcount > 0 + + except Exception: + conn.rollback() + raise + +# ----------------------------- +# DELETE ALIAS +# ----------------------------- +def delete_alias(alias_id: int) -> bool: + + # Validate input + if alias_id is None: + raise ValueError("alias_id cannot be None") + + conn = get_db() + try: + cur = conn.execute( + "DELETE FROM aliases WHERE id = ?", + (alias_id,) + ) + conn.commit() + + return cur.rowcount > 0 + + except Exception: + conn.rollback() + raise + +# ----------------------------- +# Initialize Aliases DB Table +# ----------------------------- +@register_init +def init_db_alias_table(cur): + logger = get_logger(__name__) + + # ALIASES TABLE + cur.execute(""" + CREATE TABLE aliases ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL UNIQUE, + target TEXT NOT NULL, + note TEXT, + ssl_enabled INTEGER NOT NULL DEFAULT 0 + ); + """) + cur.execute("CREATE INDEX idx_aliases_name ON aliases(name);") + + logger.info("ALIASES DB: Database initialized successfully for %s", settings.DOMAIN) + logger.info("ALIASES DB: Public IP: %s", settings.PUBLIC_IP) diff --git a/backend/db/hosts.py b/backend/db/hosts.py index 3287f27..7b0a639 100644 --- a/backend/db/hosts.py +++ b/backend/db/hosts.py @@ -20,13 +20,14 @@ 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: + # Check name 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") + # Check IPv4 ipv4 = data.get("ipv4") if ipv4: try: @@ -34,6 +35,7 @@ def validate_data(data: dict) -> dict: except ValueError: raise ValueError(f"Invalid IPv4 address: {ipv4}") + # Check IPv6 ipv6 = data.get("ipv6") if ipv6: try: @@ -45,6 +47,7 @@ def validate_data(data: dict) -> dict: if mac and not MAC_RE.match(mac): raise ValueError(f"Invalid MAC address: {mac}") + # Check note note = data.get("note") # Normalizzazione boolean per DB (0/1) @@ -210,19 +213,6 @@ def init_db_hosts_table(cur): """) cur.execute("CREATE INDEX idx_hosts_name ON hosts(name);") - # ALIASES TABLE - cur.execute(""" - CREATE TABLE aliases ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - host_id INTEGER NOT NULL, - alias TEXT NOT NULL, - note TEXT, - ssl_enabled INTEGER NOT NULL DEFAULT 0, - FOREIGN KEY (host_id) REFERENCES hosts(id) - ); - """) - cur.execute("CREATE INDEX idx_aliases_host ON aliases(host_id);") - # TXT TABLE cur.execute(""" CREATE TABLE txt_records ( diff --git a/backend/main.py b/backend/main.py index 4984ad0..1e9ba84 100644 --- a/backend/main.py +++ b/backend/main.py @@ -14,6 +14,7 @@ from backend.routes.backup import router as backup_router from backend.routes.health import router as health_router from backend.routes.login import router as login_router from backend.routes.hosts import router as hosts_router +from backend.routes.aliases import router as aliases_router from backend.routes.dns import router as dns_router from backend.routes.dhcp import router as dhcp_router # Import Security @@ -108,6 +109,7 @@ app.include_router(backup_router) app.include_router(health_router) app.include_router(login_router) app.include_router(hosts_router) +app.include_router(aliases_router) app.include_router(dns_router) app.include_router(dhcp_router) diff --git a/backend/routes/aliases.py b/backend/routes/aliases.py new file mode 100644 index 0000000..41b3ee8 --- /dev/null +++ b/backend/routes/aliases.py @@ -0,0 +1,266 @@ +# backend/routes/aliases.py + +# import standard modules +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.aliases import ( + get_aliases, + get_alias, + add_alias, + update_alias, + delete_alias +) +# Import Settings +from settings.settings import settings +# Import Logging +from log.log import setup_logging, get_logger + +# Create Router +router = APIRouter() + +# --------------------------------------------------------- +# FRONTEND PATHS (absolute paths inside Docker) +# --------------------------------------------------------- +# Aliass page +@router.get("/aliases") +def aliases(request: Request): + return FileResponse(os.path.join(settings.FRONTEND_DIR, "aliases.html")) + +# Serve aliases.js +@router.get("/js/aliases.js") +def css_aliases(): + return FileResponse(os.path.join(settings.FRONTEND_DIR, "js/aliases.js")) + +# --------------------------------------------------------- +# Get Aliass +# --------------------------------------------------------- +@router.get("/api/aliases", status_code=status.HTTP_200_OK, responses={ + 200: {"description": "Aliass found"}, + 500: {"description": "Internal server error"}, +}) +def api_get_aliases(request: Request): + try: + aliases = get_aliases() + return aliases or [] + + except Exception as e: + logger = get_logger("aliases") + logger.exception("Error getting list alias %s", str(e).strip()) + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail={ + "code": "ALIASES_GET_ERROR", + "status": "failure", + "message": "Internal error getting alias", + }, + ) + +# --------------------------------------------------------- +# Get Alias +# --------------------------------------------------------- +@router.get("/api/aliases/{alias_id}", status_code=status.HTTP_200_OK, responses={ + 200: {"description": "Alias found"}, + 404: {"description": "Alias not found"}, + 500: {"description": "Internal server error"}, +}) +def api_get_alias(request: Request, alias_id: int): + + try: + alias = get_alias(alias_id) + if not alias: # None or empty dict + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail={ + "code": "ALIAS_NOT_FOUND", + "status": "failure", + "message": "Alias not found", + "alias_id": alias_id, + }, + ) + return alias + + except Exception as e: + logger = get_logger("aliases") + logger.exception("Error adding alias %s: %s", alias_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": "ALIAS_GET_ERROR", + "status": "failure", + "message": "Internal error getting alias", + "took_ms": took_ms, + }, + ) + +# --------------------------------------------------------- +# Add Aliass +# --------------------------------------------------------- +@router.post("/api/aliases", status_code=status.HTTP_200_OK, responses={ + 200: {"description": "Alias added"}, + 409: {"description": "Alias already present"}, + 500: {"description": "Internal server error"}, +}) +def api_add_alias(request: Request, data: dict): + + # Inizializzazioni + start_ns = time.monotonic_ns() + + try: + alias_id = add_alias(data) + if(alias_id > 0): + took_ms = (time.monotonic_ns() - start_ns) / 1_000_000 + return JSONResponse( + status_code=status.HTTP_200_OK, + content={ + "code": "ALIAS_ADDED", + "status": "success", + "message": "Alias added successfully", + "alias_id": alias_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": "ALIAS_ALREADY_PRESENT", + "status": "failure", + "message": "Alias already present", + "took_ms": took_ms, + }, + ) + + except HTTPException as httpe: + raise httpe + + except Exception as e: + logger = get_logger("aliases") + logger.exception("Error adding alias: %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": "ALIAS_ADD_ERROR", + "status": "failure", + "message": "Internal error adding alias", + "took_ms": took_ms, + }, + ) + +# --------------------------------------------------------- +# Update Alias +# --------------------------------------------------------- +@router.put("/api/aliases/{alias_id}", status_code=status.HTTP_200_OK, responses={ + 200: {"description": "Alias updated"}, + 404: {"description": "Alias not found"}, + 500: {"description": "Internal server error"}, +}) +def api_update_alias(request: Request, data: dict, alias_id: int): + + # Inizializzazioni + start_ns = time.monotonic_ns() + + try: + updated = update_alias(alias_id, data) + if updated: + took_ms = (time.monotonic_ns() - start_ns) / 1_000_000 + return JSONResponse( + status_code=status.HTTP_200_OK, + content={ + "code": "ALIAS_UPDATED", + "status": "success", + "message": "Alias updated successfully", + "alias_id": alias_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": "ALIAS_NOT_FOUND", + "status": "failure", + "message": "Alias not found", + "alias_id": alias_id, + "took_ms": took_ms, + }, + ) + + except Exception as e: + logger = get_logger("aliases") + logger.exception("Error updating alias %s: %s", alias_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": "ALIAS_UPDATE_ERROR", + "status": "failure", + "message": "Internal error updating alias", + "alias_id": alias_id, + "took_ms": took_ms, + }, + ) + +# --------------------------------------------------------- +# Delete +# --------------------------------------------------------- +@router.delete("/api/aliases/{alias_id}", status_code=status.HTTP_200_OK, responses={ + 200: {"description": "Alias deleted"}, + 404: {"description": "Alias not found"}, + 500: {"description": "Internal server error"}, +}) +def api_delete_alias(request: Request, alias_id: int): + + # Inizializzazioni + start_ns = time.monotonic_ns() + + try: + deleted = delete_alias(alias_id) + if deleted: + took_ms = (time.monotonic_ns() - start_ns) / 1_000_000 + return JSONResponse( + status_code=status.HTTP_200_OK, + content={ + "code": "ALIAS_DELETED", + "status": "success", + "message": "Alias deleted successfully", + "details": {"took_ms": took_ms, "alias_id": alias_id,}, + }, + ) + + # Not Found + took_ms = (time.monotonic_ns() - start_ns) / 1_000_000 + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail={ + "code": "ALIAS_NOT_FOUND", + "status": "failure", + "message": "Alias not found", + "alias_id": alias_id, + "took_ms": took_ms, + }, + ) + + except Exception as e: + logger = get_logger("aliases") + logger.exception("Error deleting alias %s: %s", alias_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": "ALIAS_DELETE_ERROR", + "status": "failure", + "message": "Internal error deleting alias", + "alias_id": alias_id, + "took_ms": took_ms, + }, + ) diff --git a/entrypoint.py b/entrypoint.py index 5b25579..29ec408 100755 --- a/entrypoint.py +++ b/entrypoint.py @@ -7,8 +7,9 @@ import sys import argparse # Import local modules from backend.db.db import init_db -import backend.db.hosts import backend.db.users +import backend.db.hosts +import backend.db.aliases # Import Settings from settings.settings import settings # Import Log diff --git a/frontend/aliases.html b/frontend/aliases.html new file mode 100644 index 0000000..f7beff8 --- /dev/null +++ b/frontend/aliases.html @@ -0,0 +1,167 @@ + + + + + Network Manager + + + + + + + + + + + + + + +
+
+ + + +
+
+ + +
+ + +
+
+
+ +
+

+ 🖧 + + Aliases List +

+
+ + +
+ + +
+
+ +
+
+ + +
+ + + +
+
+
+
+ + + + + + + + + + + + + +
Alias Target Note SSL Actions
+ + + + + + + + + + + diff --git a/frontend/hosts.html b/frontend/hosts.html index 148ed06..a00f497 100644 --- a/frontend/hosts.html +++ b/frontend/hosts.html @@ -96,12 +96,12 @@ - - - - - - + + + + + + @@ -124,31 +124,20 @@
Name IPv4 IPv6 MAC Note SSL Hostname IPv4 IPv6 MAC Note SSL Actions