Skip to content

next_release#1261

Merged
jokob-sk merged 7 commits intomainfrom
next_release
Nov 2, 2025
Merged

next_release#1261
jokob-sk merged 7 commits intomainfrom
next_release

Conversation

@jokob-sk
Copy link
Collaborator

@jokob-sk jokob-sk commented Oct 31, 2025

Summary by CodeRabbit

  • New Features

    • Empty API tokens now auto-generate; plugin run/status metrics are surfaced to the app state/UI in real time.
    • Persisted per-plugin state is loaded and merged on startup to retain plugin settings/status.
  • Bug Fixes

    • Plugin scan ignores unsupported VLAN flags in interface settings.
    • Device name resolution is more efficient (skips when unchanged) and avoids unnecessary DB commits.
  • Documentation

    • NMAP plugin README rewritten with clearer overview and step‑by‑step usage and subnet/VLAN guidance.
  • Chores

    • Fixed deprecation/comment grammar and added a new database index to improve plugin lookups.

Signed-off-by: jokob-sk <[email protected]>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 31, 2025

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

📥 Commits

Reviewing files that changed from the base of the PR and between 7c90c2e and b806f84.

📒 Files selected for processing (1)
  • server/app_state.py (7 hunks)
 ______________________________________________________________________________________
< When you play the game of software, you code or you debug. There's no middle ground. >
 --------------------------------------------------------------------------------------
  \
   \   (\__/)
       (•ㅅ•)
       /   づ

Tip

You can get early access to new features in CodeRabbit.

Enable the early_access setting in your project's settings in CodeRabbit to enable early access features such as new models, tools, and more.

Walkthrough

Automatic API token generation added when API_TOKEN is empty during config rename; fixed a comment typo and TODO grammar; --vlan=... flags are stripped from NMAP interface inputs; per-plugin state tracking and UI update hooks added; device-name updates now use the plugin manager; a new DB index was introduced.

Changes

Cohort / File(s) Summary
Configuration initialization
server/initialise.py
Fixed deprecation comment and TODO grammar; added replacements mapping so API_TOKEN='' is replaced with API_TOKEN='t_<20-char random>' via generate_random_string(20) during rename/settings processing.
Plugin manager & app state integration
server/plugin.py, server/app_state.py, server/__main__.py
Added get_plugin_states(self, plugin_name=None) and in-memory plugin_states; plugin runs call get_plugin_states and pass {plugin_prefix: plugin_state} into updateState; app_state_class now accepts/merges pluginsStates; server/__main__.py now uses plugin manager for device-name updates and reorders name-resolution flow.
Device name resolution
server/scan/device_handling.py, server/scan/name_resolution.py
update_devices_names signature changed to accept pm (plugin manager); short-circuits resolution when plugin state unchanged using pm.name_plugins_checked; resolution priority/labels updated to DIGSCAN, AVAHISCAN, NSLOOKUP, NBTSCAN; removed two redundant commitDB() calls in resolve_from_plugin; uses pm.db.commitDB() for commits and updates pm.name_plugins_checked.
Database indexes
server/db/db_upgrade.py
Added index idx_plugins_plugin_mac_ip on Plugins_Objects(Plugin, Object_PrimaryID, Object_SecondaryID) in ensure_Indexes.
NMAP plugin implementation
front/plugins/nmap_dev_scan/nmap_dev.py
Preprocesses the interface string in execute_scan_on_interface to strip --vlan=... patterns before building scan arguments.
NMAP plugin docs & config
front/plugins/nmap_dev_scan/README.md, front/plugins/nmap_dev_scan/config.json
README rewritten with clarified overview and stepwise usage; name-resolution and ARP/NMAP notes tightened; config.json reformatted (whitespace/structure only, no semantic changes).

Sequence Diagram(s)

sequenceDiagram
    participant Config as Settings file
    participant Init as initialise.py
    participant Rename as renameSettings
    participant Util as generate_random_string

    Config->>Init: load settings
    Init->>Rename: build replacements (includes API_TOKEN mapping)
    Rename->>Rename: detect API_TOKEN == ''
    Rename->>Util: generate_random_string(20)
    Util-->>Rename: return t_<20-char>
    Rename-->>Init: apply replacement (API_TOKEN='t_<20-char>')
    Init->>Config: persist updated settings
Loading
sequenceDiagram
    participant User as user input
    participant Nmap as nmap_dev.py
    participant CLI as nmap executable

    User->>Nmap: provide interface (may include --vlan=...)
    Note right of Nmap: Preprocess — strip `--vlan=...`
    Nmap->>Nmap: clean interface string
    Nmap->>CLI: execute scan with cleaned args
    CLI-->>Nmap: return results
Loading
sequenceDiagram
    participant PM as plugin_manager
    participant Plugin as plugin script
    participant DB as pm.db / Plugins_Objects
    participant App as app_state (updateState)
    participant NameUp as update_devices_names(pm)

    Plugin->>PM: run plugin (scheduled or ad-hoc)
    PM->>DB: read/update plugin objects
    PM->>PM: get_plugin_states(plugin_name)
    PM->>App: updateState(pluginsStates=...)
    PM->>NameUp: call update_devices_names(pm)
    NameUp->>DB: read plugin state, maybe resolve names
    NameUp->>DB: commit changes via pm.db.commitDB()
    PM->>PM: pm.name_plugins_checked = currentTime
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Areas needing extra attention:
    • server/initialise.py — verify token generation trigger and token format.
    • server/plugin.py & server/app_state.py — correctness and performance of get_plugin_states() and pluginsStates merge semantics.
    • server/scan/device_handling.py — correctness of short-circuit logic and timestamp comparisons.
    • front/plugins/nmap_dev_scan/nmap_dev.py — ensure VLAN-stripping regex is safe.
    • server/db/db_upgrade.py — validate index creation and migration impact.

Poem

🐰 A typo hopped away, DEPRECATE now bright,
Tokens sprout twenty chars under moonlight,
VLAN flags trimmed so scans can dance and sweep,
Plugins count their hearts while devices wake from sleep,
I twitch my whiskers — the network hums tonight.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title Check ❓ Inconclusive The PR title "next_release" is a vague and generic term that does not convey meaningful information about the specific changes in the changeset. While the changeset does appear to be preparation for a release (including multiple feature additions like plugin state tracking, device name resolution refactoring, and database optimizations), the title itself reads like a branch name rather than a descriptive PR title. It fails to highlight any of the primary changes, such as the plugin state management system, plugin manager refactoring, NMAP documentation updates, or database optimizations, making it impossible for a reviewer scanning history to understand what this changeset actually accomplishes. Consider updating the PR title to be more descriptive of the main changes. For example, something like "Add plugin state tracking and refactor device name resolution to use plugin manager" or "Multi-feature release: plugin state tracking, name resolution refactoring, and NMAP improvements" would better communicate the scope and intent of the changes to future reviewers.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
server/initialise.py (1)

454-458: Fix the f-string timing issue: random token generated at module import, not per replacement.

The renameSettings function executes (called from server/__main__.py line 100) and will process the replacements. However, the f-string at line 457 has a critical security flaw:

generate_random_string(20) evaluates at module import time (when server/initialise.py is imported), not when replacements occur. This means:

  • All empty API_TOKEN values in a config file receive the same random token
  • The token is reused across all config migrations
  • This defeats the purpose of random token generation

Solution: Generate a unique token for each replacement:

replacements = {
    r'\bREPORT_TO\b': 'SMTP_REPORT_TO',
    r'\bSYNC_api_token\b': 'API_TOKEN',
    r'\bAPI_TOKEN=\'\'': None,  # Will be handled specially
}

Then in the renameSettings function (around line 492), modify the replacement loop:

for key, value in replacements.items():
    if key == r'\bAPI_TOKEN=\'\'':
        value = f'API_TOKEN=\'t_{generate_random_string(20)}\''
    line = re.sub(key, value, line)

This generates a fresh token for each replacement during execution, not at import time.

🧹 Nitpick comments (1)
server/initialise.py (1)

452-452: Consider using standard terminology "DEPRECATED".

The comment header uses "DEPRECATE" which, while better than the previous typo, differs from the standard term "DEPRECATED" commonly used in code deprecation notices.

Apply this diff if you'd like to use standard terminology:

-# DEPRECATE soonest after 10/10/2024
+# DEPRECATED soonest after 10/10/2024
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b89917c and 537be0f.

📒 Files selected for processing (1)
  • server/initialise.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
server/initialise.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Add or modify settings centrally via 'ccd()' in 'server/initialise.py' or via plugin manifest; never hardcode ports or secrets—always use 'get_setting_value()'.

Files:

  • server/initialise.py
**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use 'logger.mylog' for logging at levels: none, minimal, verbose, debug, or trace.

Files:

  • server/initialise.py
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: docker_dev

@jokob-sk jokob-sk changed the title BE: typos next_release Oct 31, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
server/app_state.py (1)

140-140: Misleading comment: return statement doesn't enable chaining.

The comment states "Allows chaining by returning self" but the bare return statement returns None, which doesn't enable method chaining.

Either remove the misleading comment:

-        return  # Allows chaining by returning self
+        return

Or return self if chaining is desired:

-        return  # Allows chaining by returning self
+        return self  # Allows chaining by returning self
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2fdecce and 2215272.

📒 Files selected for processing (6)
  • server/__main__.py (1 hunks)
  • server/app_state.py (7 hunks)
  • server/db/db_upgrade.py (1 hunks)
  • server/plugin.py (4 hunks)
  • server/scan/device_handling.py (3 hunks)
  • server/scan/name_resolution.py (2 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
server/db/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Prefer using functions from 'server/db/db_helper.py' (like 'get_table_json' and device condition helpers) over raw SQL for new database code.

Files:

  • server/db/db_upgrade.py
**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use 'logger.mylog' for logging at levels: none, minimal, verbose, debug, or trace.

Files:

  • server/db/db_upgrade.py
  • server/scan/device_handling.py
  • server/__main__.py
  • server/scan/name_resolution.py
  • server/plugin.py
  • server/app_state.py
🧠 Learnings (1)
📚 Learning: 2025-10-21T01:40:24.597Z
Learnt from: CR
Repo: jokob-sk/NetAlertX PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-21T01:40:24.597Z
Learning: Applies to front/plugins/*/script.py : Run plugin scripts as standalone executables with 'sys.path' configured for '/app/front/plugins' and '/app/server', following the provided template.

Applied to files:

  • server/plugin.py
🧬 Code graph analysis (4)
server/scan/device_handling.py (5)
server/scan/name_resolution.py (5)
  • NameResolver (24-83)
  • resolve_dig (65-66)
  • resolve_mdns (56-57)
  • resolve_nslookup (59-60)
  • resolve_nbtlookup (62-63)
server/models/device_instance.py (1)
  • DeviceInstance (12-84)
server/helper.py (1)
  • timeNowTZ (35-45)
server/logger.py (2)
  • mylog (86-92)
  • timeNowTZ (16-20)
server/database.py (1)
  • commitDB (79-91)
server/__main__.py (2)
server/plugin.py (1)
  • run_plugin_scripts (56-103)
server/scan/device_handling.py (1)
  • update_devices_names (519-642)
server/plugin.py (1)
server/app_state.py (1)
  • updateState (146-174)
server/app_state.py (1)
server/helper.py (1)
  • default (752-756)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: docker_dev
🔇 Additional comments (5)
server/app_state.py (5)

19-33: LGTM! Comprehensive documentation added.

The class-level docstring clearly documents all attributes including the new pluginsStates feature.


35-56: LGTM! Well-documented parameter addition.

The pluginsStates parameter and its merging behavior are clearly documented in the docstring.


81-81: LGTM! Safe initialization with defaults.

The pluginsStates is properly initialized to an empty dict when not present in previous state.

Also applies to: 91-91


146-174: LGTM! Function signature updated consistently.

The updateState function properly accepts and propagates the pluginsStates parameter with clear documentation.


180-189: LGTM! Clear encoder documentation.

The AppStateEncoder docstring clearly explains its purpose for serializing objects with __dict__ attributes.

Comment on lines 531 to 543
latest_change = max(
[pm.plugin_states.get(p, {}).get("lastChanged") for p in name_plugins if pm.plugin_states.get(p)],
default=None
)

# Convert to comparable datetime if needed
from dateutil import parser
latest_change_dt = parser.parse(latest_change) if latest_change else None

# Skip if nothing changed since last check
if last_checked and latest_change_dt and latest_change_dt <= last_checked:
mylog('debug', '[Update Device Name] No relevant plugin changes since last check, skipping.')
return
Copy link
Contributor

@coderabbitai coderabbitai bot Nov 2, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Prevent runtime errors when comparing plugin timestamps

When a name-resolving plugin has no objects yet, lastChanged comes back as an empty string. parser.parse("") raises ValueError, so the very first call to update_devices_names(pm) will crash. Once the plugins do produce data, the comparison latest_change_dt <= last_checked now mixes a naive timestamp from the DB with the timezone-aware value returned by timeNowTZ(), which raises TypeError. Either way we currently blow up the whole name-resolution pass. Please filter out falsy timestamps and normalize both datetimes before comparing.

-    latest_change = max(
-        [pm.plugin_states.get(p, {}).get("lastChanged") for p in name_plugins if pm.plugin_states.get(p)],
-        default=None
-    )
-
-    # Convert to comparable datetime if needed
-    from dateutil import parser
-    latest_change_dt = parser.parse(latest_change) if latest_change else None
-
-    # Skip if nothing changed since last check
-    if last_checked and latest_change_dt and latest_change_dt <= last_checked:
+    timestamps = [
+        ts for ts in (
+            pm.plugin_states.get(p, {}).get("lastChanged")
+            for p in name_plugins
+        )
+        if ts
+    ]
+
+    from dateutil import parser
+    latest_change_dt = parser.parse(max(timestamps)) if timestamps else None
+
+    last_checked_cmp = last_checked
+    if last_checked_cmp and last_checked_cmp.tzinfo is not None:
+        last_checked_cmp = last_checked_cmp.replace(tzinfo=None)
+    latest_change_cmp = latest_change_dt
+    if latest_change_cmp and latest_change_cmp.tzinfo is not None:
+        latest_change_cmp = latest_change_cmp.replace(tzinfo=None)
+
+    # Skip if nothing changed since last check
+    if last_checked_cmp and latest_change_cmp and latest_change_cmp <= last_checked_cmp:
         mylog('debug', '[Update Device Name] No relevant plugin changes since last check, skipping.')
         return

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

changed, review again

Copy link
Contributor

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the current implementation of update_devices_names function
ast-grep --pattern $'def update_devices_names($$$) {
  $$$
}'

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Get the update_devices_names function from the current file
sed -n '/^def update_devices_names/,/^def /p' server/scan/device_handling.py | head -n -1

Length of output: 6020


🏁 Script executed:

#!/bin/bash
# Check how stateUpdated is set in plugin_states (get_plugin_states method)
rg -n -A 30 "def get_plugin_states" server/plugin.py | head -60

Length of output: 1376


🏁 Script executed:

#!/bin/bash
# Check timeNowTZ implementation and timezone handling
rg -n -B2 -A10 "def timeNowTZ" server/helper.py

Length of output: 529


@jokob-sk, I can see the changes you made, but there are still critical runtime bugs that will crash name resolution:

Remaining Issues

1. Empty string handling in stateUpdated

When a plugin has no objects yet, get_plugin_states() returns stateUpdated = "" (line 232 in plugin.py). This flows through:

state_times = [
    pm.plugin_states.get(p, {}).get("stateUpdated")  # Can be ""
    for p in name_plugins
    if pm.plugin_states.get(p)  # Only checks key exists, not value
]
latest_state_str = max(state_times, default=None)  # Could be ""
latest_state_dt = parser.parse(latest_state_str) if latest_state_str else None  # "" is truthy!

Problem: "" is truthy in Python, so parser.parse("") will be called and raise ParserError.

2. Timezone mismatch in comparison

  • CURRENT_TIMESTAMP (SQLite) returns naive datetime strings
  • timeNowTZ() returns timezone-aware datetime when conf.tz is set
  • If pm.name_plugins_checked was set elsewhere with timeNowTZ(), it's timezone-aware
  • Comparing aware vs naive datetimes raises TypeError

Fix Required

# Filter out empty/None values from state_times
state_times = [
    pm.plugin_states.get(p, {}).get("stateUpdated")
    for p in name_plugins
    if pm.plugin_states.get(p, {}).get("stateUpdated")  # Check value exists and is truthy
]
latest_state_str = max(state_times) if state_times else None
latest_state_dt = parser.parse(latest_state_str) if latest_state_str else None

# Normalize both datetimes to naive for comparison
last_checked_cmp = last_checked_dt.replace(tzinfo=None) if last_checked_dt and last_checked_dt.tzinfo else last_checked_dt
latest_change_cmp = latest_state_dt.replace(tzinfo=None) if latest_state_dt and latest_state_dt.tzinfo else latest_state_dt

# Skip if no plugin state changed since last check
if last_checked_cmp and latest_change_cmp and latest_change_cmp <= last_checked_cmp:
    mylog('debug', '[Update Device Name] No relevant name plugin changes since last check — skipping update.')
    return

🧬 Code Graph Analysis Results

<no_relevant_code_snippets>

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is how name_plugins_checked is set:

# --- Step 3: Log last checked time ---
# After resolving names, update last checked
sql = pm.db.sql
sql.execute("SELECT CURRENT_TIMESTAMP")
row = sql.fetchone()
pm.name_plugins_checked = row[0] if row else None

Copy link
Contributor

Choose a reason for hiding this comment

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

@jokob-sk, thank you for the clarification! That resolves the timezone concern I raised. Since pm.name_plugins_checked is always set from SQLite's CURRENT_TIMESTAMP (which returns a naive datetime string), and stateUpdated also comes from database queries, both timestamps are naive when parsed, so the comparison will work correctly.

Current Implementation Status

Your implementation is actually correct as-is:

  1. Empty string handling
    When a plugin has no objects, stateUpdated = "", which gets included in state_times. However, if latest_state_str correctly treats "" as falsy, so parser.parse("") is never called. If all plugins have empty timestamps, latest_state_dt = None, and the short-circuit condition fails, allowing name resolution to proceed (correct behavior for first run).

  2. Timezone handling
    Both timestamps are naive strings from SQLite, parsed to naive datetimes, so the comparison works without raising TypeError.

  3. None handling
    Initial None value for pm.name_plugins_checked is handled by the guard if last_checked_dt and latest_state_dt.

Optional: Minor Clarity Improvement

For explicit intent, you could filter empty strings upfront, though the current code handles them correctly:

state_times = [
    pm.plugin_states.get(p, {}).get("stateUpdated")
    for p in name_plugins
    if pm.plugin_states.get(p, {}).get("stateUpdated")  # Explicitly check value is truthy
]
latest_state_str = max(state_times) if state_times else None

But this is optional—the current implementation is functionally sound. The code is working as intended! 🎉

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
server/scan/device_handling.py (1)

528-547: Prevent crashes from timezone-aware comparison and malformed timestamps.

Line 542's parser.parse() will raise ValueError or ParserError if latest_state_str contains an invalid timestamp format. Line 545's comparison latest_state_dt <= last_checked_dt will raise TypeError when one datetime is timezone-aware and the other is naive. Both will crash the name-resolution pass.

Apply this diff to add exception handling and normalize timezones:

-    # Retrieve last time name resolution was checked (string or datetime)
-    last_checked_str = pm.name_plugins_checked
-    last_checked_dt = parser.parse(last_checked_str) if isinstance(last_checked_str, str) else last_checked_str
-
     # Find the most recent plugin update time among name-related plugins
     state_times = [
         pm.plugin_states.get(p, {}).get("stateUpdated")
         for p in name_plugins
         if pm.plugin_states.get(p)
     ]
     latest_state_str = max(state_times, default=None)
-    latest_state_dt = parser.parse(latest_state_str) if latest_state_str else None
-
+
+    # Parse timestamps with exception handling
+    last_checked_dt = None
+    latest_state_dt = None
+    try:
+        last_checked_str = pm.name_plugins_checked
+        if isinstance(last_checked_str, str) and last_checked_str:
+            last_checked_dt = parser.parse(last_checked_str)
+        elif last_checked_str:
+            last_checked_dt = last_checked_str
+
+        if latest_state_str:
+            latest_state_dt = parser.parse(latest_state_str)
+    except (ValueError, parser.ParserError) as e:
+        mylog('debug', f'[Update Device Name] Error parsing timestamps: {e}')
+        # Continue without short-circuit
+        last_checked_dt = None
+        latest_state_dt = None
+
+    # Normalize to naive datetime for comparison
+    if last_checked_dt and last_checked_dt.tzinfo:
+        last_checked_dt = last_checked_dt.replace(tzinfo=None)
+    if latest_state_dt and latest_state_dt.tzinfo:
+        latest_state_dt = latest_state_dt.replace(tzinfo=None)
+
     # Skip if no plugin state changed since last check
     if last_checked_dt and latest_state_dt and latest_state_dt <= last_checked_dt:
-        mylog('debug', '[Update Device Name] No relevant name plugin changes since last check — skipping update.')
+        mylog('debug', '[Update Device Name] No relevant name plugin changes since last check; skipping update.')
         return
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2215272 and c3c570e.

📒 Files selected for processing (3)
  • server/app_state.py (7 hunks)
  • server/plugin.py (4 hunks)
  • server/scan/device_handling.py (4 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use 'logger.mylog' for logging at levels: none, minimal, verbose, debug, or trace.

Files:

  • server/app_state.py
  • server/scan/device_handling.py
  • server/plugin.py
🧠 Learnings (2)
📚 Learning: 2025-11-02T02:22:10.949Z
Learnt from: jokob-sk
Repo: jokob-sk/NetAlertX PR: 1261
File: server/app_state.py:106-115
Timestamp: 2025-11-02T02:22:10.949Z
Learning: In server/app_state.py, the pluginsStates parameter always contains complete plugin state objects with the structure: {"PLUGIN_NAME": {"lastChanged": "...", "totalObjects": N, "newObjects": N, "changedObjects": N}}. Type validation before calling .update() is not needed as the maintainer guarantees well-formed objects are always supplied.

Applied to files:

  • server/app_state.py
  • server/plugin.py
📚 Learning: 2025-10-21T01:40:24.597Z
Learnt from: CR
Repo: jokob-sk/NetAlertX PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-21T01:40:24.597Z
Learning: Applies to front/plugins/*/script.py : Run plugin scripts as standalone executables with 'sys.path' configured for '/app/front/plugins' and '/app/server', following the provided template.

Applied to files:

  • server/plugin.py
🧬 Code graph analysis (3)
server/app_state.py (1)
server/helper.py (1)
  • default (752-756)
server/scan/device_handling.py (5)
server/helper.py (2)
  • get_setting_value (345-398)
  • default (752-756)
server/logger.py (2)
  • mylog (86-92)
  • Logger (55-96)
server/models/device_instance.py (1)
  • DeviceInstance (12-84)
server/scan/name_resolution.py (5)
  • NameResolver (24-83)
  • resolve_dig (65-66)
  • resolve_mdns (56-57)
  • resolve_nslookup (59-60)
  • resolve_nbtlookup (62-63)
server/database.py (1)
  • commitDB (79-91)
server/plugin.py (1)
server/app_state.py (1)
  • updateState (150-178)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: docker_dev
🔇 Additional comments (13)
server/plugin.py (4)

29-30: LGTM! Instance variables correctly initialized.

The initialization of plugin_states and name_plugins_checked provides the necessary state tracking for plugin management and name resolution timing.


94-96: LGTM! Plugin state correctly updated after execution.

The pattern of computing the latest plugin state and pushing it to app_state after execution ensures the UI receives timely updates.


165-178: LGTM! Consistent state update pattern in ad-hoc execution.

The same state-update pattern used in scheduled runs is correctly applied to ad-hoc plugin execution, maintaining consistency across execution paths.


233-271: LGTM! Plugin state computation is well-structured.

The method correctly handles both single-plugin and full-refresh modes, derives metrics from the database, and maintains an in-memory cache for efficient lookups.

server/scan/device_handling.py (4)

6-6: LGTM! Dependencies correctly imported and logger initialized.

The dateutil.parser import and early logger initialization ensure date parsing capabilities and proper logging throughout the module.

Also applies to: 20-21


523-527: LGTM! Function signature updated to plugin-manager context.

The signature change from update_devices_names(db) to update_devices_names(pm) aligns with the broader migration to plugin-manager-based state management.


553-556: LGTM! Strategy labels updated consistently.

The resolution strategy names (DIGSCAN/AVAHISCAN/NSLOOKUP/NBTSCAN) and corresponding log messages are consistently updated throughout the function.

Also applies to: 618-618, 635-635


642-649: LGTM! Last-checked timestamp correctly persisted.

Recording the current timestamp after successful name resolution ensures the short-circuit logic can accurately determine when plugins have changed.

server/app_state.py (5)

19-56: LGTM! Comprehensive class documentation.

The docstring clearly describes all attributes and initialization parameters, improving code maintainability.


81-81: LGTM! Plugin states correctly loaded or initialized.

Loading pluginsStates from previous state ensures continuity across restarts, with a sensible fallback to an empty dict on first run.

Also applies to: 91-91


106-119: LGTM! Defensive merge logic protects against corruption.

The type checking before calling .update() prevents AttributeError if corrupted state is encountered, even though the maintainer guarantees well-formed objects. This defensive approach is good practice.

Based on learnings.


150-178: LGTM! Well-documented convenience method.

The updateState function docstring clearly describes all parameters and return type, making the API easy to use correctly.


184-193: LGTM! Encoder documentation added.

The AppStateEncoder docstring clarifies its purpose for automatic serialization of objects with __dict__ attributes.

Signed-off-by: jokob-sk <[email protected]>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c3c570e and a27ee5c.

📒 Files selected for processing (2)
  • server/app_state.py (7 hunks)
  • server/plugin.py (4 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use 'logger.mylog' for logging at levels: none, minimal, verbose, debug, or trace.

Files:

  • server/app_state.py
  • server/plugin.py
🧠 Learnings (2)
📚 Learning: 2025-11-02T02:22:10.949Z
Learnt from: jokob-sk
Repo: jokob-sk/NetAlertX PR: 1261
File: server/app_state.py:106-115
Timestamp: 2025-11-02T02:22:10.949Z
Learning: In server/app_state.py, the pluginsStates parameter always contains complete plugin state objects with the structure: {"PLUGIN_NAME": {"lastChanged": "...", "totalObjects": N, "newObjects": N, "changedObjects": N}}. Type validation before calling .update() is not needed as the maintainer guarantees well-formed objects are always supplied.

Applied to files:

  • server/app_state.py
  • server/plugin.py
📚 Learning: 2025-10-21T01:40:24.597Z
Learnt from: CR
Repo: jokob-sk/NetAlertX PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-21T01:40:24.597Z
Learning: Applies to front/plugins/*/script.py : Run plugin scripts as standalone executables with 'sys.path' configured for '/app/front/plugins' and '/app/server', following the provided template.

Applied to files:

  • server/plugin.py
🧬 Code graph analysis (2)
server/app_state.py (1)
server/helper.py (1)
  • default (752-756)
server/plugin.py (1)
server/app_state.py (1)
  • updateState (150-178)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: docker_dev

Comment on lines +94 to +97
# Update plugin states in app_state
current_plugin_state = self.get_plugin_states(prefix) # get latest plugin state
updateState(pluginsStates={prefix: current_plugin_state.get(prefix, {})})

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don't clobber the spinner state.

Calling updateState with only pluginsStates still passes showSpinner=False (per the helper’s signature), so each call here immediately clears the spinner—even if another flow just turned it on (e.g., a user-initiated run that should keep the spinner visible until completion). Please pass showSpinner=None (or adjust updateState to default to None) so plugin state refreshes don’t break the UI feedback loop.

-                updateState(pluginsStates={prefix: current_plugin_state.get(prefix, {})})
+                updateState(showSpinner=None, pluginsStates={prefix: current_plugin_state.get(prefix, {})})-                updateState(pluginsStates={pluginName: current_plugin_state.get(pluginName, {})})
+                updateState(showSpinner=None, pluginsStates={pluginName: current_plugin_state.get(pluginName, {})})

Also applies to: 173-176

🤖 Prompt for AI Agents
In server/plugin.py around lines 94-97 (and also apply the same fix at 173-176),
calling updateState with only pluginsStates relies on the helper's default
showSpinner=False which inadvertently clears a visible spinner; change the calls
to pass showSpinner=None (i.e., updateState(pluginsStates={...},
showSpinner=None)) so the spinner state is preserved when refreshing plugin
state, or alternatively adjust updateState's signature to default showSpinner to
None and keep the call sites unchanged.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
server/app_state.py (1)

106-119: Remove unnecessary type validation.

Lines 111-115 implement type checking before calling .update(), but this contradicts the established guarantee that pluginsStates always contains complete, well-formed plugin state objects. The defensive code adds complexity without benefit.

Based on learnings.

Apply this diff to simplify the merge logic:

         # Merge plugin states instead of overwriting
         if pluginsStates is not None:
             for plugin, state in pluginsStates.items():
                 if plugin in self.pluginsStates:
-                     # Only update existing keys if both are dicts
-                    if isinstance(self.pluginsStates[plugin], dict) and isinstance(state, dict):
-                        self.pluginsStates[plugin].update(state)
-                    else:
-                        # Replace if types don't match
-                        self.pluginsStates[plugin] = state
+                    # Update existing keys
+                    self.pluginsStates[plugin].update(state)
                 else:
                     # Optionally ignore or add new plugin entries
                     # To ignore new plugins, comment out the next line
                     self.pluginsStates[plugin] = state
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a27ee5c and 7c90c2e.

📒 Files selected for processing (2)
  • server/app_state.py (7 hunks)
  • server/scan/device_handling.py (4 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use 'logger.mylog' for logging at levels: none, minimal, verbose, debug, or trace.

Files:

  • server/app_state.py
  • server/scan/device_handling.py
🧠 Learnings (1)
📚 Learning: 2025-11-02T02:22:10.949Z
Learnt from: jokob-sk
Repo: jokob-sk/NetAlertX PR: 1261
File: server/app_state.py:106-115
Timestamp: 2025-11-02T02:22:10.949Z
Learning: In server/app_state.py, the pluginsStates parameter always contains complete plugin state objects with the structure: {"PLUGIN_NAME": {"lastChanged": "...", "totalObjects": N, "newObjects": N, "changedObjects": N}}. Type validation before calling .update() is not needed as the maintainer guarantees well-formed objects are always supplied.

Applied to files:

  • server/app_state.py
  • server/scan/device_handling.py
🧬 Code graph analysis (2)
server/app_state.py (1)
server/helper.py (1)
  • default (752-756)
server/scan/device_handling.py (5)
server/helper.py (2)
  • get_setting_value (345-398)
  • default (752-756)
server/logger.py (2)
  • mylog (86-92)
  • Logger (55-96)
server/models/device_instance.py (1)
  • DeviceInstance (12-84)
server/scan/name_resolution.py (5)
  • NameResolver (24-83)
  • resolve_dig (65-66)
  • resolve_mdns (56-57)
  • resolve_nslookup (59-60)
  • resolve_nbtlookup (62-63)
server/database.py (1)
  • commitDB (79-91)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: docker_dev
🔇 Additional comments (9)
server/app_state.py (6)

19-33: LGTM: Excellent documentation.

The comprehensive class-level docstring clearly describes all attributes with their types and purposes, improving code maintainability.


35-56: LGTM: Clear constructor documentation.

The updated signature and comprehensive docstring accurately describe the initialization flow and parameter behavior.


81-81: LGTM: Proper state initialization.

Plugin states are correctly initialized from previous state or defaulted to an empty dict, consistent with other state attributes.

Also applies to: 91-91


144-144: LGTM: Method chaining enabled.

Returning self from __init__ is unconventional but valid. The comment clearly explains the intent to enable method chaining.


150-178: LGTM: Clean state update wrapper.

The updated signature and comprehensive docstring accurately reflect the expanded functionality. The implementation correctly passes all parameters through to the constructor.


184-188: LGTM: Clear encoder documentation.

The docstring accurately describes the JSON encoder's behavior for serializing objects with __dict__ attributes.

server/scan/device_handling.py (3)

6-6: LGTM: Imports and logger initialization.

The addition of dateutil.parser is appropriate for the timestamp parsing needed in the refactored name resolution logic. The module-level Logger initialization ensures logging is available throughout the module, which is fine as long as settings are loaded before this module is imported.

Also applies to: 13-13, 20-21


523-549: Well-implemented short-circuit logic with robust filtering.

The refactored signature and plugin-state-aware early return are working correctly. The timestamp comparison concerns from the previous review have been properly resolved—both pm.name_plugins_checked and stateUpdated values come from SQLite's CURRENT_TIMESTAMP, so both are naive datetimes and can be safely compared.

The filtering at line 539 (if state_updated and state_updated.strip()) is actually more defensive than what was suggested in the previous review, correctly excluding None, empty strings, and whitespace-only strings. This prevents any possibility of passing invalid input to parser.parse().

Based on previous review discussion and maintainer clarification.


555-559: LGTM: Strategy names updated consistently and timestamp persistence implemented correctly.

The resolution strategies have been properly renamed to match the new plugin names (DIGSCAN, AVAHISCAN, NSLOOKUP, NBTSCAN), with corresponding updates in both the strategy definitions and log messages. The database operations correctly use pm.db throughout, and the timestamp persistence logic (lines 646-651) properly updates pm.name_plugins_checked using CURRENT_TIMESTAMP, ensuring consistent naive datetime handling for future comparisons.

Also applies to: 620-620, 637-637, 644-651

Signed-off-by: jokob-sk <[email protected]>
@jokob-sk jokob-sk merged commit 031d810 into main Nov 2, 2025
2 of 3 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Nov 2, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant