Compare commits

..

14 Commits

Author SHA1 Message Date
jokob-sk
6bc2de6e24 TEST: scan processing - de;eted
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2026-02-06 18:54:15 +11:00
jokob-sk
09b42166cc TEST: scan processing 9
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2026-02-06 18:48:40 +11:00
jokob-sk
dbe490a042 TEST: scan processing 8
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2026-02-06 18:43:53 +11:00
jokob-sk
5996e70f60 TEST: scan processing 7
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2026-02-06 18:39:08 +11:00
jokob-sk
15366a7f2e TEST: scan processing 6 + css fixes
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2026-02-06 18:34:25 +11:00
jokob-sk
d5d1684ef9 TEST: scan processing 5
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2026-02-06 18:21:24 +11:00
jokob-sk
c1141fc9a8 TEST: scan processing 4
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2026-02-06 18:12:16 +11:00
jokob-sk
d38dcda35b TEST: scan processing 3
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2026-02-06 18:06:57 +11:00
jokob-sk
ac5224747e TEST: scan processing 2
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2026-02-06 18:03:17 +11:00
jokob-sk
5c23bde21c TEST: scan processing 1
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2026-02-06 17:57:03 +11:00
jokob-sk
8e83d9b67d BE: Removal of debug code taht was causing devices to appear online #1489
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2026-02-06 14:18:15 +11:00
jokob-sk
30c004eb77 GIT: static code check for disabled CurrentScan cleanup - test
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2026-02-06 14:13:53 +11:00
jokob-sk
1b6dc94bae Deleting Plugin Objects was not possible #1486
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2026-02-05 11:57:37 +11:00
jokob-sk
76d37edc63 jokob-sk/netalertx -> netalertx/netalertx
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2026-02-04 21:21:05 +11:00
33 changed files with 159 additions and 74 deletions

View File

@@ -17,6 +17,23 @@ jobs:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: 🚨 Ensure DELETE FROM CurrentScan is not commented out
run: |
echo "🔍 Checking that DELETE FROM CurrentScan is not commented out..."
MATCHES=$(grep -RInE '^[[:space:]]*#[[:space:]]*db\.sql\.execute\("DELETE FROM CurrentScan"\)' \
--include="*.py" .) || true
if [ -n "$MATCHES" ]; then
echo "❌ Found commented-out DELETE FROM CurrentScan call:"
echo "$MATCHES"
echo
echo "This line must NOT be commented out in committed code."
exit 1
else
echo "✅ DELETE FROM CurrentScan is active."
fi
- name: Check for incorrect absolute '/php/' URLs in frontend code - name: Check for incorrect absolute '/php/' URLs in frontend code
run: | run: |
echo "🔍 Checking for incorrect absolute '/php/' URLs (should be 'php/' or './php/')..." echo "🔍 Checking for incorrect absolute '/php/' URLs (should be 'php/' or './php/')..."

View File

@@ -3,8 +3,8 @@ name: 🧪 Manual Test Suite Selector
on: on:
workflow_dispatch: workflow_dispatch:
inputs: inputs:
run_authoritative: run_scan:
description: '📂 authoritative_fields/ (Logic, Locks, IPs)' description: '📂 scan/ (Scan, Logic, Locks, IPs)'
type: boolean type: boolean
default: true default: true
run_api: run_api:
@@ -43,7 +43,7 @@ jobs:
run: | run: |
PATHS="" PATHS=""
# Folder Mapping with 'test/' prefix # Folder Mapping with 'test/' prefix
if [ "${{ github.event.inputs.run_authoritative }}" == "true" ]; then PATHS="$PATHS test/authoritative_fields/"; fi if [ "${{ github.event.inputs.scan }}" == "true" ]; then PATHS="$PATHS test/scan/"; fi
if [ "${{ github.event.inputs.run_api }}" == "true" ]; then PATHS="$PATHS test/api_endpoints/ test/server/"; fi if [ "${{ github.event.inputs.run_api }}" == "true" ]; then PATHS="$PATHS test/api_endpoints/ test/server/"; fi
if [ "${{ github.event.inputs.run_backend }}" == "true" ]; then PATHS="$PATHS test/backend/"; fi if [ "${{ github.event.inputs.run_backend }}" == "true" ]; then PATHS="$PATHS test/backend/"; fi
if [ "${{ github.event.inputs.run_docker_env }}" == "true" ]; then PATHS="$PATHS test/docker_tests/"; fi if [ "${{ github.event.inputs.run_docker_env }}" == "true" ]; then PATHS="$PATHS test/docker_tests/"; fi

View File

@@ -56,14 +56,14 @@ docker run -d \
--tmpfs /tmp:uid=20211,gid=20211,mode=1700 \ --tmpfs /tmp:uid=20211,gid=20211,mode=1700 \
-e PORT=20211 \ -e PORT=20211 \
-e APP_CONF_OVERRIDE='{"GRAPHQL_PORT":"20214"}' \ -e APP_CONF_OVERRIDE='{"GRAPHQL_PORT":"20214"}' \
ghcr.io/jokob-sk/netalertx:latest ghcr.io/netalertx/netalertx:latest
``` ```
Note: Your `/local_data_dir` should contain a `config` and `db` folder. Note: Your `/local_data_dir` should contain a `config` and `db` folder.
To deploy a containerized instance directly from the source repository, execute the following BASH sequence: To deploy a containerized instance directly from the source repository, execute the following BASH sequence:
```bash ```bash
git clone https://github.com/jokob-sk/NetAlertX.git git clone https://github.com/netalertx/NetAlertX.git
cd NetAlertX cd NetAlertX
docker compose up --force-recreate --build docker compose up --force-recreate --build
# To customize: edit docker-compose.yaml and run that last command again # To customize: edit docker-compose.yaml and run that last command again

View File

@@ -21,7 +21,7 @@ docker run \
--tmpfs /tmp:uid=20211,gid=20211,mode=1700 \ --tmpfs /tmp:uid=20211,gid=20211,mode=1700 \
-e PORT=20211 \ -e PORT=20211 \
-e APP_CONF_OVERRIDE='{"GRAPHQL_PORT":"20214"}' \ -e APP_CONF_OVERRIDE='{"GRAPHQL_PORT":"20214"}' \
ghcr.io/jokob-sk/netalertx:latest ghcr.io/netalertx/netalertx:latest
``` ```
@@ -34,7 +34,7 @@ Note: Your `/local_data_dir` should contain a `config` and `db` folder.
If possible, check if your issue got fixed in the `_dev` image before opening a new issue. The container is: If possible, check if your issue got fixed in the `_dev` image before opening a new issue. The container is:
`ghcr.io/jokob-sk/netalertx-dev:latest` `ghcr.io/netalertx/netalertx-dev:latest`
> ⚠ Please backup your DB and config beforehand! > ⚠ Please backup your DB and config beforehand!

View File

@@ -43,7 +43,7 @@ The following steps will guide you to set up your environment for local developm
### 1. Download the code: ### 1. Download the code:
- `mkdir /development` - `mkdir /development`
- `cd /development && git clone https://github.com/jokob-sk/NetAlertX.git` - `cd /development && git clone https://github.com/netalertx/NetAlertX.git`
### 2. Create a DEV .env_dev file ### 2. Create a DEV .env_dev file

View File

@@ -17,7 +17,7 @@ services:
netalertx: netalertx:
#use an environmental variable to set host networking mode if needed #use an environmental variable to set host networking mode if needed
container_name: netalertx # The name when you docker contiainer ls container_name: netalertx # The name when you docker contiainer ls
image: ghcr.io/jokob-sk/netalertx:latest image: ghcr.io/netalertx/netalertx:latest
network_mode: ${NETALERTX_NETWORK_MODE:-host} # Use host networking for ARP scanning and other services network_mode: ${NETALERTX_NETWORK_MODE:-host} # Use host networking for ARP scanning and other services
read_only: true # Make the container filesystem read-only read_only: true # Make the container filesystem read-only

View File

@@ -31,7 +31,7 @@ docker run -d --rm --network=host \
--tmpfs /tmp:uid=${NETALERTX_UID:-20211},gid=${NETALERTX_GID:-20211},mode=1700 \ --tmpfs /tmp:uid=${NETALERTX_UID:-20211},gid=${NETALERTX_GID:-20211},mode=1700 \
-e PORT=20211 \ -e PORT=20211 \
-e APP_CONF_OVERRIDE={"GRAPHQL_PORT":"20214"} \ -e APP_CONF_OVERRIDE={"GRAPHQL_PORT":"20214"} \
ghcr.io/jokob-sk/netalertx:latest ghcr.io/netalertx/netalertx:latest
``` ```
> Runtime UID/GID: The image defaults to a service user `netalertx` (UID/GID 20211). A separate readonly lock owner also uses UID/GID 20211 for 004/005 immutability. You can override the runtime UID/GID at build (ARG) or run (`--user` / compose `user:`) but must align writable mounts (`/data`, `/tmp*`) and tmpfs `uid/gid` to that choice. > Runtime UID/GID: The image defaults to a service user `netalertx` (UID/GID 20211). A separate readonly lock owner also uses UID/GID 20211 for 004/005 immutability. You can override the runtime UID/GID at build (ARG) or run (`--user` / compose `user:`) but must align writable mounts (`/data`, `/tmp*`) and tmpfs `uid/gid` to that choice.

View File

@@ -35,9 +35,9 @@ services:
netalertx: netalertx:
container_name: netalertx container_name: netalertx
# Use this line for stable release # Use this line for stable release
image: "ghcr.io/jokob-sk/netalertx:latest" image: "ghcr.io/netalertx/netalertx:latest"
# Or, use this for the latest development build # Or, use this for the latest development build
# image: "ghcr.io/jokob-sk/netalertx-dev:latest" # image: "ghcr.io/netalertx/netalertx-dev:latest"
network_mode: "host" network_mode: "host"
restart: unless-stopped restart: unless-stopped
cap_drop: # Drop all capabilities for enhanced security cap_drop: # Drop all capabilities for enhanced security

View File

@@ -44,7 +44,7 @@ Use the following Compose snippet to deploy NetAlertX with a **static LAN IP** a
```yaml ```yaml
services: services:
netalertx: netalertx:
image: ghcr.io/jokob-sk/netalertx:latest image: ghcr.io/netalertx/netalertx:latest
... ...
networks: networks:
swarm-ipvlan: swarm-ipvlan:

View File

@@ -32,12 +32,22 @@ NetAlertX is a lightweight, flexible platform for monitoring networks, tracking
![Event-Driven Alerts](./img/FEATURES/Event-Driven_Alerts.png) ![Event-Driven Alerts](./img/FEATURES/Event-Driven_Alerts.png)
- **Real-Time Notifications**: Receive immediate alerts for new devices, disconnected devices, or unexpected changes. - **Real-Time Notifications**: Receive immediate alerts for new devices, disconnected devices, or unexpected changes.
- **Customizable Triggers**: Define rules based on device type, IP ranges, presence, or other network parameters. - **Customizable Filters and Rules**: Define rules based on device type, IP ranges, presence, or other network parameters.
- **Alert Deduplication & Suppression**: Avoid unnecessary noise with smart alert handling. - **Alert Deduplication & Suppression**: Avoid unnecessary noise with smart alert handling.
- **Historical Logs**: Maintain a complete timeline of network events for review and reporting. - **Historical Logs**: Maintain a complete timeline of network events for review and reporting.
--- ---
## Workflows for implementing Business rules
![orkflows](./img/WORKFLOWS/workflows.png)
- **Custom rules**: Cretae custom flows and update device information based to scan results.
- **Customizable Triggers**: Define rules based on any device data, including device type, IP ranges, presence, or other network parameters.
- **Automated Updates**: Automate repetitive tasks, making network management more efficient.
---
## Multi-Channel Notification ## Multi-Channel Notification
![Multi-Channel Notification](./img/FEATURES/Multi-Channel_Notifications.png) ![Multi-Channel Notification](./img/FEATURES/Multi-Channel_Notifications.png)

View File

@@ -12,7 +12,7 @@ docker run --rm --network=host \
-v /etc/localtime:/etc/localtime:ro \ -v /etc/localtime:/etc/localtime:ro \
--tmpfs /tmp:uid=20211,gid=20211,mode=1700 \ --tmpfs /tmp:uid=20211,gid=20211,mode=1700 \
-e PORT=20211 \ -e PORT=20211 \
ghcr.io/jokob-sk/netalertx:latest ghcr.io/netalertx/netalertx:latest
``` ```
> [!WARNING] > [!WARNING]
@@ -70,7 +70,7 @@ If you use a custom `PUID` (e.g. `0`) and `GUID` (e.g. `100`) make sure you also
docker run -it --rm --name netalertx --user "0" \ docker run -it --rm --name netalertx --user "0" \
-v /local_data_dir:/data \ -v /local_data_dir:/data \
--tmpfs /tmp:uid=20211,gid=20211,mode=1700 \ --tmpfs /tmp:uid=20211,gid=20211,mode=1700 \
ghcr.io/jokob-sk/netalertx:latest ghcr.io/netalertx/netalertx:latest
``` ```
2. Wait for logs showing **permissions being fixed**. The container will then **hang intentionally**. 2. Wait for logs showing **permissions being fixed**. The container will then **hang intentionally**.
@@ -95,7 +95,7 @@ docker run -it --rm --name netalertx --user "0" \
services: services:
netalertx: netalertx:
container_name: netalertx container_name: netalertx
image: "ghcr.io/jokob-sk/netalertx" image: "ghcr.io/netalertx/netalertx"
network_mode: "host" network_mode: "host"
cap_drop: # Drop all capabilities for enhanced security cap_drop: # Drop all capabilities for enhanced security
- ALL - ALL

View File

@@ -318,7 +318,7 @@ As per user feedback, weve re-introduced the ability to control which user th
services: services:
netalertx: netalertx:
container_name: netalertx container_name: netalertx
image: "ghcr.io/jokob-sk/netalertx" image: "ghcr.io/netalertx/netalertx"
network_mode: "host" network_mode: "host"
cap_drop: cap_drop:
- ALL - ALL

View File

@@ -80,9 +80,9 @@ services:
netalertx: netalertx:
container_name: netalertx container_name: netalertx
# Use this line for the stable release # Use this line for the stable release
image: "ghcr.io/jokob-sk/netalertx:latest" image: "ghcr.io/netalertx/netalertx:latest"
# Or use this line for the latest development build # Or use this line for the latest development build
# image: "ghcr.io/jokob-sk/netalertx-dev:latest" # image: "ghcr.io/netalertx/netalertx-dev:latest"
network_mode: "host" network_mode: "host"
restart: unless-stopped restart: unless-stopped

View File

@@ -39,7 +39,7 @@ You can specify the DNS server in the docker-compose to improve name resolution
services: services:
netalertx: netalertx:
container_name: netalertx container_name: netalertx
image: "ghcr.io/jokob-sk/netalertx:latest" image: "ghcr.io/netalertx/netalertx:latest"
... ...
dns: # specifying the DNS servers used for the container dns: # specifying the DNS servers used for the container
- 10.8.0.1 - 10.8.0.1

View File

@@ -37,8 +37,8 @@ services:
netalertx: netalertx:
container_name: netalertx container_name: netalertx
# use the below line if you want to test the latest dev image # use the below line if you want to test the latest dev image
# image: "ghcr.io/jokob-sk/netalertx-dev:latest" # image: "ghcr.io/netalertx/netalertx-dev:latest"
image: "ghcr.io/jokob-sk/netalertx:latest" image: "ghcr.io/netalertx/netalertx:latest"
network_mode: "host" network_mode: "host"
restart: unless-stopped restart: unless-stopped
cap_drop: # Drop all capabilities for enhanced security cap_drop: # Drop all capabilities for enhanced security

View File

@@ -538,6 +538,7 @@ body
font-size: larger; font-size: larger;
float: right; float: right;
text-align: center; text-align: center;
z-index: 1;
} }
hr hr
@@ -929,6 +930,14 @@ height: 50px;
.nav-tabs-custom .tab-content { .nav-tabs-custom .tab-content {
overflow: scroll; overflow: scroll;
} }
.infobox_label
{
display: none;
}
.small-box .icon
{
display: block !important;
}
} }
.top_small_box_gray_text { .top_small_box_gray_text {

View File

@@ -572,7 +572,7 @@ function purgeAllExecute() {
data: JSON.stringify({ data: JSON.stringify({
dbtable: dbTable, dbtable: dbTable,
columnName: 'Plugin', columnName: 'Plugin',
id: plugPrefix id: [plugPrefix]
}), }),
contentType: "application/json", contentType: "application/json",
success: function(response, textStatus) { success: function(response, textStatus) {
@@ -603,15 +603,18 @@ function deleteListed(plugPrefixArg, dbTableArg) {
// Ask for confirmation // Ask for confirmation
showModalWarning(`${getString('Gen_Purge')} ${plugPrefix} ${dbTable}`, `${getString('Gen_AreYouSure')} (${idArr.length})`, showModalWarning(`${getString('Gen_Purge')} ${plugPrefix} ${dbTable}`, `${getString('Gen_AreYouSure')} (${idArr.length})`,
`${getString('Gen_Cancel')}`, `${getString('Gen_Okay')}`, "deleteListedExecute"); `${getString('Gen_Cancel')}`, `${getString('Gen_Okay')}`, () => deleteListedExecute(idArr));
} }
// -------------------------------------------------------- // --------------------------------------------------------
function deleteListedExecute() { function deleteListedExecute(idArr) {
const apiBase = getApiBase(); const apiBase = getApiBase();
const apiToken = getSetting("API_TOKEN"); const apiToken = getSetting("API_TOKEN");
const url = `${apiBase}/dbquery/delete`; const url = `${apiBase}/dbquery/delete`;
console.log(idArr);
$.ajax({ $.ajax({
method: "POST", method: "POST",
url: url, url: url,
@@ -619,7 +622,7 @@ function deleteListedExecute() {
data: JSON.stringify({ data: JSON.stringify({
dbtable: dbTable, dbtable: dbTable,
columnName: 'Index', columnName: 'Index',
id: idArr.toString() id: idArr
}), }),
contentType: "application/json", contentType: "application/json",
success: function(response, textStatus) { success: function(response, textStatus) {

View File

@@ -57,7 +57,7 @@
virtualisation.oci-containers = { virtualisation.oci-containers = {
containers = { containers = {
netalertx = { netalertx = {
image = "ghcr.io/jokob-sk/netalertx:${cfg.imageTag}"; image = "ghcr.io/netalertx/netalertx:${cfg.imageTag}";
autoStart = true; autoStart = true;
extraOptions = [ extraOptions = [
"--network=host" "--network=host"

View File

@@ -185,7 +185,7 @@ printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Cloning app
printf "%b\n" "--------------------------------------------------------------------------" printf "%b\n" "--------------------------------------------------------------------------"
mkdir -p "$INSTALL_DIR" mkdir -p "$INSTALL_DIR"
git clone https://github.com/jokob-sk/NetAlertX.git "$INSTALL_DIR/" git clone https://github.com/netalertx/NetAlertX.git "$INSTALL_DIR/"
if [ ! -f "$INSTALL_DIR/front/buildtimestamp.txt" ]; then if [ ! -f "$INSTALL_DIR/front/buildtimestamp.txt" ]; then
date +%s > "$INSTALL_DIR/front/buildtimestamp.txt" date +%s > "$INSTALL_DIR/front/buildtimestamp.txt"

View File

@@ -1287,14 +1287,22 @@ def dbquery_update(payload=None):
def dbquery_delete(payload=None): def dbquery_delete(payload=None):
data = request.get_json() or {} data = request.get_json() or {}
required = ["columnName", "id", "dbtable"] required = ["columnName", "id", "dbtable"]
if not all(data.get(k) for k in required): if not all(k in data and data[k] for k in required):
return jsonify({"success": False, "message": "ERROR: Missing parameters", "error": "Missing required 'columnName', 'id', or 'dbtable' query parameter"}), 400 return jsonify({
"success": False,
"message": "ERROR: Missing parameters",
"error": "Missing required 'columnName', 'id', or 'dbtable' query parameter"
}), 400
return delete_query( dbtable = data["dbtable"]
column_name=data["columnName"], column_name = data["columnName"]
ids=data["id"], ids = data["id"]
dbtable=data["dbtable"],
) # Ensure ids is a list
if not isinstance(ids, list):
ids = [ids]
return delete_query(column_name, ids, dbtable)
# -------------------------- # --------------------------

View File

@@ -11,6 +11,7 @@ INSTALL_PATH = os.getenv("NETALERTX_APP", "/app")
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"]) sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
from database import get_temp_db_connection # noqa: E402 [flake8 lint suppression] from database import get_temp_db_connection # noqa: E402 [flake8 lint suppression]
from logger import mylog # noqa: E402 [flake8 lint suppression]
def read_query(raw_sql_b64): def read_query(raw_sql_b64):
@@ -82,17 +83,18 @@ def delete_query(column_name, ids, dbtable):
conn = get_temp_db_connection() conn = get_temp_db_connection()
cur = conn.cursor() cur = conn.cursor()
if not isinstance(ids, list):
ids = [ids]
deleted_count = 0 deleted_count = 0
for id_val in ids: for id_val in ids:
sql = f"DELETE FROM {dbtable} WHERE {column_name} = ?" # Wrap table and column in quotes to handle reserved words
sql = f'DELETE FROM "{dbtable}" WHERE "{column_name}" = ?'
mylog("debug", f"[delete_query] sql {sql} with id={id_val}")
cur.execute(sql, (id_val,)) cur.execute(sql, (id_val,))
deleted_count += cur.rowcount deleted_count += cur.rowcount
conn.commit() conn.commit()
conn.close() conn.close()
return jsonify({"success": True, "deleted_count": deleted_count}) return jsonify({"success": True, "deleted_count": deleted_count})
except Exception as e: except Exception as e:
return jsonify({"success": False, "error": str(e)}), 400 return jsonify({"success": False, "error": str(e)}), 400

View File

@@ -44,7 +44,7 @@ ALLOWED_NMAP_MODES = Literal[
NOTIFICATION_LEVELS = Literal["info", "warning", "error", "alert", "interrupt"] NOTIFICATION_LEVELS = Literal["info", "warning", "error", "alert", "interrupt"]
ALLOWED_TABLES = Literal["Devices", "Events", "Sessions", "Settings", "CurrentScan", "Online_History", "Plugins_Objects"] ALLOWED_TABLES = Literal["Devices", "Events", "Sessions", "Settings", "CurrentScan", "Online_History", "Plugins_Objects", "Plugins_History"]
ALLOWED_LOG_FILES = Literal[ ALLOWED_LOG_FILES = Literal[
"app.log", "app_front.log", "IP_changes.log", "stdout.log", "stderr.log", "app.log", "app_front.log", "IP_changes.log", "stdout.log", "stderr.log",

View File

@@ -103,7 +103,7 @@ def process_scan(db):
# Clear current scan as processed # Clear current scan as processed
# 🐛 CurrentScan DEBUG: comment out below when debugging to keep the CurrentScan table after restarts/scan finishes # 🐛 CurrentScan DEBUG: comment out below when debugging to keep the CurrentScan table after restarts/scan finishes
# db.sql.execute("DELETE FROM CurrentScan") db.sql.execute("DELETE FROM CurrentScan")
# re-broadcast unread notifiation count to update FE # re-broadcast unread notifiation count to update FE
update_unread_notifications_count() update_unread_notifications_count()

View File

@@ -41,6 +41,8 @@ def scan_db():
devIsNew INTEGER DEFAULT 1, devIsNew INTEGER DEFAULT 1,
devFavorite INTEGER DEFAULT 0, devFavorite INTEGER DEFAULT 0,
devScan INTEGER DEFAULT 1, devScan INTEGER DEFAULT 1,
devAlertDown INTEGER DEFAULT 0,
devAlertEvents INTEGER DEFAULT 1,
-- Authoritative Metadata Columns -- Authoritative Metadata Columns
devMacSource TEXT, devMacSource TEXT,
@@ -81,6 +83,41 @@ def scan_db():
) )
""") """)
# 3. Events Table
cur.execute("""
CREATE TABLE Events (
eve_MAC TEXT,
eve_IP TEXT,
eve_DateTime TEXT,
eve_EventType TEXT,
eve_AdditionalInfo TEXT,
eve_PendingAlertEmail INTEGER
)
""")
# 4. LatestEventsPerMAC View
cur.execute("""DROP VIEW IF EXISTS LatestEventsPerMAC;""")
cur.execute("""
CREATE VIEW LatestEventsPerMAC AS
WITH RankedEvents AS (
SELECT
e.*,
ROW_NUMBER() OVER (PARTITION BY e.eve_MAC ORDER BY e.eve_DateTime DESC) AS row_num
FROM Events AS e
)
SELECT
e.eve_MAC,
e.eve_EventType,
e.eve_DateTime,
e.eve_PendingAlertEmail,
d.devPresentLastScan,
c.scanLastIP
FROM RankedEvents AS e
LEFT JOIN Devices AS d ON e.eve_MAC = d.devMac
LEFT JOIN CurrentScan AS c ON e.eve_MAC = c.scanMac
WHERE e.row_num = 1;
""")
# 3. LatestDeviceScan View (Inner Join for Online Devices) # 3. LatestDeviceScan View (Inner Join for Online Devices)
cur.execute(""" cur.execute("""
CREATE VIEW LatestDeviceScan AS CREATE VIEW LatestDeviceScan AS

View File

@@ -104,4 +104,3 @@ def test_primary_ipv4_is_set_and_ipv6_preserved(scan_db, mock_device_handling):
assert row["devLastIP"] == "10.0.0.5" assert row["devLastIP"] == "10.0.0.5"
assert row["devPrimaryIPv4"] == "10.0.0.5" assert row["devPrimaryIPv4"] == "10.0.0.5"
assert row["devPrimaryIPv6"] == "2001:db8::2" assert row["devPrimaryIPv6"] == "2001:db8::2"