Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/ADVISORY_MULTI_NETWORK.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Effective multi-network monitoring starts with understanding how NetAlertX "sees
* **A. Understand Network Accessibility:** Local ARP-based scanning (**ARPSCAN**) only discovers devices on directly accessible subnets due to Layer 2 limitations. It cannot traverse VPNs or routed borders without specific configuration.
* **B. Plan Subnet & Scan Interfaces:** Explicitly configure each accessible segment in `SCAN_SUBNETS` with the corresponding interfaces.
* **C. Remote & Inaccessible Networks:** For networks unreachable via ARP, use these strategies:
* **Alternate Plugins:** Supplement discovery with [SNMPDSC](SNMPDSC) or [DHCP lease imports](https://docs.netalertx.com/PLUGINS/?h=DHCPLSS#available-plugins).
* **Alternate Plugins:** Supplement discovery with [SNMPDSC](https://docs.netalertx.com/PLUGINS/?h=SNMPDSC#available-plugins) or [DHCP lease imports](https://docs.netalertx.com/PLUGINS/?h=DHCPLSS#available-plugins).
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check plugin ID casing in docs/PLUGINS.md to determine correct format

# Search for SNMPDSC and DBCLNP plugin references
rg -n "^\| \`(SNMPDSC|DBCLNP)\`" docs/PLUGINS.md

Repository: netalertx/NetAlertX

Length of output: 497


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check the specific file for h= query casing
rg -n "\?h=" docs/ADVISORY_MULTI_NETWORK.md

# Also list any occurrences of these plugin IDs in the file for context
rg -n "\bSNMPDSC\b|\bDBCLNP\b" docs/ADVISORY_MULTI_NETWORK.md

# Check where those plugin IDs appear elsewhere in docs (optional but helpful)
rg -n "DBCLNP|SNMPDSC" docs --glob='*.md' | head -n 50

Repository: netalertx/NetAlertX

Length of output: 3101


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find how `?h=` is used/parsed in the repo
rg -n "\"\?h=|\\?h=|\\bh=\\$\\{?|\\bh=\" --glob='*.{js,ts,tsx,jsx,py,rb,go,java,cs,php,sh,md,html,css,scss,vue}' . || true

# Look for `available-plugins` anchor/id generation
rg -n "available-plugins" docs . || true

# Find any code/docs that mention DBCLNP/SNMPDSC link construction
rg -n "SNMPDSC|DBCLNP|DHCPLSS" docs . || true

# Check if there's a docs-link helper for plugin IDs
rg -n "PLUGINS/.+\\?h=|docs\\.netalertx\\.com/PLUGINS/\\?h=" . || true

Repository: netalertx/NetAlertX

Length of output: 133


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find how `?h=` is used/parsed in the repo (search relevant patterns)
rg -n --glob='*.{js,ts,tsx,jsx,py,rb,go,java,cs,php,sh,md,html,css,scss,vue}' '(\\\?h=|\\bh=\\$\\{|\\bh=)' . || true

# Find any docs anchor generation / references for "available-plugins"
rg -n "available-plugins" docs . || true

# Check for any code/docs that construct PLUGINS search URLs with ?h=
rg -n "docs\\.netalertx\\.com/PLUGINS/\\?h=|/PLUGINS/\\?h=" . || true

# Check for any helper that builds plugin links with `h` param
rg -n "PLUGINS.*\\?h=|\\?h=.*`#available-plugins`|plugin.*\\?h=" . || true

Repository: netalertx/NetAlertX

Length of output: 3543


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Confirm canonical casing in docs/PLUGINS.md
rg -n "^\| `(DBCLNP|SNMPDSC|DHCPLSS)`" docs/PLUGINS.md || true

# Find any code/templates that parse `?h=` (static-site/search logic)
rg -n "h=|\\?h=|searchParams\\.get\\(['\"]h['\"]\\)|URLSearchParams\\(|get\\(['\"]h['\"]\\)" . --glob='*.{js,ts,tsx,jsx,py,rb,go,java,cs,php,sh,md,html,css,scss,vue}' || true

# Look specifically for any case-normalization logic around that parameter
rg -n "toLowerCase\\(\\)|toUpperCase\\(\\)|lowercase|uppercase" . --glob='*.{js,ts,tsx,jsx,py,rb,go,java,cs,php,sh,md,html,css,scss,vue}' || true

Repository: netalertx/NetAlertX

Length of output: 50532


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Where the frontend reads query param `h`
rg -n "get\\(['\"]h['\"]\\)" . --glob='*.{js,ts,tsx,jsx,php,html,css,scss}' --glob='!**/node_modules/**' || true
rg -n "URLSearchParams\\([^\\)]*\\)\\s*;|new URLSearchParams\\(window\\.location\\.search\\)" front docs server --glob='*.{js,ts,tsx,jsx,php,html}' || true

# If there's any case normalization around it
rg -n "get\\(['\"]h['\"]\\).*toLowerCase|toLowerCase\\(\\).*get\\(['\"]h['\"]\\)|toUpperCase\\(\\).*get\\(['\"]h['\"]\\)" . --glob='*.{js,ts,tsx,jsx,php,html}' || true

# Inspect any docs override that might implement query highlighting
rg -n "\\?h=|\\b'h\\b|available-plugins" docs/overrides docs --glob='*.{html,js,css,md}' --max-count=200 || true

Repository: netalertx/NetAlertX

Length of output: 2081


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Find where `h` is read from query params
rg -n "get\\(['\"]h['\"]\\)" front server --glob='*.{js,ts,php}' || true
rg -n "\\?h=|/PLUGINS/\\?h=|available-plugins" front server docs --glob='*.{js,ts,php,md,html}' || true

# 2) For the likely locations, print small context blocks around where URLSearchParams is proxied/used.
for f in \
  front/js/common.js \
  front/pluginsCore.php \
  front/report.php \
  front/deviceDetails.php \
  front/js/ui_components.js \
; do
  if [ -f "$f" ]; then
    echo "===== $f (context around h) ====="
    rg -n "h['\"]\\)|get\\(['\"]h['\"]\\)" "$f" || true
    # Print a small window around occurrences to avoid huge output
    rg -n "get\\(['\"]h['\"]\\)" "$f" -n | head -n 20 | cut -d: -f1 | while read -r line; do
      start=$((line-15)); end=$((line+40));
      sed -n "${start},${end}p" "$f" | nl -ba | sed -n '1,120p'
      echo "----"
    done || true
  fi
done

# 3) Also check server side for handling of query param `h`
rg -n "REQUEST.*\\?h|\\bh\\b.*request|\\?h=" server --glob='*.{php,py,js,ts}' || true
rg -n "getQueryParam\\(|query.*h|search.*h" server --glob='*.{php,py,js,ts}' || true

Repository: netalertx/NetAlertX

Length of output: 7687


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Search for any code that explicitly reads query param `h`
rg -n --hidden --no-ignore-vcs --glob='!**/node_modules/**' \
  "(get\\(['\"]h['\"]\\)|searchParams\\.get\\(['\"]h['\"]\\)|URLSearchParams\\([^\\)]*\\)\\s*\\)|location\\.search)" .

# Search for any usage of `available-plugins` in scripts/templates (might tie to `h=...`)
rg -n --hidden --no-ignore-vcs --glob='!**/node_modules/**' \
  "available-plugins|PLUGINS\\/?\\?h=|\\?h=" docs front server . --glob='*.{js,ts,php,py,html,css,md}'

# Narrow further to docs theme overrides
rg -n --hidden --no-ignore-vcs --glob='!**/node_modules/**' \
  "URLSearchParams|location\\.search|\\?h=|available-plugins|get\\(['\"]h['\"]\\)" docs/overrides docs --glob='*.{js,html,css,md}'

Repository: netalertx/NetAlertX

Length of output: 771


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find any code that reads query param `h` directly
rg -n "params\\.get\\(['\"]h['\"]\\)|get\\(['\"]h['\"]\\)\\s*\\)|request\\.args\\.get\\(['\"]h['\"]\\)|args\\.get\\(['\"]h['\"]\\)" front server docs . --glob='*.{js,ts,php,py,rb,html,md,css,scss,vue}' || true

# Look for any `h` handling on query/search params (normalization or lower/upper)
rg -n "['\"]h['\"]\\s*\\)|\\bh\\b\\s*[:=].*\\?h=|lowercase|uppercase|toLowerCase\\(|toUpperCase\\(" front server docs . --glob='*.{js,ts,php,py,rb,html,md,css,scss,vue}' || true

# If nothing found, at least inspect the occurrences of URLSearchParams usage to see if 'h' is used.
for f in front/report.php front/pluginsCore.php front/deviceDetails.php front/js/ui_components.js front/js/common.js; do
  if [ -f "$f" ]; then
    echo "==== $f ===="
    rg -n "URLSearchParams\\(|window\\.location\\.search" "$f" || true
    # show nearby lines for the first few matches
    rg -n "URLSearchParams\\(" "$f" | head -n 10 | cut -d: -f1 | while read -r line; do
      start=$((line-10)); end=$((line+60));
      sed -n "${start},${end}p" "$f" | nl -ba
      echo "----"
    done
  fi
done

Repository: netalertx/NetAlertX

Length of output: 50439


Standardize h query param casing to match plugin IDs

docs/ADVISORY_MULTI_NETWORK.md uses ?h=SNMPDSC (line 10), which matches the plugin ID SNMPDSC in docs/PLUGINS.md. Line 113 uses ?h=dbclnp, but the plugin ID there is DBCLNP; update the link to ?h=DBCLNP for consistent casing (and to avoid any case-sensitive matching).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/ADVISORY_MULTI_NETWORK.md` at line 10, The documentation uses
inconsistent casing for plugin query params; update the link in
ADVISORY_MULTI_NETWORK.md that currently uses ?h=dbclnp to use the plugin ID's
uppercase form ?h=DBCLNP so it matches the plugin ID (DBCLNP) used in PLUGINS.md
and aligns with the existing ?h=SNMPDSC link; search for occurrences of
?h=dbclnp and replace them with ?h=DBCLNP to ensure case-sensitive matching.

* **Sync Hub for MSP & Multi-Site Deployments:** Run secondary NetAlertX instances on isolated networks and aggregate data using the **SYNC plugin**. Use the [`SYNC_BEHAVIOR`](https://github.com/netalertx/NetAlertX/tree/main/front/plugins/sync/README.md#hub-device-write-behavior-sync_behavior) setting on the hub to control whether the hub inherits device config from nodes or manages it independently.
* **Manual Entry:** For static assets where only ICMP (ping) status is needed.

Expand Down Expand Up @@ -110,7 +110,7 @@ Don't let a massive device list overwhelm you. Use the [Multi-edit features](./D
As your environment grows, tuning the underlying engine is vital to maintain a snappy UI and reliable discovery cycles.

* **Plugin Scheduling:** Avoid "Scan Storms" by staggering plugin execution. Running intensive tasks like `NMAP` or `MASS_DNS` simultaneously can spike CPU and cause database locks.
* **Database Health:** Large-scale monitoring generates massive event logs. Use the **[DBCLNP (Database Cleanup)](https://www.google.com/search?q=https://docs.netalertx.com/PLUGINS/%23dbclnp)** plugin to prune old records and keep the SQLite database performant.
* **Database Health:** Large-scale monitoring generates massive event logs. Use the **[DBCLNP (Database Cleanup)](https://docs.netalertx.com/PLUGINS/?h=dbclnp#available-plugins)** plugin to prune old records and keep the SQLite database performant.
* **Resource Management:** For high-device counts, consider increasing the memory limit for the container and utilizing `tmpfs` for temporary files to reduce SD card/disk I/O bottlenecks.
* Enable the `DEEP_SLEEP` setting.

Expand Down
13 changes: 6 additions & 7 deletions docs/ADVISORY_MULTI_SITE_MONITORING.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,11 @@ PUSH mode is typically recommended for MSP deployments because remote customer e

The hub can operate in different synchronization ownership modes depending on your operational requirements.

| Mode | Best For |
| -------------- | -------------------------------------------------------------------- |
| `copy-new` | MSP environments where the hub becomes the long-term source of truth |
| `carbon-copy` | Fully managed remote appliances where nodes remain authoritative |
| `hub-defaults` | Centralized inventory management with hub-defined policies |
| Mode | Best For |
| -------------- | --------------------------------------------------------------------- |
| `copy-new` | After initial discovery the hub becomes the long-term source of truth |
| `carbon-copy` | Fully managed remote appliances where nodes remain authoritative |
| `hub-defaults` | Centralized inventory management with hub-defined policies |

This flexibility allows NetAlertX to support both:

Expand Down Expand Up @@ -121,14 +121,13 @@ For best results in multi-site environments:
* Use predefined "Down Devices" dashboards
* Enable Prometheus metrics export
* Use UI Filters to create site-specific views
* Configure notification throttling to reduce alert fatigue

---

# Related Documentation

* [Remote Networks](./REMOTE_NETWORKS.md)
* [Sync Hub Plugin](../front/plugins/sync/README.md)
* [Sync Hub Plugin](https://github.com/netalertx/NetAlertX/tree/main/front/plugins/sync/README.md)
* [Workflows](./WORKFLOWS.md)
* [Metrics API](./API_METRICS.md)
* [Eyes on Glass / NOC Dashboard](./ADVISORY_EYES_ON_GLASS.md)
Expand Down
7 changes: 7 additions & 0 deletions docs/CUSTOM_PROPERTIES.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,22 @@ This functionality allows you to define **custom properties** for devices, which
Visible properties (`CUSTPROP_show: true`) are displayed as interactive icons in the device listing. Each icon can perform one of the following actions based on the `CUSTPROP_type`:

1. **Modals (e.g., Show Notes)**:

- Displays detailed information in a popup modal.
- Example: Firmware version details.

2. **Links**:

- Redirect to an external or internal URL.
- Example: Open a device's documentation or external site.

3. **Device Actions**:

- Manage devices with actions like delete.
- Example: Quickly remove a device from the network.

4. **Plugins**:

- Future placeholder for running custom plugin scripts.
- **Note**: Not implemented yet.

Expand All @@ -41,12 +45,15 @@ Visible properties (`CUSTPROP_show: true`) are displayed as interactive icons in
## Example Use Cases

1. **Device Documentation Link**:

- Add a custom property with `CUSTPROP_type` set to `link` or `link_new_tab` to allow quick navigation to the external documentation of the device.

2. **Firmware Details**:

- Use `CUSTPROP_type: show_notes` to display firmware versions or upgrade instructions in a modal.

3. **Device Removal**:

- Enable device removal functionality using `CUSTPROP_type: delete_dev`.

---
Expand Down
2 changes: 2 additions & 0 deletions docs/DEV_ENV_SETUP.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@ Before starting development, please review the following guidelines.
The application architecture is designed for extensibility and maintainability. It relies heavily on configuration manifests via plugins and settings to dynamically build the UI and populate the application with data from various sources.

For details, see:

- [Plugins Development](PLUGINS_DEV.md) (includes video)
- [Settings System](SETTINGS_SYSTEM.md)

Focus on **core functionality** and integrate with existing tools rather than reinventing the wheel.

Examples:

- Using **Apprise** for notifications instead of implementing multiple separate gateways
- Implementing **regex-based validation** instead of one-off validation for each setting

Expand Down
2 changes: 1 addition & 1 deletion docs/PLUGINS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ NetAlertX supports additional plugins to extend its functionality, each with its
> ![Loaded plugins settings](./img/PLUGINS/enable_plugin.gif)

1. Pick your `🔍 dev scanner` plugin (e.g. `ARPSCAN` or `NMAPDEV`), or import devices into the application with an `📥 importer` plugin. (See **Enabling plugins** below)
2. Pick a `▶️ publisher` plugin, if you want to send notifications. If you don't see a publisher you'd like to use, look at the [📚_publisher_apprise](/front/plugins/_publisher_apprise/) plugin which is a proxy for over 80 notification services.
2. Pick a `▶️ publisher` plugin, if you want to send notifications. If you don't see a publisher you'd like to use, look at the [📚_publisher_apprise](https://docs.netalertx.com/PLUGINS/?h=APPRISE#available-plugins) plugin which is a proxy for over 80 notification services.
3. Setup your [Network topology diagram](./NETWORK_TREE.md)
4. Fine-tune [Notifications](./NOTIFICATIONS.md)
5. Setup [Workflows](./WORKFLOWS.md)
Expand Down
1 change: 1 addition & 0 deletions docs/SUBNETS.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ You need to specify the network interface and the network mask. You can also con
> If the timeout is too short, you may see timeout errors in the log. To prevent the application from hanging due to unresponsive plugins, scans are canceled when they exceed the timeout limit.
>
> To fix this:
>
> - Reduce the subnet size (e.g., change `/16` to `/24`).
> - Increase the timeout (e.g., set `ARPSCAN_RUN_TIMEOUT` to `300` for a 5-minute timeout).
> - Extend the scan interval (e.g., set `ARPSCAN_RUN_SCHD` to `*/10 * * * *` to scan every 10 minutes).
Expand Down
23 changes: 9 additions & 14 deletions front/plugins/icmp_scan/icmp.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
INSTALL_PATH = os.getenv('NETALERTX_APP', '/app')
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])

from plugin_helper import Plugin_Objects # noqa: E402 [flake8 lint suppression]
from plugin_helper import Plugin_Objects, parse_scan_subnets # noqa: E402 [flake8 lint suppression]
from logger import mylog, Logger # noqa: E402 [flake8 lint suppression]
from helper import get_setting_value # noqa: E402 [flake8 lint suppression]
from const import logPath # noqa: E402 [flake8 lint suppression]
Expand All @@ -34,18 +34,6 @@
RESULT_FILE = os.path.join(LOG_PATH, f'last_result.{pluginName}.log')


def parse_scan_subnets(subnets):
"""Extract subnet and interface from SCAN_SUBNETS"""
ranges = []
interfaces = []
for entry in subnets:
parts = entry.split("--interface=")
ranges.append(parts[0].strip())
if len(parts) > 1:
interfaces.append(parts[1].strip())
return ranges, interfaces


def get_device_by_ip(ip, all_devices):
"""Get existing device based on IP"""
for device in all_devices:
Expand All @@ -66,7 +54,14 @@ def main():
fakeMac = get_setting_value('ICMP_FAKE_MAC')
scan_subnets = get_setting_value("SCAN_SUBNETS")

subnets, interfaces = parse_scan_subnets(scan_subnets)
parsed = parse_scan_subnets(scan_subnets)

subnets = [x.subnet for x in parsed]
interfaces = [
x.resolved_interface
for x in parsed
if x.resolved_interface
]

# Initialize the Plugin obj output file
plugin_objects = Plugin_Objects(RESULT_FILE)
Expand Down
35 changes: 18 additions & 17 deletions front/plugins/ipneigh/ipneigh.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
INSTALL_PATH = os.getenv('NETALERTX_APP', '/app')
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])

from plugin_helper import Plugin_Objects # noqa: E402 [flake8 lint suppression]
from plugin_helper import Plugin_Objects, parse_scan_subnets # noqa: E402 [flake8 lint suppression]
from logger import mylog, Logger # noqa: E402 [flake8 lint suppression]
from utils.datetime_utils import timeNowUTC # noqa: E402 [flake8 lint suppression]
from const import logPath # noqa: E402 [flake8 lint suppression]
Expand Down Expand Up @@ -38,14 +38,14 @@ def main():
mylog('verbose', [f'[{pluginName}] In script'])

# Retrieve configuration settings
SCAN_SUBNETS = get_setting_value('SCAN_SUBNETS')
scan_subnets = get_setting_value('SCAN_SUBNETS')

mylog('verbose', [f'[{pluginName}] SCAN_SUBNETS value: {SCAN_SUBNETS}'])
parsed = parse_scan_subnets(scan_subnets)

# Extract interfaces from SCAN_SUBNETS
interfaces = ','.join(
entry.split('--interface=')[-1].strip() for entry in SCAN_SUBNETS if '--interface=' in entry
)
interfaces = []
for entry in parsed:
if entry.resolved_interface and entry.resolved_interface not in interfaces:
interfaces.append(entry.resolved_interface)

mylog('verbose', [f'[{pluginName}] Interfaces value: "{interfaces}"'])

Expand Down Expand Up @@ -117,27 +117,28 @@ def get_neighbors(interfaces):

results = []

for interface in interfaces.split(","):
for interface in interfaces:
try:

# Ping all IPv6 devices in multicast to trigger NDP

mylog('verbose', [f'[{pluginName}] Pinging on interface: "{interface}"'])
command = f"ping ff02::1%{interface} -c 2".split()
subprocess.run(command)
mylog('verbose', [f'[{pluginName}] Pinging completed: "{interface}"'])

# Check the neighbourhood tables

mylog('verbose', [f'[{pluginName}] Scanning interface: "{interface}"'])
mylog('verbose', [f'[{pluginName}] Pinging completed, now scanning interface: "{interface}"'])

command = f"ip neighbor show nud all dev {interface}".split()
output = subprocess.check_output(command, universal_newlines=True)

output = subprocess.check_output(
command,
universal_newlines=True
)

results += output.split("\n")

mylog('verbose', [f'[{pluginName}] Scanning interface succeded: "{interface}"'])

except subprocess.CalledProcessError as e:
# An error occurred, handle it
error_type = type(e).__name__ # Capture the error type
error_type = type(e).__name__
mylog('verbose', [f'[{pluginName}] Scanning interface failed: "{interface}" ({error_type})'])

return results
Expand Down
55 changes: 54 additions & 1 deletion front/plugins/plugin_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,68 @@
import re
import base64
import json
from dataclasses import dataclass

INSTALL_PATH = os.getenv('NETALERTX_APP', '/app')

sys.path.append(f"{INSTALL_PATH}/front/plugins")
sys.path.append(f'{INSTALL_PATH}/server')

from logger import mylog # noqa: E402 [flake8 lint suppression]
from logger import mylog, Logger # noqa: E402 [flake8 lint suppression]
from utils.datetime_utils import timeNowUTC # noqa: E402 [flake8 lint suppression]
from const import default_tz, fullConfPath # noqa: E402 [flake8 lint suppression]
from helper import get_setting_value # noqa: E402 [flake8 lint suppression]

# Make sure log level is initialized correctly
Logger(get_setting_value('LOG_LEVEL'))


# -------------------------------------------------------------------------------
@dataclass
class ScanSubnet:
raw: str
subnet: str
interface: str | None = None
vlan: str | None = None
resolved_interface: str | None = None


# -------------------------------------------------------------------------------
def parse_scan_subnets(scan_subnets):
"""Parse SCAN_SUBNETS entries into structured objects."""

results = []

for entry in scan_subnets:

interface_match = re.search(r'--interface=([^\s]+)', entry)
interface = interface_match.group(1) if interface_match else None

vlan_match = re.search(r'--vlan=(\d+)', entry)
vlan = vlan_match.group(1) if vlan_match else None

resolved_interface = interface

if interface and vlan:
vlan_interface = f"{interface}.{vlan}"

if os.path.exists(f"/sys/class/net/{vlan_interface}"):
resolved_interface = vlan_interface

subnet = re.sub(r'\s+--interface=[^\s]+', '', entry)
subnet = re.sub(r'\s+--vlan=\d+', '', subnet)

results.append(
ScanSubnet(
raw=entry,
subnet=subnet.strip(),
interface=interface,
vlan=vlan,
resolved_interface=resolved_interface,
)
)

return results


# -------------------------------------------------------------------------------
Expand Down
Loading