diff --git a/.devcontainer/scripts/load-devices.sh b/.devcontainer/scripts/load-devices.sh new file mode 100755 index 00000000..a9581ce5 --- /dev/null +++ b/.devcontainer/scripts/load-devices.sh @@ -0,0 +1,78 @@ +#!/bin/bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" +if [ -n "${CSV_PATH:-}" ]; then + : # user provided CSV_PATH +else + # Portable mktemp fallback: try GNU coreutils first, then busybox-style + if mktemp --version >/dev/null 2>&1; then + CSV_PATH="$(mktemp --tmpdir netalertx-devices-XXXXXX.csv 2>/dev/null || mktemp /tmp/netalertx-devices-XXXXXX.csv)" + else + CSV_PATH="$(mktemp -t netalertx-devices.XXXXXX 2>/dev/null || mktemp /tmp/netalertx-devices-XXXXXX.csv)" + fi +fi +DEVICE_COUNT="${DEVICE_COUNT:-255}" +SEED="${SEED:-20211}" +NETWORK_CIDR="${NETWORK_CIDR:-192.168.50.0/22}" +DB_DIR="${NETALERTX_DB:-/data/db}" +DB_FILE="${DB_DIR%/}/app.db" + +# Ensure we are inside the devcontainer +"${SCRIPT_DIR}/isDevContainer.sh" >/dev/null + +if [ ! -f "${DB_FILE}" ]; then + echo "[load-devices] Database not found at ${DB_FILE}. Is the devcontainer initialized?" >&2 + exit 1 +fi + +if ! command -v sqlite3 >/dev/null 2>&1; then + echo "[load-devices] sqlite3 is required but not installed." >&2 + exit 1 +fi +if ! command -v python3 >/dev/null 2>&1; then + echo "[load-devices] python3 is required but not installed." >&2 + exit 1 +fi +if ! command -v curl >/dev/null 2>&1; then + echo "[load-devices] curl is required but not installed." >&2 + exit 1 +fi + +# Generate synthetic device inventory CSV +python3 "${REPO_ROOT}/scripts/generate-device-inventory.py" \ + --output "${CSV_PATH}" \ + --devices "${DEVICE_COUNT}" \ + --seed "${SEED}" \ + --network "${NETWORK_CIDR}" >/dev/null + +echo "[load-devices] CSV generated at ${CSV_PATH} (devices=${DEVICE_COUNT}, seed=${SEED})" + +API_TOKEN="$(sqlite3 "${DB_FILE}" "SELECT setValue FROM Settings WHERE setKey='API_TOKEN';")" +GRAPHQL_PORT="$(sqlite3 "${DB_FILE}" "SELECT setValue FROM Settings WHERE setKey='GRAPHQL_PORT';")" + +if [ -z "${API_TOKEN}" ] || [ -z "${GRAPHQL_PORT}" ]; then + echo "[load-devices] Failed to read API_TOKEN or GRAPHQL_PORT from ${DB_FILE}" >&2 + exit 1 +fi + +IMPORT_URL="http://localhost:${GRAPHQL_PORT}/devices/import" + +HTTP_CODE=$(curl -sS -o /tmp/load-devices-response.json -w "%{http_code}" \ + -X POST "${IMPORT_URL}" \ + -H "Authorization: Bearer ${API_TOKEN}" \ + -F "file=@${CSV_PATH}") + +if [ "${HTTP_CODE}" != "200" ]; then + echo "[load-devices] Import failed with HTTP ${HTTP_CODE}. Response:" >&2 + cat /tmp/load-devices-response.json >&2 + exit 1 +fi + +# Fetch totals for a quick sanity check +TOTALS=$(curl -sS -H "Authorization: Bearer ${API_TOKEN}" "http://localhost:${GRAPHQL_PORT}/devices/totals" || true) + +echo "[load-devices] Import succeeded (HTTP ${HTTP_CODE})." +echo "[load-devices] Devices totals: ${TOTALS}" +echo "[load-devices] Done. CSV kept at ${CSV_PATH}" diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 8c676cc6..a193ddd8 100755 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -21,7 +21,6 @@ "showReuseMessage": false, "group": "POSIX Tasks" }, - "problemMatcher": [], "group": { "kind": "build", @@ -59,6 +58,31 @@ "color": "terminal.ansiRed" } }, + { + "label": "[Dev Container] Load Sample Devices", + "type": "shell", + "command": "./isDevContainer.sh || exit 1; ./load-devices.sh", + "detail": "Generates a synthetic device inventory and imports it into the devcontainer database via /devices/import.", + "options": { + "cwd": "/workspaces/NetAlertX/.devcontainer/scripts", + "env": { + "CSV_PATH": "/tmp/netalertx-devices.csv" + } + }, + "presentation": { + "echo": true, + "reveal": "always", + "panel": "shared", + "showReuseMessage": false, + "clear": false, + "group": "Devcontainer" + }, + "problemMatcher": [], + "icon": { + "id": "cloud-upload", + "color": "terminal.ansiYellow" + } + }, { "label": "[Dev Container] Re-Run Startup Script", "type": "shell", @@ -73,7 +97,6 @@ "panel": "shared", "showReuseMessage": false }, - "problemMatcher": [], "icon": { "id": "beaker", diff --git a/scripts/generate-device-inventory.py b/scripts/generate-device-inventory.py index 62f7c4c7..e0f612cb 100644 --- a/scripts/generate-device-inventory.py +++ b/scripts/generate-device-inventory.py @@ -162,8 +162,9 @@ def build_row( now: dt.datetime, ) -> dict[str, str]: comments = "Synthetic device generated for testing." - first_seen = random_time(now) - last_seen = random_time(now) + t1 = random_time(now) + t2 = random_time(now) + first_seen, last_seen = (t1, t2) if t1 <= t2 else (t2, t1) fqdn = f"{name.lower().replace(' ', '-')}.{site}" if name else "" # Minimal fields set; missing ones default to empty string for CSV compatibility. @@ -215,6 +216,7 @@ def generate_rows(args: argparse.Namespace, header: list[str]) -> list[dict[str, rows: list[dict[str, str]] = [] + # Include one Internet root device that anchors the tree; it does not consume an IP. required_devices = 1 + args.switches + args.aps + args.devices if required_devices > len(ip_pool): raise ValueError( @@ -227,6 +229,30 @@ def generate_rows(args: argparse.Namespace, header: list[str]) -> list[dict[str, ip_pool.remove(choice) return choice + # Root "Internet" device (no parent, no IP) so the topology has a defined root. + root_row = build_row( + name="Internet", + dev_type="Gateway", + vendor="NetAlertX", + mac="Internet", + parent_mac="", + ip="", + header=header, + owner=args.owner, + site=args.site, + ssid=args.ssid, + now=now, + ) + root_row["devComments"] = "Synthetic root device representing the Internet." + root_row["devParentRelType"] = "Root" + root_row["devStaticIP"] = "0" + root_row["devScan"] = "0" + root_row["devAlertEvents"] = "0" + root_row["devAlertDown"] = "0" + root_row["devLogEvents"] = "0" + root_row["devPresentLastScan"] = "0" + rows.append(root_row) + router_mac = random_mac(macs) router_ip = take_ip() rows.append(