Files
NetAlertX/search/search_index.json

1 line
355 KiB
JSON

{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"NetAlertX Documentation","text":"<p>Welcome to the official NetAlertX documentation! NetAlertX is a powerful tool designed to simplify the management and monitoring of your network. Below, you will find guides and resources to help you set up, configure, and troubleshoot your NetAlertX instance.</p> <p></p>"},{"location":"#in-app-help","title":"In-App Help","text":"<p>NetAlertX provides contextual help within the application:</p> <ul> <li>Hover over settings, fields, or labels to see additional tooltips and guidance.</li> <li>Click \u2754 (question-mark) icons next to various elements to view detailed information.</li> </ul>"},{"location":"#installation-guides","title":"Installation Guides","text":"<p>The app can be installed different ways, with the best support of the docker-based deployments. This includes the Home Assistant and Unraid installation approaches. See details below. </p>"},{"location":"#docker-fully-supported","title":"Docker (Fully Supported)","text":"<p>NetAlertX is fully supported in Docker environments, allowing for easy setup and configuration. Follow the official guide to get started:</p> <ul> <li>Docker Installation Guide</li> </ul> <p>This guide will take you through the process of setting up NetAlertX using Docker Compose or standalone Docker commands.</p>"},{"location":"#home-assistant-fully-supported","title":"Home Assistant (Fully Supported)","text":"<p>You can install NetAlertX also as a Home Assistant addon via the alexbelgium/hassio-addons repository. This is only possible if you run a supervised instance of Home Assistant. If not, you can still run NetAlertX in a separate Docker container and follow this guide to configure MQTT.</p> <ul> <li>[Installation] Home Assistant </li> </ul>"},{"location":"#unraid-partial-support","title":"Unraid (Partial Support)","text":"<p>The Unraid template was created by the community, so it's only partially supported. Alternatively, here is another version of the Unraid template. </p> <ul> <li>[Installation] Unraid App </li> </ul>"},{"location":"#bare-metal-installation-experimental","title":"Bare-Metal Installation (Experimental)","text":"<p>If you prefer to run NetAlertX on your own hardware, you can try the experimental bare-metal installation. Please note that this method is still under development, and are looking for maintainers to help improve it.</p> <ul> <li>Bare-Metal Installation Guide</li> </ul>"},{"location":"#help-and-support","title":"Help and Support","text":"<p>If you need help or run into issues, here are some resources to guide you:</p> <p>Before opening an issue, please:</p> <ul> <li>Check common issues to see if your problem has already been reported.</li> <li>Look at closed issues for possible solutions to past problems.</li> <li>Enable debugging to gather more information: Debug Guide.</li> </ul> <p>Need more help? Join the community discussions or submit a support request:</p> <ul> <li>Visit the GitHub Discussions for community support.</li> <li>If you are experiencing issues that require immediate attention, consider opening an issue on our GitHub Issues page.</li> </ul>"},{"location":"#contributing","title":"Contributing","text":"<p>NetAlertX is open-source and welcomes contributions from the community! If you'd like to help improve the software, please follow the guidelines below:</p> <ul> <li>Fork the repository and make your changes.</li> <li>Submit a pull request with a detailed description of what you\u2019ve changed and why.</li> </ul> <p>For more information on contributing, check out our Dev Guide.</p>"},{"location":"#stay-updated","title":"Stay Updated","text":"<p>To keep up with the latest changes and updates to NetAlertX, please refer to the following resources:</p> <ul> <li>Releases</li> </ul> <p>Make sure to follow the project on GitHub to get notifications for new releases and important updates.</p>"},{"location":"#additional-info","title":"Additional info","text":"<ul> <li>Documentation Index: Check out the full documentation index for all the guides available.</li> </ul> <p>If you have any suggestions or improvements, please don\u2019t hesitate to contribute!</p> <p>NetAlertX is actively maintained. You can find the source code, report bugs, or request new features on our GitHub page.</p>"},{"location":"API/","title":"NetAlertX API Documentation","text":"<p>This API provides programmatic access to devices, events, sessions, metrics, network tools, and sync in NetAlertX. It is implemented as a REST and GraphQL server. All requests require authentication via API Token (<code>API_TOKEN</code> setting) unless explicitly noted. For example, to authorize a GraphQL request, you need to use a <code>Authorization: Bearer API_TOKEN</code> header as per example below:</p> <pre><code>curl 'http://host:GRAPHQL_PORT/graphql' \\\n -X POST \\\n -H 'Authorization: Bearer API_TOKEN' \\\n -H 'Content-Type: application/json' \\\n --data '{\n \"query\": \"query GetDevices($options: PageQueryOptionsInput) { devices(options: $options) { devices { rowid devMac devName devOwner devType devVendor devLastConnection devStatus } count } }\",\n \"variables\": {\n \"options\": {\n \"page\": 1,\n \"limit\": 10,\n \"sort\": [{ \"field\": \"devName\", \"order\": \"asc\" }],\n \"search\": \"\",\n \"status\": \"connected\"\n }\n }\n }'\n</code></pre> <p>The API server runs on <code>0.0.0.0:&lt;graphql_port&gt;</code> with CORS enabled for all main endpoints.</p>"},{"location":"API/#authentication","title":"Authentication","text":"<p>All endpoints require an API token provided in the HTTP headers:</p> <pre><code>Authorization: Bearer &lt;API_TOKEN&gt;\n</code></pre> <p>If the token is missing or invalid, the server will return:</p> <pre><code>{ \"error\": \"Forbidden\" }\n</code></pre>"},{"location":"API/#base-url","title":"Base URL","text":"<pre><code>http://&lt;server&gt;:&lt;GRAPHQL_PORT&gt;/\n</code></pre>"},{"location":"API/#endpoints","title":"Endpoints","text":"<p>Tip</p> <p>When retrieving devices or settings try using the GraphQL API endpoint first as it is read-optimized.</p> <ul> <li>Device API Endpoints \u2013 Manage individual devices</li> <li>Devices Collection \u2013 Bulk operations on multiple devices</li> <li>Events \u2013 Device event logging and management</li> <li>Sessions \u2013 Connection sessions and history</li> <li>Settings \u2013 Settings</li> <li>Messaging:</li> <li>In app messaging - In-app messaging</li> <li>Metrics \u2013 Prometheus metrics and per-device status</li> <li>Network Tools \u2013 Utilities like Wake-on-LAN, traceroute, nslookup, nmap, and internet info</li> <li>Online History \u2013 Online/offline device records</li> <li>GraphQL \u2013 Advanced queries and filtering</li> <li>Sync \u2013 Synchronization between multiple NetAlertX instances</li> <li>DB query (\u26a0 Internal) - Low level database access - use other endpoints if possible</li> </ul> <p>See Testing for example requests and usage.</p>"},{"location":"API/#notes","title":"Notes","text":"<ul> <li>All endpoints enforce Bearer token authentication.</li> <li>Errors return JSON with <code>success: False</code> and an error message.</li> <li>GraphQL is available for advanced queries, while REST endpoints cover structured use cases.</li> <li>Endpoints run on <code>0.0.0.0:&lt;GRAPHQL_PORT&gt;</code> with CORS enabled.</li> <li>Use consistent API tokens and node/plugin names when interacting with <code>/sync</code> to ensure data integrity.</li> </ul>"},{"location":"API_DBQUERY/","title":"Database Query API","text":"<p>The Database Query API provides direct, low-level access to the NetAlertX database. It allows read, write, update, and delete operations against tables, using base64-encoded SQL or structured parameters.</p> <p>Warning</p> <p>This API is primarily used internally to generate and render the application UI. These endpoints are low-level and powerful, and should be used with caution. Wherever possible, prefer the standard API endpoints. Invalid or unsafe queries can corrupt data. If you need data in a specific format that is not already provided, please open an issue or pull request with a clear, broadly useful use case. This helps ensure new endpoints benefit the wider community rather than relying on raw database queries.</p>"},{"location":"API_DBQUERY/#authentication","title":"Authentication","text":"<p>All <code>/dbquery/*</code> endpoints require an API token in the HTTP headers:</p> <pre><code>Authorization: Bearer &lt;API_TOKEN&gt;\n</code></pre> <p>If the token is missing or invalid:</p> <pre><code>{ \"error\": \"Forbidden\" }\n</code></pre>"},{"location":"API_DBQUERY/#endpoints","title":"Endpoints","text":""},{"location":"API_DBQUERY/#1-post-dbqueryread","title":"1. <code>POST /dbquery/read</code>","text":"<p>Execute a read-only SQL query (e.g., <code>SELECT</code>).</p>"},{"location":"API_DBQUERY/#request-body","title":"Request Body","text":"<pre><code>{\n \"rawSql\": \"U0VMRUNUICogRlJPTSBERVZJQ0VT\" // base64 encoded SQL\n}\n</code></pre> <p>Decoded SQL:</p> <pre><code>SELECT * FROM Devices;\n</code></pre>"},{"location":"API_DBQUERY/#response","title":"Response","text":"<pre><code>{\n \"success\": true,\n \"results\": [\n { \"devMac\": \"AA:BB:CC:DD:EE:FF\", \"devName\": \"Phone\" }\n ]\n}\n</code></pre>"},{"location":"API_DBQUERY/#curl-example","title":"<code>curl</code> Example","text":"<pre><code>curl -X POST \"http://&lt;server_ip&gt;:&lt;GRAPHQL_PORT&gt;/dbquery/read\" \\\n -H \"Authorization: Bearer &lt;API_TOKEN&gt;\" \\\n -H \"Accept: application/json\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"rawSql\": \"U0VMRUNUICogRlJPTSBERVZJQ0VT\"\n }'\n</code></pre>"},{"location":"API_DBQUERY/#2-post-dbqueryupdate-safer-than-dbquerywrite","title":"2. <code>POST /dbquery/update</code> (safer than <code>/dbquery/write</code>)","text":"<p>Update rows in a table by <code>columnName</code> + <code>id</code>. <code>/dbquery/update</code> is parameterized to reduce the risk of SQL injection, while <code>/dbquery/write</code> executes raw SQL directly.</p>"},{"location":"API_DBQUERY/#request-body_1","title":"Request Body","text":"<pre><code>{\n \"columnName\": \"devMac\",\n \"id\": [\"AA:BB:CC:DD:EE:FF\"],\n \"dbtable\": \"Devices\",\n \"columns\": [\"devName\", \"devOwner\"],\n \"values\": [\"Laptop\", \"Alice\"]\n}\n</code></pre>"},{"location":"API_DBQUERY/#response_1","title":"Response","text":"<pre><code>{ \"success\": true, \"updated_count\": 1 }\n</code></pre>"},{"location":"API_DBQUERY/#curl-example_1","title":"<code>curl</code> Example","text":"<pre><code>curl -X POST \"http://&lt;server_ip&gt;:&lt;GRAPHQL_PORT&gt;/dbquery/update\" \\\n -H \"Authorization: Bearer &lt;API_TOKEN&gt;\" \\\n -H \"Accept: application/json\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"columnName\": \"devMac\",\n \"id\": [\"AA:BB:CC:DD:EE:FF\"],\n \"dbtable\": \"Devices\",\n \"columns\": [\"devName\", \"devOwner\"],\n \"values\": [\"Laptop\", \"Alice\"]\n }'\n</code></pre>"},{"location":"API_DBQUERY/#3-post-dbquerywrite","title":"3. <code>POST /dbquery/write</code>","text":"<p>Execute a write query (<code>INSERT</code>, <code>UPDATE</code>, <code>DELETE</code>).</p>"},{"location":"API_DBQUERY/#request-body_2","title":"Request Body","text":"<pre><code>{\n \"rawSql\": \"SU5TRVJUIElOVE8gRGV2aWNlcyAoZGV2TWFjLCBkZXYgTmFtZSwgZGV2Rmlyc3RDb25uZWN0aW9uLCBkZXZMYXN0Q29ubmVjdGlvbiwgZGV2TGFzdElQKSBWQUxVRVMgKCc2QTpCQjo0Qzo1RDo2RTonLCAnVGVzdERldmljZScsICcyMDI1LTA4LTMwIDEyOjAwOjAwJywgJzIwMjUtMDgtMzAgMTI6MDA6MDAnLCAnMTAuMC4wLjEwJyk=\"\n}\n</code></pre> <p>Decoded SQL:</p> <pre><code>INSERT INTO Devices (devMac, devName, devFirstConnection, devLastConnection, devLastIP)\nVALUES ('6A:BB:4C:5D:6E', 'TestDevice', '2025-08-30 12:00:00', '2025-08-30 12:00:00', '10.0.0.10');\n</code></pre>"},{"location":"API_DBQUERY/#response_2","title":"Response","text":"<pre><code>{ \"success\": true, \"affected_rows\": 1 }\n</code></pre>"},{"location":"API_DBQUERY/#curl-example_2","title":"<code>curl</code> Example","text":"<pre><code>curl -X POST \"http://&lt;server_ip&gt;:&lt;GRAPHQL_PORT&gt;/dbquery/write\" \\\n -H \"Authorization: Bearer &lt;API_TOKEN&gt;\" \\\n -H \"Accept: application/json\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"rawSql\": \"SU5TRVJUIElOVE8gRGV2aWNlcyAoZGV2TWFjLCBkZXYgTmFtZSwgZGV2Rmlyc3RDb25uZWN0aW9uLCBkZXZMYXN0Q29ubmVjdGlvbiwgZGV2TGFzdElQKSBWQUxVRVMgKCc2QTpCQjo0Qzo1RDo2RTonLCAnVGVzdERldmljZScsICcyMDI1LTA4LTMwIDEyOjAwOjAwJywgJzIwMjUtMDgtMzAgMTI6MDA6MDAnLCAnMTAuMC4wLjEwJyk=\"\n }'\n</code></pre>"},{"location":"API_DBQUERY/#4-post-dbquerydelete","title":"4. <code>POST /dbquery/delete</code>","text":"<p>Delete rows in a table by <code>columnName</code> + <code>id</code>.</p>"},{"location":"API_DBQUERY/#request-body_3","title":"Request Body","text":"<pre><code>{\n \"columnName\": \"devMac\",\n \"id\": [\"AA:BB:CC:DD:EE:FF\"],\n \"dbtable\": \"Devices\"\n}\n</code></pre>"},{"location":"API_DBQUERY/#response_3","title":"Response","text":"<pre><code>{ \"success\": true, \"deleted_count\": 1 }\n</code></pre>"},{"location":"API_DBQUERY/#curl-example_3","title":"<code>curl</code> Example","text":"<pre><code>curl -X POST \"http://&lt;server_ip&gt;:&lt;GRAPHQL_PORT&gt;/dbquery/delete\" \\\n -H \"Authorization: Bearer &lt;API_TOKEN&gt;\" \\\n -H \"Accept: application/json\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"columnName\": \"devMac\",\n \"id\": [\"AA:BB:CC:DD:EE:FF\"],\n \"dbtable\": \"Devices\"\n }'\n</code></pre>"},{"location":"API_DEVICE/","title":"Device API Endpoints","text":"<p>Manage a single device by its MAC address. Operations include retrieval, updates, deletion, resetting properties, and copying data between devices. All endpoints require authorization via Bearer token.</p>"},{"location":"API_DEVICE/#1-retrieve-device-details","title":"1. Retrieve Device Details","text":"<ul> <li> <p>GET <code>/device/&lt;mac&gt;</code> Fetch all details for a single device, including:</p> </li> <li> <p>Computed status (<code>devStatus</code>) \u2192 <code>On-line</code>, <code>Off-line</code>, or <code>Down</code></p> </li> <li>Session and event counts (<code>devSessions</code>, <code>devEvents</code>, <code>devDownAlerts</code>)</li> <li>Presence hours (<code>devPresenceHours</code>)</li> <li>Children devices (<code>devChildrenDynamic</code>) and NIC children (<code>devChildrenNicsDynamic</code>)</li> </ul> <p>Special case: <code>mac=new</code> returns a template for a new device with default values.</p> <p>Response (success):</p> <pre><code>{\n \"devMac\": \"AA:BB:CC:DD:EE:FF\",\n \"devName\": \"Net - Huawei\",\n \"devOwner\": \"Admin\",\n \"devType\": \"Router\",\n \"devVendor\": \"Huawei\",\n \"devStatus\": \"On-line\",\n \"devSessions\": 12,\n \"devEvents\": 5,\n \"devDownAlerts\": 1,\n \"devPresenceHours\": 32,\n \"devChildrenDynamic\": [...],\n \"devChildrenNicsDynamic\": [...],\n ...\n}\n</code></pre> <p>Error Responses:</p> <ul> <li>Device not found \u2192 HTTP 404</li> <li>Unauthorized \u2192 HTTP 403</li> </ul>"},{"location":"API_DEVICE/#2-update-device-fields","title":"2. Update Device Fields","text":"<ul> <li>POST <code>/device/&lt;mac&gt;</code> Create or update a device record.</li> </ul> <p>Request Body:</p> <pre><code>{\n \"devName\": \"New Device\",\n \"devOwner\": \"Admin\",\n \"createNew\": true\n}\n</code></pre> <p>Behavior:</p> <ul> <li>If <code>createNew=true</code> \u2192 creates a new device</li> <li>Otherwise \u2192 updates existing device fields</li> </ul> <p>Response:</p> <pre><code>{\n \"success\": true\n}\n</code></pre> <p>Error Responses:</p> <ul> <li>Unauthorized \u2192 HTTP 403</li> </ul>"},{"location":"API_DEVICE/#3-delete-a-device","title":"3. Delete a Device","text":"<ul> <li>DELETE <code>/device/&lt;mac&gt;/delete</code> Deletes the device with the given MAC.</li> </ul> <p>Response:</p> <pre><code>{\n \"success\": true\n}\n</code></pre> <p>Error Responses:</p> <ul> <li>Unauthorized \u2192 HTTP 403</li> </ul>"},{"location":"API_DEVICE/#4-delete-all-events-for-a-device","title":"4. Delete All Events for a Device","text":"<ul> <li>DELETE <code>/device/&lt;mac&gt;/events/delete</code> Removes all events associated with a device.</li> </ul> <p>Response:</p> <pre><code>{\n \"success\": true\n}\n</code></pre>"},{"location":"API_DEVICE/#5-reset-device-properties","title":"5. Reset Device Properties","text":"<ul> <li>POST <code>/device/&lt;mac&gt;/reset-props</code> Resets the device's custom properties to default values.</li> </ul> <p>Request Body: Optional JSON for additional parameters.</p> <p>Response:</p> <pre><code>{\n \"success\": true\n}\n</code></pre>"},{"location":"API_DEVICE/#6-copy-device-data","title":"6. Copy Device Data","text":"<ul> <li>POST <code>/device/copy</code> Copy all data from one device to another. If a device exists with <code>macTo</code>, it is replaced.</li> </ul> <p>Request Body:</p> <pre><code>{\n \"macFrom\": \"AA:BB:CC:DD:EE:FF\",\n \"macTo\": \"11:22:33:44:55:66\"\n}\n</code></pre> <p>Response:</p> <pre><code>{\n \"success\": true,\n \"message\": \"Device copied from AA:BB:CC:DD:EE:FF to 11:22:33:44:55:66\"\n}\n</code></pre> <p>Error Responses:</p> <ul> <li>Missing <code>macFrom</code> or <code>macTo</code> \u2192 HTTP 400</li> <li>Unauthorized \u2192 HTTP 403</li> </ul>"},{"location":"API_DEVICE/#7-update-a-single-column","title":"7. Update a Single Column","text":"<ul> <li>POST <code>/device/&lt;mac&gt;/update-column</code> Update one specific column for a device.</li> </ul> <p>Request Body:</p> <pre><code>{\n \"columnName\": \"devName\",\n \"columnValue\": \"Updated Device Name\"\n}\n</code></pre> <p>Response (success):</p> <pre><code>{\n \"success\": true\n}\n</code></pre> <p>Error Responses:</p> <ul> <li>Device not found \u2192 HTTP 404</li> <li>Missing <code>columnName</code> or <code>columnValue</code> \u2192 HTTP 400</li> <li>Unauthorized \u2192 HTTP 403</li> </ul>"},{"location":"API_DEVICE/#example-curl-requests","title":"Example <code>curl</code> Requests","text":"<p>Get Device Details:</p> <pre><code>curl -X GET \"http://&lt;server_ip&gt;:&lt;GRAPHQL_PORT&gt;/device/AA:BB:CC:DD:EE:FF\" \\\n -H \"Authorization: Bearer &lt;API_TOKEN&gt;\"\n</code></pre> <p>Update Device Fields:</p> <pre><code>curl -X POST \"http://&lt;server_ip&gt;:&lt;GRAPHQL_PORT&gt;/device/AA:BB:CC:DD:EE:FF\" \\\n -H \"Authorization: Bearer &lt;API_TOKEN&gt;\" \\\n -H \"Content-Type: application/json\" \\\n --data '{\"devName\": \"New Device Name\"}'\n</code></pre> <p>Delete Device:</p> <pre><code>curl -X DELETE \"http://&lt;server_ip&gt;:&lt;GRAPHQL_PORT&gt;/device/AA:BB:CC:DD:EE:FF/delete\" \\\n -H \"Authorization: Bearer &lt;API_TOKEN&gt;\"\n</code></pre> <p>Copy Device Data:</p> <pre><code>curl -X POST \"http://&lt;server_ip&gt;:&lt;GRAPHQL_PORT&gt;/device/copy\" \\\n -H \"Authorization: Bearer &lt;API_TOKEN&gt;\" \\\n -H \"Content-Type: application/json\" \\\n --data '{\"macFrom\":\"AA:BB:CC:DD:EE:FF\",\"macTo\":\"11:22:33:44:55:66\"}'\n</code></pre> <p>Update Single Column:</p> <pre><code>curl -X POST \"http://&lt;server_ip&gt;:&lt;GRAPHQL_PORT&gt;/device/AA:BB:CC:DD:EE:FF/update-column\" \\\n -H \"Authorization: Bearer &lt;API_TOKEN&gt;\" \\\n -H \"Content-Type: application/json\" \\\n --data '{\"columnName\":\"devName\",\"columnValue\":\"Updated Device\"}'\n</code></pre>"},{"location":"API_DEVICES/","title":"Devices Collection API Endpoints","text":"<p>The Devices Collection API provides operations to retrieve, manage, import/export, and filter devices in bulk. All endpoints require authorization via Bearer token.</p>"},{"location":"API_DEVICES/#endpoints","title":"Endpoints","text":""},{"location":"API_DEVICES/#1-get-all-devices","title":"1. Get All Devices","text":"<ul> <li>GET <code>/devices</code> Retrieves all devices from the database.</li> </ul> <p>Response (success):</p> <pre><code>{\n \"success\": true,\n \"devices\": [\n {\n \"devName\": \"Net - Huawei\",\n \"devMAC\": \"AA:BB:CC:DD:EE:FF\",\n \"devIP\": \"192.168.1.1\",\n \"devType\": \"Router\",\n \"devFavorite\": 0,\n \"devStatus\": \"online\"\n },\n ...\n ]\n}\n</code></pre> <p>Error Responses:</p> <ul> <li>Unauthorized \u2192 HTTP 403</li> </ul>"},{"location":"API_DEVICES/#2-delete-devices-by-mac","title":"2. Delete Devices by MAC","text":"<ul> <li>DELETE <code>/devices</code> Deletes devices by MAC address. Supports exact matches or wildcard <code>*</code>.</li> </ul> <p>Request Body:</p> <pre><code>{\n \"macs\": [\"AA:BB:CC:DD:EE:FF\", \"11:22:33:*\"]\n}\n</code></pre> <p>Behavior:</p> <ul> <li>If <code>macs</code> is omitted or <code>null</code> \u2192 deletes all devices.</li> <li>Wildcards <code>*</code> match multiple devices.</li> </ul> <p>Response:</p> <pre><code>{\n \"success\": true,\n \"deleted_count\": 5\n}\n</code></pre> <p>Error Responses:</p> <ul> <li>Unauthorized \u2192 HTTP 403</li> </ul>"},{"location":"API_DEVICES/#3-delete-devices-with-empty-macs","title":"3. Delete Devices with Empty MACs","text":"<ul> <li>DELETE <code>/devices/empty-macs</code> Removes all devices where MAC address is null or empty.</li> </ul> <p>Response:</p> <pre><code>{\n \"success\": true,\n \"deleted\": 3\n}\n</code></pre>"},{"location":"API_DEVICES/#4-delete-unknown-devices","title":"4. Delete Unknown Devices","text":"<ul> <li>DELETE <code>/devices/unknown</code> Deletes devices with names marked as <code>(unknown)</code> or <code>(name not found)</code>.</li> </ul> <p>Response:</p> <pre><code>{\n \"success\": true,\n \"deleted\": 2\n}\n</code></pre>"},{"location":"API_DEVICES/#5-export-devices","title":"5. Export Devices","text":"<ul> <li>GET <code>/devices/export</code> or <code>/devices/export/&lt;format&gt;</code> Exports all devices in CSV (default) or JSON format.</li> </ul> <p>Query Parameter / URL Parameter:</p> <ul> <li><code>format</code> (optional) \u2192 <code>csv</code> (default) or <code>json</code></li> </ul> <p>CSV Response:</p> <ul> <li>Returns as a downloadable CSV file: <code>Content-Disposition: attachment; filename=devices.csv</code></li> </ul> <p>JSON Response:</p> <pre><code>{\n \"data\": [\n { \"devName\": \"Net - Huawei\", \"devMAC\": \"AA:BB:CC:DD:EE:FF\", ... },\n ...\n ],\n \"columns\": [\"devName\", \"devMAC\", \"devIP\", \"devType\", \"devFavorite\", \"devStatus\"]\n}\n</code></pre> <p>Error Responses:</p> <ul> <li>Unsupported format \u2192 HTTP 400</li> </ul>"},{"location":"API_DEVICES/#6-import-devices-from-csv","title":"6. Import Devices from CSV","text":"<ul> <li>POST <code>/devices/import</code> Imports devices from an uploaded CSV or base64-encoded CSV content.</li> </ul> <p>Request Body (multipart file or JSON with <code>content</code> field):</p> <pre><code>{\n \"content\": \"&lt;base64-encoded CSV content&gt;\"\n}\n</code></pre> <p>Response:</p> <pre><code>{\n \"success\": true,\n \"inserted\": 25,\n \"skipped_lines\": [3, 7]\n}\n</code></pre> <p>Error Responses:</p> <ul> <li>Missing file or content \u2192 HTTP 400 / 404</li> <li>CSV malformed \u2192 HTTP 400</li> </ul>"},{"location":"API_DEVICES/#7-get-device-totals","title":"7. Get Device Totals","text":"<ul> <li>GET <code>/devices/totals</code> Returns counts of devices by various categories.</li> </ul> <p>Response:</p> <pre><code>[ \n 120, // Total devices\n 85, // Connected\n 5, // Favorites\n 10, // New\n 8, // Down\n 12 // Archived\n]\n</code></pre> <p>Order: <code>[all, connected, favorites, new, down, archived]</code></p>"},{"location":"API_DEVICES/#8-get-devices-by-status","title":"8. Get Devices by Status","text":"<ul> <li>GET <code>/devices/by-status?status=&lt;status&gt;</code> Returns devices filtered by status.</li> </ul> <p>Query Parameter:</p> <ul> <li><code>status</code> \u2192 Supported values: <code>online</code>, <code>offline</code>, <code>down</code>, <code>archived</code>, <code>favorites</code>, <code>new</code>, <code>my</code></li> <li>If omitted, returns all devices.</li> </ul> <p>Response (success):</p> <pre><code>[\n { \"id\": \"AA:BB:CC:DD:EE:FF\", \"title\": \"Net - Huawei\", \"favorite\": 0 },\n { \"id\": \"11:22:33:44:55:66\", \"title\": \"\u2605 USG Firewall\", \"favorite\": 1 }\n]\n</code></pre> <p>If <code>devFavorite=1</code>, the title is prepended with a star <code>\u2605</code>.</p>"},{"location":"API_DEVICES/#example-curl-requests","title":"Example <code>curl</code> Requests","text":"<p>Get All Devices:</p> <pre><code>curl -X GET \"http://&lt;server_ip&gt;:&lt;GRAPHQL_PORT&gt;/devices\" \\\n -H \"Authorization: Bearer &lt;API_TOKEN&gt;\"\n</code></pre> <p>Delete Devices by MAC:</p> <pre><code>curl -X DELETE \"http://&lt;server_ip&gt;:&lt;GRAPHQL_PORT&gt;/devices\" \\\n -H \"Authorization: Bearer &lt;API_TOKEN&gt;\" \\\n -H \"Content-Type: application/json\" \\\n --data '{\"macs\":[\"AA:BB:CC:DD:EE:FF\",\"11:22:33:*\"]}'\n</code></pre> <p>Export Devices CSV:</p> <pre><code>curl -X GET \"http://&lt;server_ip&gt;:&lt;GRAPHQL_PORT&gt;/devices/export?format=csv\" \\\n -H \"Authorization: Bearer &lt;API_TOKEN&gt;\"\n</code></pre> <p>Import Devices from CSV:</p> <pre><code>curl -X POST \"http://&lt;server_ip&gt;:&lt;GRAPHQL_PORT&gt;/devices/import\" \\\n -H \"Authorization: Bearer &lt;API_TOKEN&gt;\" \\\n -F \"file=@devices.csv\"\n</code></pre> <p>Get Devices by Status:</p> <pre><code>curl -X GET \"http://&lt;server_ip&gt;:&lt;GRAPHQL_PORT&gt;/devices/by-status?status=online\" \\\n -H \"Authorization: Bearer &lt;API_TOKEN&gt;\"\n</code></pre>"},{"location":"API_EVENTS/","title":"Events API Endpoints","text":"<p>The Events API provides access to device event logs, allowing creation, retrieval, deletion, and summary of events over time.</p>"},{"location":"API_EVENTS/#endpoints","title":"Endpoints","text":""},{"location":"API_EVENTS/#1-create-event","title":"1. Create Event","text":"<ul> <li>POST <code>/events/create/&lt;mac&gt;</code> Create an event for a device identified by its MAC address.</li> </ul> <p>Request Body (JSON):</p> <pre><code>{\n \"ip\": \"192.168.1.10\",\n \"event_type\": \"Device Down\",\n \"additional_info\": \"Optional info about the event\",\n \"pending_alert\": 1,\n \"event_time\": \"2025-08-24T12:00:00Z\"\n}\n</code></pre> <ul> <li> <p>Parameters:</p> </li> <li> <p><code>ip</code> (string, optional): IP address of the device</p> </li> <li><code>event_type</code> (string, optional): Type of event (default <code>\"Device Down\"</code>)</li> <li><code>additional_info</code> (string, optional): Extra information</li> <li><code>pending_alert</code> (int, optional): 1 if alert email is pending (default 1)</li> <li><code>event_time</code> (ISO datetime, optional): Event timestamp; defaults to current time</li> </ul> <p>Response (JSON):</p> <pre><code>{\n \"success\": true,\n \"message\": \"Event created for 00:11:22:33:44:55\"\n}\n</code></pre>"},{"location":"API_EVENTS/#2-get-events","title":"2. Get Events","text":"<ul> <li>GET <code>/events</code> Retrieve all events, optionally filtered by MAC address:</li> </ul> <pre><code>/events?mac=&lt;mac&gt;\n</code></pre> <p>Response:</p> <pre><code>{\n \"success\": true,\n \"events\": [\n {\n \"eve_MAC\": \"00:11:22:33:44:55\",\n \"eve_IP\": \"192.168.1.10\",\n \"eve_DateTime\": \"2025-08-24T12:00:00Z\",\n \"eve_EventType\": \"Device Down\",\n \"eve_AdditionalInfo\": \"\",\n \"eve_PendingAlertEmail\": 1\n }\n ]\n}\n</code></pre>"},{"location":"API_EVENTS/#3-delete-events","title":"3. Delete Events","text":"<ul> <li>DELETE <code>/events/&lt;mac&gt;</code> \u2192 Delete events for a specific MAC</li> <li>DELETE <code>/events</code> \u2192 Delete all events</li> <li>DELETE <code>/events/&lt;days&gt;</code> \u2192 Delete events older than N days</li> </ul> <p>Response:</p> <pre><code>{\n \"success\": true,\n \"message\": \"Deleted events older than &lt;days&gt; days\"\n}\n</code></pre>"},{"location":"API_EVENTS/#4-event-totals-over-a-period","title":"4. Event Totals Over a Period","text":"<ul> <li>GET <code>/sessions/totals?period=&lt;period&gt;</code> Return event and session totals over a given period.</li> </ul> <p>Query Parameters:</p> Parameter Description <code>period</code> Time period for totals, e.g., <code>\"7 days\"</code>, <code>\"1 month\"</code>, <code>\"1 year\"</code>, <code>\"100 years\"</code> <p>Sample Response (JSON Array):</p> <pre><code>[120, 85, 5, 10, 3, 7]\n</code></pre> <p>Meaning of Values:</p> <ol> <li>Total events in the period</li> <li>Total sessions</li> <li>Missing sessions</li> <li>Voided events (<code>eve_EventType LIKE 'VOIDED%'</code>)</li> <li>New device events (<code>eve_EventType LIKE 'New Device'</code>)</li> <li>Device down events (<code>eve_EventType LIKE 'Device Down'</code>)</li> </ol>"},{"location":"API_EVENTS/#notes","title":"Notes","text":"<ul> <li>All endpoints require authorization (Bearer token). Unauthorized requests return:</li> </ul> <pre><code>{ \"error\": \"Forbidden\" }\n</code></pre> <ul> <li> <p>Events are stored in the Events table with the following fields: <code>eve_MAC</code>, <code>eve_IP</code>, <code>eve_DateTime</code>, <code>eve_EventType</code>, <code>eve_AdditionalInfo</code>, <code>eve_PendingAlertEmail</code>.</p> </li> <li> <p>Event creation automatically logs activity for debugging.</p> </li> </ul>"},{"location":"API_EVENTS/#example-curl-requests","title":"Example <code>curl</code> Requests","text":"<p>Create Event:</p> <pre><code>curl -X POST \"http://&lt;server_ip&gt;:&lt;GRAPHQL_PORT&gt;/events/create/00:11:22:33:44:55\" \\\n -H \"Authorization: Bearer &lt;API_TOKEN&gt;\" \\\n -H \"Content-Type: application/json\" \\\n --data '{\n \"ip\": \"192.168.1.10\",\n \"event_type\": \"Device Down\",\n \"additional_info\": \"Power outage\",\n \"pending_alert\": 1\n }'\n</code></pre> <p>Get Events for a Device:</p> <pre><code>curl \"http://&lt;server_ip&gt;:&lt;GRAPHQL_PORT&gt;/events?mac=00:11:22:33:44:55\" \\\n -H \"Authorization: Bearer &lt;API_TOKEN&gt;\"\n</code></pre> <p>Delete Events Older Than 30 Days:</p> <pre><code>curl -X DELETE \"http://&lt;server_ip&gt;:&lt;GRAPHQL_PORT&gt;/events/30\" \\\n -H \"Authorization: Bearer &lt;API_TOKEN&gt;\"\n</code></pre> <p>Get Event Totals for 7 Days:</p> <pre><code>curl \"http://&lt;server_ip&gt;:&lt;GRAPHQL_PORT&gt;/sessions/totals?period=7 days\" \\\n -H \"Authorization: Bearer &lt;API_TOKEN&gt;\"\n</code></pre>"},{"location":"API_GRAPHQL/","title":"GraphQL API Endpoint","text":"<p>GraphQL queries are read-optimized for speed. Data may be slightly out of date until the file system cache refreshes. The GraphQL endpoints allows you to access the following objects:</p> <ul> <li>Devices</li> <li>Settings</li> </ul>"},{"location":"API_GRAPHQL/#endpoints","title":"Endpoints","text":"<ul> <li> <p>GET <code>/graphql</code> Returns a simple status message (useful for browser or debugging).</p> </li> <li> <p>POST <code>/graphql</code> Execute GraphQL queries against the <code>devicesSchema</code>.</p> </li> </ul>"},{"location":"API_GRAPHQL/#devices-query","title":"Devices Query","text":""},{"location":"API_GRAPHQL/#sample-query","title":"Sample Query","text":"<pre><code>query GetDevices($options: PageQueryOptionsInput) {\n devices(options: $options) {\n devices {\n rowid\n devMac\n devName\n devOwner\n devType\n devVendor\n devLastConnection\n devStatus\n }\n count\n }\n}\n</code></pre>"},{"location":"API_GRAPHQL/#query-parameters","title":"Query Parameters","text":"Parameter Description <code>page</code> Page number of results to fetch. <code>limit</code> Number of results per page. <code>sort</code> Sorting options (<code>field</code> = field name, <code>order</code> = <code>asc</code> or <code>desc</code>). <code>search</code> Term to filter devices. <code>status</code> Filter devices by status: <code>my_devices</code>, <code>connected</code>, <code>favorites</code>, <code>new</code>, <code>down</code>, <code>archived</code>, <code>offline</code>. <code>filters</code> Additional filters (array of <code>{ filterColumn, filterValue }</code>)."},{"location":"API_GRAPHQL/#curl-example","title":"<code>curl</code> Example","text":"<pre><code>curl 'http://host:GRAPHQL_PORT/graphql' \\\n -X POST \\\n -H 'Authorization: Bearer API_TOKEN' \\\n -H 'Content-Type: application/json' \\\n --data '{\n \"query\": \"query GetDevices($options: PageQueryOptionsInput) { devices(options: $options) { devices { rowid devMac devName devOwner devType devVendor devLastConnection devStatus } count } }\",\n \"variables\": {\n \"options\": {\n \"page\": 1,\n \"limit\": 10,\n \"sort\": [{ \"field\": \"devName\", \"order\": \"asc\" }],\n \"search\": \"\",\n \"status\": \"connected\"\n }\n }\n }'\n</code></pre>"},{"location":"API_GRAPHQL/#sample-response","title":"Sample Response","text":"<pre><code>{\n \"data\": {\n \"devices\": {\n \"devices\": [\n {\n \"rowid\": 1,\n \"devMac\": \"00:11:22:33:44:55\",\n \"devName\": \"Device 1\",\n \"devOwner\": \"Owner 1\",\n \"devType\": \"Type 1\",\n \"devVendor\": \"Vendor 1\",\n \"devLastConnection\": \"2025-01-01T00:00:00Z\",\n \"devStatus\": \"connected\"\n }\n ],\n \"count\": 1\n }\n }\n}\n</code></pre>"},{"location":"API_GRAPHQL/#settings-query","title":"Settings Query","text":"<p>The settings query provides access to NetAlertX configuration stored in the settings table.</p>"},{"location":"API_GRAPHQL/#sample-query_1","title":"Sample Query","text":"<pre><code>query GetSettings {\n settings {\n settings {\n setKey\n setName\n setDescription\n setType\n setOptions\n setGroup\n setValue\n setEvents\n setOverriddenByEnv\n }\n count\n }\n}\n</code></pre>"},{"location":"API_GRAPHQL/#schema-fields","title":"Schema Fields","text":"Field Type Description <code>setKey</code> String Unique key identifier for the setting. <code>setName</code> String Human-readable name. <code>setDescription</code> String Description or documentation of the setting. <code>setType</code> String Data type (<code>string</code>, <code>int</code>, <code>bool</code>, <code>json</code>, etc.). <code>setOptions</code> String Available options (for dropdown/select-type settings). <code>setGroup</code> String Group/category the setting belongs to. <code>setValue</code> String Current value of the setting. <code>setEvents</code> String Events or triggers related to this setting. <code>setOverriddenByEnv</code> Boolean Whether the setting is overridden by an environment variable at runtime."},{"location":"API_GRAPHQL/#curl-example_1","title":"<code>curl</code> Example","text":"<pre><code>curl 'http://host:GRAPHQL_PORT/graphql' \\\n -X POST \\\n -H 'Authorization: Bearer API_TOKEN' \\\n -H 'Content-Type: application/json' \\\n --data '{\n \"query\": \"query GetSettings { settings { settings { setKey setName setDescription setType setOptions setGroup setValue setEvents setOverriddenByEnv } count } }\"\n }'\n</code></pre>"},{"location":"API_GRAPHQL/#sample-response_1","title":"Sample Response","text":"<pre><code>{\n \"data\": {\n \"settings\": {\n \"settings\": [\n {\n \"setKey\": \"UI_MY_DEVICES\",\n \"setName\": \"My Devices Filter\",\n \"setDescription\": \"Defines which statuses to include in the 'My Devices' view.\",\n \"setType\": \"list\",\n \"setOptions\": \"[\\\"online\\\",\\\"new\\\",\\\"down\\\",\\\"offline\\\",\\\"archived\\\"]\",\n \"setGroup\": \"UI\",\n \"setValue\": \"[\\\"online\\\",\\\"new\\\"]\",\n \"setEvents\": null,\n \"setOverriddenByEnv\": false\n },\n {\n \"setKey\": \"NETWORK_DEVICE_TYPES\",\n \"setName\": \"Network Device Types\",\n \"setDescription\": \"Types of devices considered as network infrastructure.\",\n \"setType\": \"list\",\n \"setOptions\": \"[\\\"Router\\\",\\\"Switch\\\",\\\"AP\\\"]\",\n \"setGroup\": \"Network\",\n \"setValue\": \"[\\\"Router\\\",\\\"Switch\\\"]\",\n \"setEvents\": null,\n \"setOverriddenByEnv\": true\n }\n ],\n \"count\": 2\n }\n }\n}\n</code></pre>"},{"location":"API_GRAPHQL/#notes","title":"Notes","text":"<ul> <li>Device and settings queries can be combined in one request since GraphQL supports batching.</li> <li>The <code>setOverriddenByEnv</code> flag helps identify setting values that are locked at container runtime.</li> <li>The schema is read-only \u2014 updates must be performed through other APIs or configuration management. See the other API endpoints for details. </li> </ul>"},{"location":"API_MESSAGING_IN_APP/","title":"In-app Notifications API","text":"<p>Manage in-app notifications for users. Notifications can be written, retrieved, marked as read, or deleted.</p>"},{"location":"API_MESSAGING_IN_APP/#write-notification","title":"Write Notification","text":"<ul> <li>POST <code>/messaging/in-app/write</code> \u2192 Create a new in-app notification.</li> </ul> <p>Request Body:</p> <p><code>json { \"content\": \"This is a test notification\", \"level\": \"alert\" // optional, [\"interrupt\",\"info\",\"alert\"] default: \"alert\" }</code></p> <p>Response:</p> <p><code>json { \"success\": true }</code></p>"},{"location":"API_MESSAGING_IN_APP/#curl-example","title":"<code>curl</code> Example","text":"<pre><code>curl -X POST \"http://&lt;server_ip&gt;:&lt;GRAPHQL_PORT&gt;/messaging/in-app/write\" \\\n -H \"Authorization: Bearer &lt;API_TOKEN&gt;\" \\\n -H \"Accept: application/json\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"content\": \"This is a test notification\",\n \"level\": \"alert\"\n }'\n</code></pre>"},{"location":"API_MESSAGING_IN_APP/#get-unread-notifications","title":"Get Unread Notifications","text":"<ul> <li>GET <code>/messaging/in-app/unread</code> \u2192 Retrieve all unread notifications.</li> </ul> <p>Response:</p> <p><code>json [ { \"timestamp\": \"2025-10-10T12:34:56\", \"guid\": \"f47ac10b-58cc-4372-a567-0e02b2c3d479\", \"read\": 0, \"level\": \"alert\", \"content\": \"This is a test notification\" } ]</code></p>"},{"location":"API_MESSAGING_IN_APP/#curl-example_1","title":"<code>curl</code> Example","text":"<pre><code>curl -X GET \"http://&lt;server_ip&gt;:&lt;GRAPHQL_PORT&gt;/messaging/in-app/unread\" \\\n -H \"Authorization: Bearer &lt;API_TOKEN&gt;\" \\\n -H \"Accept: application/json\"\n</code></pre>"},{"location":"API_MESSAGING_IN_APP/#mark-all-notifications-as-read","title":"Mark All Notifications as Read","text":"<ul> <li>POST <code>/messaging/in-app/read/all</code> \u2192 Mark all notifications as read.</li> </ul> <p>Response:</p> <p><code>json { \"success\": true }</code></p>"},{"location":"API_MESSAGING_IN_APP/#curl-example_2","title":"<code>curl</code> Example","text":"<pre><code>curl -X POST \"http://&lt;server_ip&gt;:&lt;GRAPHQL_PORT&gt;/messaging/in-app/read/all\" \\\n -H \"Authorization: Bearer &lt;API_TOKEN&gt;\" \\\n -H \"Accept: application/json\"\n</code></pre>"},{"location":"API_MESSAGING_IN_APP/#mark-single-notification-as-read","title":"Mark Single Notification as Read","text":"<ul> <li>POST <code>/messaging/in-app/read/&lt;guid&gt;</code> \u2192 Mark a single notification as read using its GUID.</li> </ul> <p>Response (success):</p> <p><code>json { \"success\": true }</code></p> <p>Response (failure):</p> <p><code>json { \"success\": false, \"error\": \"Notification not found\" }</code></p>"},{"location":"API_MESSAGING_IN_APP/#curl-example_3","title":"<code>curl</code> Example","text":"<pre><code>curl -X POST \"http://&lt;server_ip&gt;:&lt;GRAPHQL_PORT&gt;/messaging/in-app/read/f47ac10b-58cc-4372-a567-0e02b2c3d479\" \\\n -H \"Authorization: Bearer &lt;API_TOKEN&gt;\" \\\n -H \"Accept: application/json\"\n</code></pre>"},{"location":"API_MESSAGING_IN_APP/#delete-all-notifications","title":"Delete All Notifications","text":"<ul> <li>DELETE <code>/messaging/in-app/delete</code> \u2192 Remove all notifications from the system.</li> </ul> <p>Response:</p> <p><code>json { \"success\": true }</code></p>"},{"location":"API_MESSAGING_IN_APP/#curl-example_4","title":"<code>curl</code> Example","text":"<pre><code>curl -X DELETE \"http://&lt;server_ip&gt;:&lt;GRAPHQL_PORT&gt;/messaging/in-app/delete\" \\\n -H \"Authorization: Bearer &lt;API_TOKEN&gt;\" \\\n -H \"Accept: application/json\"\n</code></pre>"},{"location":"API_MESSAGING_IN_APP/#delete-single-notification","title":"Delete Single Notification","text":"<ul> <li>DELETE <code>/messaging/in-app/delete/&lt;guid&gt;</code> \u2192 Remove a single notification by its GUID.</li> </ul> <p>Response (success):</p> <p><code>json { \"success\": true }</code></p> <p>Response (failure):</p> <p><code>json { \"success\": false, \"error\": \"Notification not found\" }</code></p>"},{"location":"API_MESSAGING_IN_APP/#curl-example_5","title":"<code>curl</code> Example","text":"<pre><code>curl -X DELETE \"http://&lt;server_ip&gt;:&lt;GRAPHQL_PORT&gt;/messaging/in-app/delete/f47ac10b-58cc-4372-a567-0e02b2c3d479\" \\\n -H \"Authorization: Bearer &lt;API_TOKEN&gt;\" \\\n -H \"Accept: application/json\"\n</code></pre>"},{"location":"API_METRICS/","title":"Metrics API Endpoint","text":"<p>The <code>/metrics</code> endpoint exposes Prometheus-compatible metrics for NetAlertX, including aggregate device counts and per-device status.</p>"},{"location":"API_METRICS/#endpoint-details","title":"Endpoint Details","text":"<ul> <li>GET <code>/metrics</code> \u2192 Returns metrics in plain text.</li> <li>Host: NetAlertX server</li> <li>Port: As configured in <code>GRAPHQL_PORT</code> (default: <code>20212</code>)</li> </ul>"},{"location":"API_METRICS/#example-output","title":"Example Output","text":"<pre><code>netalertx_connected_devices 31\nnetalertx_offline_devices 54\nnetalertx_down_devices 0\nnetalertx_new_devices 0\nnetalertx_archived_devices 31\nnetalertx_favorite_devices 2\nnetalertx_my_devices 54\n\nnetalertx_device_status{device=\"Net - Huawei\", mac=\"Internet\", ip=\"1111.111.111.111\", vendor=\"None\", first_connection=\"2021-01-01 00:00:00\", last_connection=\"2025-08-04 17:57:00\", dev_type=\"Router\", device_status=\"Online\"} 1\nnetalertx_device_status{device=\"Net - USG\", mac=\"74:ac:74:ac:74:ac\", ip=\"192.168.1.1\", vendor=\"Ubiquiti Networks Inc.\", first_connection=\"2022-02-12 22:05:00\", last_connection=\"2025-06-07 08:16:49\", dev_type=\"Firewall\", device_status=\"Archived\"} 1\nnetalertx_device_status{device=\"Raspberry Pi 4 LAN\", mac=\"74:ac:74:ac:74:74\", ip=\"192.168.1.9\", vendor=\"Raspberry Pi Trading Ltd\", first_connection=\"2022-02-12 22:05:00\", last_connection=\"2025-08-04 17:57:00\", dev_type=\"Singleboard Computer (SBC)\", device_status=\"Online\"} 1\n...\n</code></pre>"},{"location":"API_METRICS/#metrics-overview","title":"Metrics Overview","text":""},{"location":"API_METRICS/#1-aggregate-device-counts","title":"1. Aggregate Device Counts","text":"Metric Description <code>netalertx_connected_devices</code> Devices currently connected <code>netalertx_offline_devices</code> Devices currently offline <code>netalertx_down_devices</code> Down/unreachable devices <code>netalertx_new_devices</code> Recently detected devices <code>netalertx_archived_devices</code> Archived devices <code>netalertx_favorite_devices</code> User-marked favorites <code>netalertx_my_devices</code> Devices associated with the current user"},{"location":"API_METRICS/#2-per-device-status","title":"2. Per-Device Status","text":"<p>Metric: <code>netalertx_device_status</code> Each device has labels:</p> <ul> <li><code>device</code>: friendly name</li> <li><code>mac</code>: MAC address (or placeholder)</li> <li><code>ip</code>: last recorded IP</li> <li><code>vendor</code>: manufacturer or \"None\"</li> <li><code>first_connection</code>: timestamp of first detection</li> <li><code>last_connection</code>: most recent contact</li> <li><code>dev_type</code>: device type/category</li> <li><code>device_status</code>: current status (<code>Online</code>, <code>Offline</code>, <code>Archived</code>, <code>Down</code>, \u2026)</li> </ul> <p>Metric value is always <code>1</code> (presence indicator).</p>"},{"location":"API_METRICS/#querying-with-curl","title":"Querying with <code>curl</code>","text":"<pre><code>curl 'http://&lt;server_ip&gt;:&lt;GRAPHQL_PORT&gt;/metrics' \\\n -H 'Authorization: Bearer &lt;API_TOKEN&gt;' \\\n -H 'Accept: text/plain'\n</code></pre> <p>Replace placeholders:</p> <ul> <li><code>&lt;server_ip&gt;</code> \u2013 NetAlertX host IP/hostname</li> <li><code>&lt;GRAPHQL_PORT&gt;</code> \u2013 configured port (default <code>20212</code>)</li> <li><code>&lt;API_TOKEN&gt;</code> \u2013 your API token</li> </ul>"},{"location":"API_METRICS/#prometheus-scraping-configuration","title":"Prometheus Scraping Configuration","text":"<pre><code>scrape_configs:\n - job_name: 'netalertx'\n metrics_path: /metrics\n scheme: http\n scrape_interval: 60s\n static_configs:\n - targets: ['&lt;server_ip&gt;:&lt;GRAPHQL_PORT&gt;']\n authorization:\n type: Bearer\n credentials: &lt;API_TOKEN&gt;\n</code></pre>"},{"location":"API_METRICS/#grafana-dashboard-template","title":"Grafana Dashboard Template","text":"<p>Sample template JSON: Download</p>"},{"location":"API_NETTOOLS/","title":"Net Tools API Endpoints","text":"<p>The Net Tools API provides network diagnostic utilities, including Wake-on-LAN, traceroute, speed testing, DNS resolution, nmap scanning, and internet connection information.</p> <p>All endpoints require authorization via Bearer token.</p>"},{"location":"API_NETTOOLS/#endpoints","title":"Endpoints","text":""},{"location":"API_NETTOOLS/#1-wake-on-lan","title":"1. Wake-on-LAN","text":"<ul> <li>POST <code>/nettools/wakeonlan</code> Sends a Wake-on-LAN packet to wake a device.</li> </ul> <p>Request Body (JSON):</p> <pre><code>{\n \"devMac\": \"AA:BB:CC:DD:EE:FF\"\n}\n</code></pre> <p>Response (success):</p> <pre><code>{\n \"success\": true,\n \"message\": \"WOL packet sent\",\n \"output\": \"Sent magic packet to AA:BB:CC:DD:EE:FF\"\n}\n</code></pre> <p>Error Responses:</p> <ul> <li>Invalid MAC address \u2192 HTTP 400</li> <li>Command failure \u2192 HTTP 500</li> </ul>"},{"location":"API_NETTOOLS/#2-traceroute","title":"2. Traceroute","text":"<ul> <li>POST <code>/nettools/traceroute</code> Performs a traceroute to a specified IP address.</li> </ul> <p>Request Body:</p> <pre><code>{\n \"devLastIP\": \"192.168.1.1\"\n}\n</code></pre> <p>Response (success):</p> <pre><code>{\n \"success\": true,\n \"output\": \"traceroute output as string\"\n}\n</code></pre> <p>Error Responses:</p> <ul> <li>Invalid IP \u2192 HTTP 400</li> <li>Traceroute command failure \u2192 HTTP 500</li> </ul>"},{"location":"API_NETTOOLS/#3-speedtest","title":"3. Speedtest","text":"<ul> <li>GET <code>/nettools/speedtest</code> Runs an internet speed test using <code>speedtest-cli</code>.</li> </ul> <p>Response (success):</p> <pre><code>{\n \"success\": true,\n \"output\": [\n \"Ping: 15 ms\",\n \"Download: 120.5 Mbit/s\",\n \"Upload: 22.4 Mbit/s\"\n ]\n}\n</code></pre> <p>Error Responses:</p> <ul> <li>Command failure \u2192 HTTP 500</li> </ul>"},{"location":"API_NETTOOLS/#4-dns-lookup-nslookup","title":"4. DNS Lookup (nslookup)","text":"<ul> <li>POST <code>/nettools/nslookup</code> Resolves an IP address or hostname using <code>nslookup</code>.</li> </ul> <p>Request Body:</p> <pre><code>{\n \"devLastIP\": \"8.8.8.8\"\n}\n</code></pre> <p>Response (success):</p> <pre><code>{\n \"success\": true,\n \"output\": [\n \"Server: 8.8.8.8\",\n \"Address: 8.8.8.8#53\",\n \"Name: google-public-dns-a.google.com\"\n ]\n}\n</code></pre> <p>Error Responses:</p> <ul> <li>Missing or invalid <code>devLastIP</code> \u2192 HTTP 400</li> <li>Command failure \u2192 HTTP 500</li> </ul>"},{"location":"API_NETTOOLS/#5-nmap-scan","title":"5. Nmap Scan","text":"<ul> <li>POST <code>/nettools/nmap</code> Runs an nmap scan on a target IP address or range.</li> </ul> <p>Request Body:</p> <pre><code>{\n \"scan\": \"192.168.1.0/24\",\n \"mode\": \"fast\"\n}\n</code></pre> <p>Supported Modes:</p> Mode nmap Arguments <code>fast</code> <code>-F</code> <code>normal</code> default <code>detail</code> <code>-A</code> <code>skipdiscovery</code> <code>-Pn</code> <p>Response (success):</p> <pre><code>{\n \"success\": true,\n \"mode\": \"fast\",\n \"ip\": \"192.168.1.0/24\",\n \"output\": [\n \"Starting Nmap 7.91\",\n \"Host 192.168.1.1 is up\",\n \"... scan results ...\"\n ]\n}\n</code></pre> <p>Error Responses:</p> <ul> <li>Invalid IP \u2192 HTTP 400</li> <li>Invalid mode \u2192 HTTP 400</li> <li>Command failure \u2192 HTTP 500</li> </ul>"},{"location":"API_NETTOOLS/#6-internet-connection-info","title":"6. Internet Connection Info","text":"<ul> <li>GET <code>/nettools/internetinfo</code> Fetches public internet connection information using <code>ipinfo.io</code>.</li> </ul> <p>Response (success):</p> <pre><code>{\n \"success\": true,\n \"output\": \"IP: 203.0.113.5 City: Sydney Country: AU Org: Example ISP\"\n}\n</code></pre> <p>Error Responses:</p> <ul> <li>Failed request or empty response \u2192 HTTP 500</li> </ul>"},{"location":"API_NETTOOLS/#example-curl-requests","title":"Example <code>curl</code> Requests","text":"<p>Wake-on-LAN:</p> <pre><code>curl -X POST \"http://&lt;server_ip&gt;:&lt;GRAPHQL_PORT&gt;/nettools/wakeonlan\" \\\n -H \"Authorization: Bearer &lt;API_TOKEN&gt;\" \\\n -H \"Content-Type: application/json\" \\\n --data '{\"devMac\":\"AA:BB:CC:DD:EE:FF\"}'\n</code></pre> <p>Traceroute:</p> <pre><code>curl -X POST \"http://&lt;server_ip&gt;:&lt;GRAPHQL_PORT&gt;/nettools/traceroute\" \\\n -H \"Authorization: Bearer &lt;API_TOKEN&gt;\" \\\n -H \"Content-Type: application/json\" \\\n --data '{\"devLastIP\":\"192.168.1.1\"}'\n</code></pre> <p>Speedtest:</p> <pre><code>curl \"http://&lt;server_ip&gt;:&lt;GRAPHQL_PORT&gt;/nettools/speedtest\" \\\n -H \"Authorization: Bearer &lt;API_TOKEN&gt;\"\n</code></pre> <p>Nslookup:</p> <pre><code>curl -X POST \"http://&lt;server_ip&gt;:&lt;GRAPHQL_PORT&gt;/nettools/nslookup\" \\\n -H \"Authorization: Bearer &lt;API_TOKEN&gt;\" \\\n -H \"Content-Type: application/json\" \\\n --data '{\"devLastIP\":\"8.8.8.8\"}'\n</code></pre> <p>Nmap Scan:</p> <pre><code>curl -X POST \"http://&lt;server_ip&gt;:&lt;GRAPHQL_PORT&gt;/nettools/nmap\" \\\n -H \"Authorization: Bearer &lt;API_TOKEN&gt;\" \\\n -H \"Content-Type: application/json\" \\\n --data '{\"scan\":\"192.168.1.0/24\",\"mode\":\"fast\"}'\n</code></pre> <p>Internet Info:</p> <pre><code>curl \"http://&lt;server_ip&gt;:&lt;GRAPHQL_PORT&gt;/nettools/internetinfo\" \\\n -H \"Authorization: Bearer &lt;API_TOKEN&gt;\"\n</code></pre>"},{"location":"API_OLD/","title":"[Deprecated] API endpoints","text":"<p>Warning</p> <p>Some of these endpoints will be deprecated soon. Please refere to the new API endpoints docs for details on the new API layer.</p> <p>NetAlertX comes with a couple of API endpoints. All requests need to be authorized (executed in a logged in browser session) or you have to pass the value of the <code>API_TOKEN</code> settings as authorization bearer, for example:</p> <pre><code>curl 'http://host:GRAPHQL_PORT/graphql' \\\n -X POST \\\n -H 'Authorization: Bearer API_TOKEN' \\\n -H 'Content-Type: application/json' \\\n --data '{\n \"query\": \"query GetDevices($options: PageQueryOptionsInput) { devices(options: $options) { devices { rowid devMac devName devOwner devType devVendor devLastConnection devStatus } count } }\",\n \"variables\": {\n \"options\": {\n \"page\": 1,\n \"limit\": 10,\n \"sort\": [{ \"field\": \"devName\", \"order\": \"asc\" }],\n \"search\": \"\",\n \"status\": \"connected\"\n }\n }\n }'\n</code></pre>"},{"location":"API_OLD/#api-endpoint-graphql","title":"API Endpoint: GraphQL","text":"<ul> <li>Endpoint URL: <code>php/server/query_graphql.php</code></li> <li>Host: <code>same as front end (web ui)</code></li> <li>Port: <code>20212</code> or as defined by the <code>GRAPHQL_PORT</code> setting</li> </ul>"},{"location":"API_OLD/#example-query-to-fetch-devices","title":"Example Query to Fetch Devices","text":"<p>First, let's define the GraphQL query to fetch devices with pagination and sorting options.</p> <pre><code>query GetDevices($options: PageQueryOptionsInput) {\n devices(options: $options) {\n devices {\n rowid\n devMac\n devName\n devOwner\n devType\n devVendor\n devLastConnection\n devStatus\n }\n count\n }\n}\n</code></pre> <p>See also: Debugging GraphQL issues</p>"},{"location":"API_OLD/#curl-command","title":"<code>curl</code> Command","text":"<p>You can use the following <code>curl</code> command to execute the query. </p> <pre><code>curl 'http://host:GRAPHQL_PORT/graphql' -X POST -H 'Authorization: Bearer API_TOKEN' -H 'Content-Type: application/json' --data '{\n \"query\": \"query GetDevices($options: PageQueryOptionsInput) { devices(options: $options) { devices { rowid devMac devName devOwner devType devVendor devLastConnection devStatus } count } }\",\n \"variables\": {\n \"options\": {\n \"page\": 1,\n \"limit\": 10,\n \"sort\": [{ \"field\": \"devName\", \"order\": \"asc\" }],\n \"search\": \"\",\n \"status\": \"connected\"\n }\n }\n }'\n</code></pre>"},{"location":"API_OLD/#explanation","title":"Explanation:","text":"<ol> <li>GraphQL Query:</li> <li>The <code>query</code> parameter contains the GraphQL query as a string.</li> <li> <p>The <code>variables</code> parameter contains the input variables for the query.</p> </li> <li> <p>Query Variables:</p> </li> <li><code>page</code>: Specifies the page number of results to fetch.</li> <li><code>limit</code>: Specifies the number of results per page.</li> <li><code>sort</code>: Specifies the sorting options, with <code>field</code> being the field to sort by and <code>order</code> being the sort order (<code>asc</code> for ascending or <code>desc</code> for descending).</li> <li><code>search</code>: A search term to filter the devices.</li> <li> <p><code>status</code>: The status filter to apply (valid values are <code>my_devices</code> (determined by the <code>UI_MY_DEVICES</code> setting), <code>connected</code>, <code>favorites</code>, <code>new</code>, <code>down</code>, <code>archived</code>, <code>offline</code>).</p> </li> <li> <p><code>curl</code> Command:</p> </li> <li>The <code>-X POST</code> option specifies that we are making a POST request.</li> <li>The <code>-H \"Content-Type: application/json\"</code> option sets the content type of the request to JSON.</li> <li>The <code>-d</code> option provides the request payload, which includes the GraphQL query and variables.</li> </ol>"},{"location":"API_OLD/#sample-response","title":"Sample Response","text":"<p>The response will be in JSON format, similar to the following:</p> <pre><code>{\n \"data\": {\n \"devices\": {\n \"devices\": [\n {\n \"rowid\": 1,\n \"devMac\": \"00:11:22:33:44:55\",\n \"devName\": \"Device 1\",\n \"devOwner\": \"Owner 1\",\n \"devType\": \"Type 1\",\n \"devVendor\": \"Vendor 1\",\n \"devLastConnection\": \"2025-01-01T00:00:00Z\",\n \"devStatus\": \"connected\"\n },\n {\n \"rowid\": 2,\n \"devMac\": \"66:77:88:99:AA:BB\",\n \"devName\": \"Device 2\",\n \"devOwner\": \"Owner 2\",\n \"devType\": \"Type 2\",\n \"devVendor\": \"Vendor 2\",\n \"devLastConnection\": \"2025-01-02T00:00:00Z\",\n \"devStatus\": \"connected\"\n }\n ],\n \"count\": 2\n }\n }\n}\n</code></pre>"},{"location":"API_OLD/#api-endpoint-json-files","title":"API Endpoint: JSON files","text":"<p>This API endpoint retrieves static files, that are periodically updated. </p> <ul> <li>Endpoint URL: <code>php/server/query_json.php?file=&lt;file name&gt;</code></li> <li>Host: <code>same as front end (web ui)</code></li> <li>Port: <code>20211</code> or as defined by the $PORT docker environment variable (same as the port for the web ui)</li> </ul>"},{"location":"API_OLD/#when-are-the-endpoints-updated","title":"When are the endpoints updated","text":"<p>The endpoints are updated when objects in the API endpoints are changed.</p>"},{"location":"API_OLD/#location-of-the-endpoints","title":"Location of the endpoints","text":"<p>In the container, these files are located under the <code>/app/api/</code> folder. You can access them via the <code>/php/server/query_json.php?file=user_notifications.json</code> endpoint.</p>"},{"location":"API_OLD/#available-endpoints","title":"Available endpoints","text":"<p>You can access the following files:</p> File name Description <code>notification_json_final.json</code> The json version of the last notification (e.g. used for webhooks - sample JSON). <code>table_devices.json</code> All of the available Devices detected by the app. <code>table_plugins_events.json</code> The list of the unprocessed (pending) notification events (plugins_events DB table). <code>table_plugins_history.json</code> The list of notification events history. <code>table_plugins_objects.json</code> The content of the plugins_objects table. Find more info on the Plugin system here <code>language_strings.json</code> The content of the language_strings table, which in turn is loaded from the plugins <code>config.json</code> definitions. <code>table_custom_endpoint.json</code> A custom endpoint generated by the SQL query specified by the <code>API_CUSTOM_SQL</code> setting. <code>table_settings.json</code> The content of the settings table. <code>app_state.json</code> Contains the current application state."},{"location":"API_OLD/#json-data-format","title":"JSON Data format","text":"<p>The endpoints starting with the <code>table_</code> prefix contain most, if not all, data contained in the corresponding database table. The common format for those is:</p> <pre><code>{\n \"data\": [\n {\n \"db_column_name\": \"data\",\n \"db_column_name2\": \"data2\" \n }, \n {\n \"db_column_name\": \"data3\",\n \"db_column_name2\": \"data4\" \n }\n ]\n}\n\n</code></pre> <p>Example JSON of the <code>table_devices.json</code> endpoint with two Devices (database rows):</p> <pre><code>{\n \"data\": [\n {\n \"devMac\": \"Internet\",\n \"devName\": \"Net - Huawei\",\n \"devType\": \"Router\",\n \"devVendor\": null,\n \"devGroup\": \"Always on\",\n \"devFirstConnection\": \"2021-01-01 00:00:00\",\n \"devLastConnection\": \"2021-01-28 22:22:11\",\n \"devLastIP\": \"192.168.1.24\",\n \"devStaticIP\": 0,\n \"devPresentLastScan\": 1,\n \"devLastNotification\": \"2023-01-28 22:22:28.998715\",\n \"devIsNew\": 0,\n \"devParentMAC\": \"\",\n \"devParentPort\": \"\",\n \"devIcon\": \"globe\"\n }, \n {\n \"devMac\": \"a4:8f:ff:aa:ba:1f\",\n \"devName\": \"Net - USG\",\n \"devType\": \"Firewall\",\n \"devVendor\": \"Ubiquiti Inc\",\n \"devGroup\": \"\",\n \"devFirstConnection\": \"2021-02-12 22:05:00\",\n \"devLastConnection\": \"2021-07-17 15:40:00\",\n \"devLastIP\": \"192.168.1.1\",\n \"devStaticIP\": 1,\n \"devPresentLastScan\": 1,\n \"devLastNotification\": \"2021-07-17 15:40:10.667717\",\n \"devIsNew\": 0,\n \"devParentMAC\": \"Internet\",\n \"devParentPort\": 1,\n \"devIcon\": \"shield-halved\"\n }\n ]\n}\n\n</code></pre>"},{"location":"API_OLD/#api-endpoint-prometheus-exporter","title":"API Endpoint: Prometheus Exporter","text":"<ul> <li>Endpoint URL: <code>/metrics</code></li> <li>Host: (where NetAlertX exporter is running)</li> <li>Port: as configured in the <code>GRAPHQL_PORT</code> setting (<code>20212</code> by default)</li> </ul>"},{"location":"API_OLD/#example-output-of-the-metrics-endpoint","title":"Example Output of the <code>/metrics</code> Endpoint","text":"<p>Below is a representative snippet of the metrics you may find when querying the <code>/metrics</code> endpoint for <code>netalertx</code>. It includes both aggregate counters and <code>device_status</code> labels per device.</p> <pre><code>netalertx_connected_devices 31\nnetalertx_offline_devices 54\nnetalertx_down_devices 0\nnetalertx_new_devices 0\nnetalertx_archived_devices 31\nnetalertx_favorite_devices 2\nnetalertx_my_devices 54\n\nnetalertx_device_status{device=\"Net - Huawei\", mac=\"Internet\", ip=\"1111.111.111.111\", vendor=\"None\", first_connection=\"2021-01-01 00:00:00\", last_connection=\"2025-08-04 17:57:00\", dev_type=\"Router\", device_status=\"Online\"} 1\nnetalertx_device_status{device=\"Net - USG\", mac=\"74:ac:74:ac:74:ac\", ip=\"192.168.1.1\", vendor=\"Ubiquiti Networks Inc.\", first_connection=\"2022-02-12 22:05:00\", last_connection=\"2025-06-07 08:16:49\", dev_type=\"Firewall\", device_status=\"Archived\"} 1\nnetalertx_device_status{device=\"Raspberry Pi 4 LAN\", mac=\"74:ac:74:ac:74:74\", ip=\"192.168.1.9\", vendor=\"Raspberry Pi Trading Ltd\", first_connection=\"2022-02-12 22:05:00\", last_connection=\"2025-08-04 17:57:00\", dev_type=\"Singleboard Computer (SBC)\", device_status=\"Online\"} 1\n...\n</code></pre>"},{"location":"API_OLD/#metrics-explanation","title":"Metrics Explanation","text":""},{"location":"API_OLD/#1-aggregate-device-counts","title":"1. Aggregate Device Counts","text":"<p>Metric names prefixed with <code>netalertx_</code> provide aggregated counts by device status:</p> <ul> <li><code>netalertx_connected_devices</code>: number of devices currently connected</li> <li><code>netalertx_offline_devices</code>: devices currently offline</li> <li><code>netalertx_down_devices</code>: down/unreachable devices</li> <li><code>netalertx_new_devices</code>: devices recently detected</li> <li><code>netalertx_archived_devices</code>: archived devices</li> <li><code>netalertx_favorite_devices</code>: user-marked favorite devices</li> <li><code>netalertx_my_devices</code>: devices associated with the current user context</li> </ul> <p>These numeric values give a high-level overview of device distribution.</p>"},{"location":"API_OLD/#2-perdevice-status-with-labels","title":"2. Per\u2011Device Status with Labels","text":"<p>Each individual device is represented by a <code>netalertx_device_status</code> metric, with descriptive labels:</p> <ul> <li><code>device</code>: friendly name of the device</li> <li><code>mac</code>: MAC address (or placeholder)</li> <li><code>ip</code>: last recorded IP address</li> <li><code>vendor</code>: manufacturer or \"None\" if unknown</li> <li><code>first_connection</code>: timestamp when the device was first observed</li> <li><code>last_connection</code>: most recent contact timestamp</li> <li><code>dev_type</code>: device category or type</li> <li><code>device_status</code>: current status (Online / Offline / Archived / Down / ...)</li> </ul> <p>The metric value is always <code>1</code> (indicating presence or active state) and the combination of labels identifies the device.</p>"},{"location":"API_OLD/#how-to-query-with-curl","title":"How to Query with <code>curl</code>","text":"<p>To fetch the metrics from the NetAlertX exporter:</p> <pre><code>curl 'http://&lt;server_ip&gt;:&lt;GRAPHQL_PORT&gt;/metrics' \\\n -H 'Authorization: Bearer &lt;API_TOKEN&gt;' \\\n -H 'Accept: text/plain'\n</code></pre> <p>Replace:</p> <ul> <li><code>&lt;server_ip&gt;</code>: IP or hostname of the NetAlertX server</li> <li><code>&lt;GRAPHQL_PORT&gt;</code>: port specified in your <code>GRAPHQL_PORT</code> setting (default: <code>20212</code>)</li> <li><code>&lt;API_TOKEN&gt;</code> your Bearer token from the <code>API_TOKEN</code> setting</li> </ul>"},{"location":"API_OLD/#summary","title":"Summary","text":"<ul> <li>Endpoint: <code>/metrics</code> provides both summary counters and per-device status entries.</li> <li>Aggregate metrics help monitor overall device states.</li> <li>Detailed metrics expose each device\u2019s metadata via labels.</li> <li>Use case: feed into Prometheus for scraping, monitoring, alerting, or charting dashboard views.</li> </ul>"},{"location":"API_OLD/#prometheus-scraping-configuration","title":"Prometheus Scraping Configuration","text":"<pre><code>scrape_configs:\n - job_name: 'netalertx'\n metrics_path: /metrics\n scheme: http\n scrape_interval: 60s\n static_configs:\n - targets: ['&lt;server_ip&gt;:&lt;GRAPHQL_PORT&gt;']\n authorization:\n type: Bearer\n credentials: &lt;API_TOKEN&gt;\n</code></pre>"},{"location":"API_OLD/#grafana-template","title":"Grafana template","text":"<p>Grafana template sample: Download json</p>"},{"location":"API_OLD/#api-endpoint-log-files","title":"API Endpoint: /log files","text":"<p>This API endpoint retrieves files from the <code>/app/log</code> folder. </p> <ul> <li>Endpoint URL: <code>php/server/query_logs.php?file=&lt;file name&gt;</code></li> <li>Host: <code>same as front end (web ui)</code></li> <li>Port: <code>20211</code> or as defined by the $PORT docker environment variable (same as the port for the web ui)</li> </ul> File Description <code>IP_changes.log</code> Logs of IP address changes <code>app.log</code> Main application log <code>app.php_errors.log</code> PHP error log <code>app_front.log</code> Frontend application log <code>app_nmap.log</code> Logs of Nmap scan results <code>db_is_locked.log</code> Logs when the database is locked <code>execution_queue.log</code> Logs of execution queue activities <code>plugins/</code> Directory for temporary plugin-related files (not accessible) <code>report_output.html</code> HTML report output <code>report_output.json</code> JSON format report output <code>report_output.txt</code> Text format report output <code>stderr.log</code> Logs of standard error output <code>stdout.log</code> Logs of standard output"},{"location":"API_OLD/#api-endpoint-config-files","title":"API Endpoint: /config files","text":"<p>To retrieve files from the <code>/app/config</code> folder. </p> <ul> <li>Endpoint URL: <code>php/server/query_config.php?file=&lt;file name&gt;</code></li> <li>Host: <code>same as front end (web ui)</code></li> <li>Port: <code>20211</code> or as defined by the $PORT docker environment variable (same as the port for the web ui)</li> </ul> File Description <code>devices.csv</code> Devices csv file <code>app.conf</code> Application config file"},{"location":"API_ONLINEHISTORY/","title":"Online History API Endpoints","text":"<p>Manage the online history records of devices. Currently, the API supports deletion of all history entries. All endpoints require authorization.</p>"},{"location":"API_ONLINEHISTORY/#1-delete-online-history","title":"1. Delete Online History","text":"<ul> <li>DELETE <code>/history</code> Remove all records from the online history table (<code>Online_History</code>). This operation cannot be undone.</li> </ul> <p>Response (success):</p> <pre><code>{\n \"success\": true,\n \"message\": \"Deleted online history\"\n}\n</code></pre> <p>Error Responses:</p> <ul> <li>Unauthorized \u2192 HTTP 403</li> </ul>"},{"location":"API_ONLINEHISTORY/#example-curl-request","title":"Example <code>curl</code> Request","text":"<pre><code>curl -X DELETE \"http://&lt;server_ip&gt;:&lt;GRAPHQL_PORT&gt;/history\" \\\n -H \"Authorization: Bearer &lt;API_TOKEN&gt;\"\n</code></pre>"},{"location":"API_SESSIONS/","title":"Sessions API Endpoints","text":"<p>Track and manage device connection sessions. Sessions record when a device connects or disconnects on the network.</p>"},{"location":"API_SESSIONS/#create-a-session","title":"Create a Session","text":"<ul> <li>POST <code>/sessions/create</code> \u2192 Create a new session for a device</li> </ul> <p>Request Body:</p> <p><code>json { \"mac\": \"AA:BB:CC:DD:EE:FF\", \"ip\": \"192.168.1.10\", \"start_time\": \"2025-08-01T10:00:00\", \"end_time\": \"2025-08-01T12:00:00\", // optional \"event_type_conn\": \"Connected\", // optional, default \"Connected\" \"event_type_disc\": \"Disconnected\" // optional, default \"Disconnected\" }</code></p> <p>Response:</p> <p><code>json { \"success\": true, \"message\": \"Session created for MAC AA:BB:CC:DD:EE:FF\" }</code></p>"},{"location":"API_SESSIONS/#curl-example","title":"<code>curl</code> Example","text":"<pre><code>curl -X POST \"http://&lt;server_ip&gt;:&lt;GRAPHQL_PORT&gt;/sessions/create\" \\\n -H \"Authorization: Bearer &lt;API_TOKEN&gt;\" \\\n -H \"Accept: application/json\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"mac\": \"AA:BB:CC:DD:EE:FF\",\n \"ip\": \"192.168.1.10\",\n \"start_time\": \"2025-08-01T10:00:00\",\n \"end_time\": \"2025-08-01T12:00:00\",\n \"event_type_conn\": \"Connected\",\n \"event_type_disc\": \"Disconnected\"\n }'\n\n</code></pre>"},{"location":"API_SESSIONS/#delete-sessions","title":"Delete Sessions","text":"<ul> <li>DELETE <code>/sessions/delete</code> \u2192 Delete all sessions for a given MAC</li> </ul> <p>Request Body:</p> <p><code>json { \"mac\": \"AA:BB:CC:DD:EE:FF\" }</code></p> <p>Response:</p> <p><code>json { \"success\": true, \"message\": \"Deleted sessions for MAC AA:BB:CC:DD:EE:FF\" }</code></p>"},{"location":"API_SESSIONS/#curl-example_1","title":"<code>curl</code> Example","text":"<pre><code>curl -X DELETE \"http://&lt;server_ip&gt;:&lt;GRAPHQL_PORT&gt;/sessions/delete\" \\\n -H \"Authorization: Bearer &lt;API_TOKEN&gt;\" \\\n -H \"Accept: application/json\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"mac\": \"AA:BB:CC:DD:EE:FF\"\n }'\n</code></pre>"},{"location":"API_SESSIONS/#list-sessions","title":"List Sessions","text":"<ul> <li>GET <code>/sessions/list</code> \u2192 Retrieve sessions optionally filtered by device and date range</li> </ul> <p>Query Parameters:</p> <ul> <li><code>mac</code> (optional) \u2192 Filter by device MAC address</li> <li><code>start_date</code> (optional) \u2192 Filter sessions starting from this date (<code>YYYY-MM-DD</code>)</li> <li><code>end_date</code> (optional) \u2192 Filter sessions ending by this date (<code>YYYY-MM-DD</code>)</li> </ul> <p>Example:</p> <p><code>/sessions/list?mac=AA:BB:CC:DD:EE:FF&amp;start_date=2025-08-01&amp;end_date=2025-08-21</code></p> <p>Response:</p> <p><code>json { \"success\": true, \"sessions\": [ { \"ses_MAC\": \"AA:BB:CC:DD:EE:FF\", \"ses_Connection\": \"2025-08-01 10:00\", \"ses_Disconnection\": \"2025-08-01 12:00\", \"ses_Duration\": \"2h 0m\", \"ses_IP\": \"192.168.1.10\", \"ses_Info\": \"\" } ] }</code></p>"},{"location":"API_SESSIONS/#curl-example_2","title":"<code>curl</code> Example","text":"<pre><code>curl -X GET \"http://&lt;server_ip&gt;:&lt;GRAPHQL_PORT&gt;/sessions/list?mac=AA:BB:CC:DD:EE:FF&amp;start_date=2025-08-01&amp;end_date=2025-08-21\" \\\n -H \"Authorization: Bearer &lt;API_TOKEN&gt;\" \\\n -H \"Accept: application/json\"\n</code></pre>"},{"location":"API_SESSIONS/#calendar-view-of-sessions","title":"Calendar View of Sessions","text":"<ul> <li>GET <code>/sessions/calendar</code> \u2192 View sessions in calendar format</li> </ul> <p>Query Parameters:</p> <ul> <li><code>start</code> \u2192 Start date (<code>YYYY-MM-DD</code>)</li> <li><code>end</code> \u2192 End date (<code>YYYY-MM-DD</code>)</li> </ul> <p>Example:</p> <p><code>/sessions/calendar?start=2025-08-01&amp;end=2025-08-21</code></p> <p>Response:</p> <p><code>json { \"success\": true, \"sessions\": [ { \"resourceId\": \"AA:BB:CC:DD:EE:FF\", \"title\": \"\", \"start\": \"2025-08-01T10:00:00\", \"end\": \"2025-08-01T12:00:00\", \"color\": \"#00a659\", \"tooltip\": \"Connection: 2025-08-01 10:00\\nDisconnection: 2025-08-01 12:00\\nIP: 192.168.1.10\", \"className\": \"no-border\" } ] }</code></p>"},{"location":"API_SESSIONS/#curl-example_3","title":"<code>curl</code> Example","text":"<pre><code>curl -X GET \"http://&lt;server_ip&gt;:&lt;GRAPHQL_PORT&gt;/sessions/calendar?start=2025-08-01&amp;end=2025-08-21\" \\\n -H \"Authorization: Bearer &lt;API_TOKEN&gt;\" \\\n -H \"Accept: application/json\"\n</code></pre>"},{"location":"API_SESSIONS/#device-sessions","title":"Device Sessions","text":"<ul> <li>GET <code>/sessions/&lt;mac&gt;</code> \u2192 Retrieve sessions for a specific device</li> </ul> <p>Query Parameters:</p> <ul> <li><code>period</code> \u2192 Period to retrieve sessions (<code>1 day</code>, <code>7 days</code>, <code>1 month</code>, etc.) Default: <code>1 day</code></li> </ul> <p>Example:</p> <p><code>/sessions/AA:BB:CC:DD:EE:FF?period=7 days</code></p> <p>Response:</p> <p><code>json { \"success\": true, \"sessions\": [ { \"ses_MAC\": \"AA:BB:CC:DD:EE:FF\", \"ses_Connection\": \"2025-08-01 10:00\", \"ses_Disconnection\": \"2025-08-01 12:00\", \"ses_Duration\": \"2h 0m\", \"ses_IP\": \"192.168.1.10\", \"ses_Info\": \"\" } ] }</code></p>"},{"location":"API_SESSIONS/#curl-example_4","title":"<code>curl</code> Example","text":"<pre><code>curl -X GET \"http://&lt;server_ip&gt;:&lt;GRAPHQL_PORT&gt;/sessions/AA:BB:CC:DD:EE:FF?period=7%20days\" \\\n -H \"Authorization: Bearer &lt;API_TOKEN&gt;\" \\\n -H \"Accept: application/json\"\n</code></pre>"},{"location":"API_SESSIONS/#session-events-summary","title":"Session Events Summary","text":"<ul> <li>GET <code>/sessions/session-events</code> \u2192 Retrieve a summary of session events</li> </ul> <p>Query Parameters:</p> <ul> <li><code>type</code> \u2192 Event type (<code>all</code>, <code>sessions</code>, <code>missing</code>, <code>voided</code>, <code>new</code>, <code>down</code>) Default: <code>all</code></li> <li><code>period</code> \u2192 Period to retrieve events (<code>7 days</code>, <code>1 month</code>, etc.)</li> </ul> <p>Example:</p> <p><code>/sessions/session-events?type=all&amp;period=7 days</code></p> <p>Response: Returns a list of events or sessions with formatted connection, disconnection, duration, and IP information.</p>"},{"location":"API_SESSIONS/#curl-example_5","title":"<code>curl</code> Example","text":"<pre><code>curl -X GET \"http://&lt;server_ip&gt;:&lt;GRAPHQL_PORT&gt;/sessions/session-events?type=all&amp;period=7%20days\" \\\n -H \"Authorization: Bearer &lt;API_TOKEN&gt;\" \\\n -H \"Accept: application/json\"\n</code></pre>"},{"location":"API_SETTINGS/","title":"Settings API Endpoints","text":"<p>Retrieve application settings stored in the configuration system. This endpoint is useful for quickly fetching individual settings such as <code>API_TOKEN</code> or <code>TIMEZONE</code>.</p> <p>For bulk or structured access (all settings, schema details, or filtering), use the GraphQL API Endpoint.</p>"},{"location":"API_SETTINGS/#get-a-setting","title":"Get a Setting","text":"<ul> <li>GET <code>/settings/&lt;key&gt;</code> \u2192 Retrieve the value of a specific setting</li> </ul> <p>Path Parameter:</p> <ul> <li><code>key</code> \u2192 The setting key to retrieve (e.g., <code>API_TOKEN</code>, <code>TIMEZONE</code>)</li> </ul> <p>Authorization: Requires a valid API token in the <code>Authorization</code> header.</p>"},{"location":"API_SETTINGS/#curl-example-success","title":"<code>curl</code> Example (Success)","text":"<pre><code>curl 'http://&lt;server_ip&gt;:&lt;GRAPHQL_PORT&gt;/settings/API_TOKEN' \\\n -H 'Authorization: Bearer &lt;API_TOKEN&gt;' \\\n -H 'Accept: application/json'\n</code></pre> <p>Response:</p> <pre><code>{\n \"success\": true,\n \"value\": \"my-secret-token\"\n}\n</code></pre>"},{"location":"API_SETTINGS/#curl-example-invalid-key","title":"<code>curl</code> Example (Invalid Key)","text":"<pre><code>curl 'http://&lt;server_ip&gt;:&lt;GRAPHQL_PORT&gt;/settings/DOES_NOT_EXIST' \\\n -H 'Authorization: Bearer &lt;API_TOKEN&gt;' \\\n -H 'Accept: application/json'\n</code></pre> <p>Response:</p> <pre><code>{\n \"success\": true,\n \"value\": null\n}\n</code></pre>"},{"location":"API_SETTINGS/#curl-example-unauthorized","title":"<code>curl</code> Example (Unauthorized)","text":"<pre><code>curl 'http://&lt;server_ip&gt;:&lt;GRAPHQL_PORT&gt;/settings/API_TOKEN' \\\n -H 'Accept: application/json'\n</code></pre> <p>Response:</p> <pre><code>{\n \"error\": \"Forbidden\"\n}\n</code></pre>"},{"location":"API_SETTINGS/#notes","title":"Notes","text":"<ul> <li>This endpoint is optimized for direct retrieval of a single setting.</li> <li>For complex retrieval scenarios (listing all settings, retrieving schema metadata like <code>setName</code>, <code>setDescription</code>, <code>setType</code>, or checking if a setting is overridden by environment variables), use the GraphQL Settings Query:</li> </ul> <pre><code>curl 'http://&lt;server_ip&gt;:&lt;GRAPHQL_PORT&gt;/graphql' \\\n -X POST \\\n -H 'Authorization: Bearer &lt;API_TOKEN&gt;' \\\n -H 'Content-Type: application/json' \\\n --data '{\n \"query\": \"query GetSettings { settings { settings { setKey setName setDescription setType setOptions setGroup setValue setEvents setOverriddenByEnv } count } }\"\n }'\n</code></pre> <p>See the GraphQL API Endpoint for more details.</p>"},{"location":"API_SYNC/","title":"Sync API Endpoint","text":"<p>The <code>/sync</code> endpoint is used by the SYNC plugin to synchronize data between multiple NetAlertX instances (e.g., from a node to a hub). It supports both GET and POST requests.</p>"},{"location":"API_SYNC/#91-get-sync","title":"9.1 GET <code>/sync</code>","text":"<p>Fetches data from a node to the hub. The data is returned as a base64-encoded JSON file.</p> <p>Example Request:</p> <pre><code>curl 'http://&lt;server&gt;:&lt;GRAPHQL_PORT&gt;/sync' \\\n -H 'Authorization: Bearer &lt;API_TOKEN&gt;'\n</code></pre> <p>Response Example:</p> <pre><code>{\n \"node_name\": \"NODE-01\",\n \"status\": 200,\n \"message\": \"OK\",\n \"data_base64\": \"eyJkZXZpY2VzIjogW3siZGV2TWFjIjogIjAwOjExOjIyOjMzOjQ0OjU1IiwiZGV2TmFtZSI6ICJEZXZpY2UgMSJ9XSwgImNvdW50Ijog1fQ==\",\n \"timestamp\": \"2025-08-24T10:15:00+10:00\"\n}\n</code></pre> <p>Notes:</p> <ul> <li><code>data_base64</code> contains the full JSON data encoded in Base64.</li> <li><code>node_name</code> corresponds to the <code>SYNC_node_name</code> setting on the node.</li> <li>Errors (e.g., missing file) return HTTP 500 with an error message.</li> </ul>"},{"location":"API_SYNC/#92-post-sync","title":"9.2 POST <code>/sync</code>","text":"<p>The POST endpoint is used by nodes to send data to the hub. The hub expects the data as form-encoded fields (application/x-www-form-urlencoded or multipart/form-data). The hub then stores the data in the plugin log folder for processing.</p>"},{"location":"API_SYNC/#required-fields","title":"Required Fields","text":"Field Type Description <code>data</code> string The payload from the plugin or devices. Typically plain text, JSON, or encrypted Base64 data. In your Python script, <code>encrypt_data()</code> is applied before sending. <code>node_name</code> string The name of the node sending the data. Matches the node\u2019s <code>SYNC_node_name</code> setting. Used to generate the filename on the hub. <code>plugin</code> string The name of the plugin sending the data. Determines the filename prefix (<code>last_result.&lt;plugin&gt;...</code>). <code>file_path</code> string (optional) Path of the local file being sent. Used only for logging/debugging purposes on the hub; not required for processing."},{"location":"API_SYNC/#how-the-hub-processes-the-post-data","title":"How the Hub Processes the POST Data","text":"<ol> <li>Receives the data and validates the API token.</li> <li>Stores the raw payload in:</li> </ol> <pre><code>INSTALL_PATH/log/plugins/last_result.&lt;plugin&gt;.encoded.&lt;node_name&gt;.&lt;sequence&gt;.log\n</code></pre> <ul> <li><code>&lt;plugin&gt;</code> \u2192 plugin name from the POST request.</li> <li><code>&lt;node_name&gt;</code> \u2192 node name from the POST request.</li> <li> <p><code>&lt;sequence&gt;</code> \u2192 incremented number for each submission.</p> </li> <li> <p>Decodes / decrypts the data if necessary (Base64 or encrypted) before processing.</p> </li> <li> <p>Processes JSON payloads (e.g., device info) to:</p> </li> <li> <p>Avoid duplicates by tracking <code>devMac</code>.</p> </li> <li>Add metadata like <code>devSyncHubNode</code>.</li> <li>Insert new devices into the database.</li> <li>Renames files to indicate they have been processed:</li> </ul> <pre><code>processed_last_result.&lt;plugin&gt;.&lt;node_name&gt;.&lt;sequence&gt;.log\n</code></pre>"},{"location":"API_SYNC/#example-post-payload","title":"Example POST Payload","text":"<p>If a node is sending device data:</p> <pre><code>curl -X POST 'http://&lt;hub&gt;:&lt;PORT&gt;/sync' \\\n -H 'Authorization: Bearer &lt;API_TOKEN&gt;' \\\n -F 'data={\"data\":[{\"devMac\":\"00:11:22:33:44:55\",\"devName\":\"Device 1\",\"devVendor\":\"Vendor A\",\"devLastIP\":\"192.168.1.10\"}]}' \\\n -F 'node_name=NODE-01' \\\n -F 'plugin=SYNC'\n</code></pre> <ul> <li>The <code>data</code> field contains JSON with a <code>data</code> array, where each element is a device object or plugin data object.</li> <li>The <code>plugin</code> and <code>node_name</code> fields allow the hub to organize and store the file correctly.</li> <li>The data is only processed if the relevant plugins are enabled and run on the target server. </li> </ul>"},{"location":"API_SYNC/#key-notes","title":"Key Notes","text":"<ul> <li>Always use the same <code>plugin</code> and <code>node_name</code> values for consistent storage.</li> <li>Encrypted data: The Python script uses <code>encrypt_data()</code> before sending, and the hub decodes it before processing.</li> <li>Sequence numbers: Every submission generates a new sequence, preventing overwriting previous data.</li> <li>Form-encoded: The hub expects <code>multipart/form-data</code> (cURL <code>-F</code>) or <code>application/x-www-form-urlencoded</code>.</li> </ul> <p>Storage Details:</p> <ul> <li>Data is stored under <code>INSTALL_PATH/log/plugins</code> with filenames following the pattern:</li> </ul> <pre><code>last_result.&lt;plugin&gt;.encoded.&lt;node_name&gt;.&lt;sequence&gt;.log\n</code></pre> <ul> <li>Both encoded and decoded files are tracked, and new submissions increment the sequence number.</li> <li>If storing fails, the API returns HTTP 500 with an error message.</li> <li>The data is only processed if the relevant plugins are enabled and run on the target server. </li> </ul>"},{"location":"API_SYNC/#93-notes-and-best-practices","title":"9.3 Notes and Best Practices","text":"<ul> <li>Authorization Required \u2013 Both GET and POST require a valid API token.</li> <li>Data Integrity \u2013 Ensure that <code>node_name</code> and <code>plugin</code> are consistent to avoid overwriting files.</li> <li>Monitoring \u2013 Notifications are generated whenever data is sent or received (<code>write_notification</code>), which can be used for alerting or auditing.</li> <li>Use Case \u2013 Typically used in multi-node deployments to consolidate device and event data on a central hub.</li> </ul>"},{"location":"API_TESTS/","title":"Tests","text":""},{"location":"API_TESTS/#unit-tests","title":"Unit Tests","text":"<p>Warning</p> <p>Please note these test modify data in the database.</p> <ol> <li>See the <code>/test</code> directory for available test cases. These are not exhaustive but cover the main API endpoints. </li> <li>To run a test case, SSH into the container: <code>sudo docker exec -it netalertx /bin/bash</code> </li> <li>Inside the container, install pytest (if not already installed): <code>pip install pytest</code> </li> <li>Run a specific test case: <code>pytest /app/test/TESTFILE.py</code></li> </ol>"},{"location":"AUTHELIA/","title":"Authelia","text":""},{"location":"AUTHELIA/#authelia-support","title":"Authelia support","text":"<p>Warning</p> <p>This is community contributed content and work in progress. Contributions are welcome.</p> <pre><code>theme: dark\n\ndefault_2fa_method: \"totp\"\n\nserver:\n address: 0.0.0.0:9091\n endpoints:\n enable_expvars: false\n enable_pprof: false\n authz:\n forward-auth:\n implementation: 'ForwardAuth'\n authn_strategies:\n - name: 'HeaderAuthorization'\n schemes:\n - 'Basic'\n - name: 'CookieSession'\n ext-authz:\n implementation: 'ExtAuthz'\n authn_strategies:\n - name: 'HeaderAuthorization'\n schemes:\n - 'Basic'\n - name: 'CookieSession'\n auth-request:\n implementation: 'AuthRequest'\n authn_strategies:\n - name: 'HeaderAuthRequestProxyAuthorization'\n schemes:\n - 'Basic'\n - name: 'CookieSession'\n legacy:\n implementation: 'Legacy'\n authn_strategies:\n - name: 'HeaderLegacy'\n - name: 'CookieSession'\n disable_healthcheck: false\n tls:\n key: \"\"\n certificate: \"\"\n client_certificates: []\n headers:\n csp_template: \"\"\n\nlog:\n ## Level of verbosity for logs: info, debug, trace.\n level: info\n\n###############################################################\n# The most important section\n###############################################################\naccess_control:\n ## Default policy can either be 'bypass', 'one_factor', 'two_factor' or 'deny'.\n default_policy: deny\n networks:\n - name: internal\n networks:\n - '192.168.0.0/18'\n - '10.10.10.0/8' # Zerotier\n - name: private\n networks:\n - '172.16.0.0/12'\n rules:\n - networks:\n - private\n domain:\n - '*'\n policy: bypass\n - networks:\n - internal\n domain:\n - '*'\n policy: bypass\n - domain:\n # exclude itself from auth, should not happen as we use Traefik middleware on a case-by-case screnario\n - 'auth.MYDOMAIN1.TLD'\n - 'authelia.MYDOMAIN1.TLD'\n - 'auth.MYDOMAIN2.TLD'\n - 'authelia.MYDOMAIN2.TLD'\n policy: bypass\n - domain:\n #All subdomains match\n - 'MYDOMAIN1.TLD'\n - '*.MYDOMAIN1.TLD'\n policy: two_factor\n - domain:\n # This will not work yet as Authelio does not support multi-domain authentication\n - 'MYDOMAIN2.TLD'\n - '*.MYDOMAIN2.TLD'\n policy: two_factor\n\n\n############################################################\nidentity_validation:\n reset_password:\n jwt_secret: \"[REDACTED]\"\n\nidentity_providers:\n oidc:\n enable_client_debug_messages: true\n enforce_pkce: public_clients_only\n hmac_secret: [REDACTED]\n lifespans:\n authorize_code: 1m\n id_token: 1h\n refresh_token: 90m\n access_token: 1h\n cors:\n endpoints:\n - authorization\n - token\n - revocation\n - introspection\n - userinfo\n allowed_origins:\n - \"*\"\n allowed_origins_from_client_redirect_uris: false\n jwks:\n - key: [REDACTED]\n certificate_chain:\n clients:\n - client_id: portainer\n client_name: Portainer\n # generate secret with \"authelia crypto hash generate pbkdf2 --random --random.length 32 --random.charset alphanumeric\"\n # Random Password: [REDACTED]\n # Digest: [REDACTED]\n client_secret: [REDACTED]\n token_endpoint_auth_method: 'client_secret_post'\n public: false\n authorization_policy: two_factor\n consent_mode: pre-configured #explicit\n pre_configured_consent_duration: '6M' #Must be re-authorised every 6 Months\n scopes:\n - openid\n #- groups #Currently not supported in Authelia V\n - email\n - profile\n redirect_uris:\n - https://portainer.MYDOMAIN1.LTD\n userinfo_signed_response_alg: none\n\n - client_id: openproject\n client_name: OpenProject\n # generate secret with \"authelia crypto hash generate pbkdf2 --random --random.length 32 --random.charset alphanumeric\"\n # Random Password: [REDACTED]\n # Digest: [REDACTED]\n client_secret: [REDACTED]\n token_endpoint_auth_method: 'client_secret_basic'\n public: false\n authorization_policy: two_factor\n consent_mode: pre-configured #explicit\n pre_configured_consent_duration: '6M' #Must be re-authorised every 6 Months\n scopes:\n - openid\n #- groups #Currently not supported in Authelia V\n - email\n - profile\n redirect_uris:\n - https://op.MYDOMAIN.TLD\n #grant_types:\n # - refresh_token\n # - authorization_code\n #response_types:\n # - code\n #response_modes:\n # - form_post\n # - query\n # - fragment\n userinfo_signed_response_alg: none\n##################################################################\n\n\ntelemetry:\n metrics:\n enabled: false\n address: tcp://0.0.0.0:9959\n\ntotp:\n disable: false\n issuer: authelia.com\n algorithm: sha1\n digits: 6\n period: 30 ## The period in seconds a one-time password is valid for.\n skew: 1\n secret_size: 32\n\nwebauthn:\n disable: false\n timeout: 60s ## Adjust the interaction timeout for Webauthn dialogues.\n display_name: Authelia\n attestation_conveyance_preference: indirect\n user_verification: preferred\n\nntp:\n address: \"pool.ntp.org\"\n version: 4\n max_desync: 5s\n disable_startup_check: false\n disable_failure: false\n\nauthentication_backend:\n password_reset:\n disable: false\n custom_url: \"\"\n refresh_interval: 5m\n file:\n path: /config/users_database.yml\n watch: true\n password:\n algorithm: argon2\n argon2:\n variant: argon2id\n iterations: 3\n memory: 65536\n parallelism: 4\n key_length: 32\n salt_length: 16\n\npassword_policy:\n standard:\n enabled: false\n min_length: 8\n max_length: 0\n require_uppercase: true\n require_lowercase: true\n require_number: true\n require_special: true\n ## zxcvbn is a well known and used password strength algorithm. It does not have tunable settings.\n zxcvbn:\n enabled: false\n min_score: 3\n\nregulation:\n max_retries: 3\n find_time: 2m\n ban_time: 5m\n\nsession:\n name: authelia_session\n secret: [REDACTED]\n expiration: 60m\n inactivity: 15m\n cookies:\n - domain: 'MYDOMAIN1.LTD'\n authelia_url: 'https://auth.MYDOMAIN1.LTD'\n name: 'authelia_session'\n default_redirection_url: 'https://MYDOMAIN1.LTD'\n - domain: 'MYDOMAIN2.LTD'\n authelia_url: 'https://auth.MYDOMAIN2.LTD'\n name: 'authelia_session_other'\n default_redirection_url: 'https://MYDOMAIN2.LTD'\n\nstorage:\n encryption_key: [REDACTED]\n local:\n path: /config/db.sqlite3\n\nnotifier:\n disable_startup_check: true\n smtp:\n address: MYOTHERDOMAIN.LTD:465\n timeout: 5s\n username: \"USER@DOMAIN\"\n password: \"[REDACTED]\"\n sender: \"Authelia &lt;postmaster@MYOTHERDOMAIN.LTD&gt;\"\n identifier: NAME@MYOTHERDOMAIN.LTD\n subject: \"[Authelia] {title}\"\n startup_check_address: postmaster@MYOTHERDOMAIN.LTD\n\n</code></pre>"},{"location":"BACKUPS/","title":"Backing things up","text":"<p>Note</p> <p>To backup 99% of your configuration backup at least the <code>/app/config</code> folder. Please read the whole page (or at least \"Scenario 2: Corrupted database\") for details. Note that database definitions might change over time. The safest way is to restore your older backups into the same version of the app they were taken from and then gradually upgarde between releases to the latest version.</p> <p>There are 4 artifacts that can be used to backup the application:</p> File Description Limitations <code>/db/app.db</code> Database file(s) The database file might be in an uncommitted state or corrupted <code>/config/app.conf</code> Configuration file Can be overridden with the <code>APP_CONF_OVERRIDE</code> env variable. <code>/config/devices.csv</code> CSV file containing device information Doesn't contain historical data <code>/config/workflows.json</code> A JSON file containing your workflows N/A"},{"location":"BACKUPS/#backup-strategies","title":"Backup strategies","text":"<p>The safest approach to backups is to backup everything, by taking regular file system backups of the <code>/db</code> and <code>/config</code> folders (I use Kopia). </p> <p>Arguably, the most time is spent setting up the device list, so if only one file is kept I'd recommend to have a latest backup of the <code>devices_&lt;timestamp&gt;.csv</code> or <code>devices.csv</code> file, followed by the <code>app.conf</code> and <code>workflows.json</code> files. You can also download <code>app.conf</code> and <code>devices.csv</code> file in the Maintenance section:</p> <p></p>"},{"location":"BACKUPS/#scenario-1-full-backup","title":"Scenario 1: Full backup","text":"<p>End-result: Full restore</p>"},{"location":"BACKUPS/#source-artifacts","title":"\ud83d\udcbe Source artifacts:","text":"<ul> <li><code>/app/db/app.db</code> (uncorrupted)</li> <li><code>/app/config/app.conf</code></li> <li><code>/app/config/workflows.json</code></li> </ul>"},{"location":"BACKUPS/#recovery","title":"\ud83d\udce5 Recovery:","text":"<p>To restore the application map the above files as described in the Setup documentation. </p>"},{"location":"BACKUPS/#scenario-2-corrupted-database","title":"Scenario 2: Corrupted database","text":"<p>End-result: Partial restore (historical data and some plugin data will be missing)</p>"},{"location":"BACKUPS/#source-artifacts_1","title":"\ud83d\udcbe Source artifacts:","text":"<ul> <li><code>/app/config/app.conf</code></li> <li><code>/app/config/devices_&lt;timestamp&gt;.csv</code> or <code>/app/config/devices.csv</code></li> <li><code>/app/config/workflows.json</code></li> </ul>"},{"location":"BACKUPS/#recovery_1","title":"\ud83d\udce5 Recovery:","text":"<p>Even with a corrupted database you can recover what I would argue is 99% of the configuration. </p> <ul> <li>upload the <code>app.conf</code> and <code>workflows.json</code> files into the mounted <code>/app/config/</code> folder as described in the Setup documentation.</li> <li>rename the <code>devices_&lt;timestamp&gt;.csv</code> to <code>devices.csv</code> and place it in the <code>/app/config</code> folder</li> <li>Restore the <code>devices.csv</code> backup via the Maintenance section</li> </ul>"},{"location":"BACKUPS/#data-and-backup-storage","title":"Data and backup storage","text":"<p>To decide on a backup strategy, check where the data is stored:</p>"},{"location":"BACKUPS/#core-configuration","title":"Core Configuration","text":"<p>The core application configuration is in the <code>app.conf</code> file (See Settings System for details), such as:</p> <ul> <li>Notification settings</li> <li>Scanner settings</li> <li>Scheduled maintenance settings</li> <li>UI configuration</li> </ul>"},{"location":"BACKUPS/#core-device-data","title":"Core Device Data","text":"<p>The core device data is backed up to the <code>devices_&lt;timestamp&gt;.csv</code> or <code>devices.csv</code> file via the CSV Backup <code>CSVBCKP</code> Plugin. This file contains data, such as:</p> <ul> <li>Device names</li> <li>Device icons</li> <li>Device network configuration</li> <li>Device categorization </li> <li>Device custom properties data</li> </ul>"},{"location":"BACKUPS/#historical-data","title":"Historical data","text":"<p>Historical data is stored in the <code>app.db</code> database (See Database overview for details). This data includes:</p> <ul> <li>Plugin objects</li> <li>Plugin historical entries</li> <li>History of Events, Notifications, Workflow Events</li> <li>Presence history</li> </ul>"},{"location":"COMMON_ISSUES/","title":"Common issues","text":""},{"location":"COMMON_ISSUES/#loading","title":"Loading...","text":"<p>Often if the application is misconfigured the <code>Loading...</code> dialog is continuously displayed. This is most likely caused by the backed failing to start. The Maintenance -&gt; Logs section should give you more details on what's happening. If there is no exception, check the Portainer log, or start the container in the foreground (without the <code>-d</code> parameter) to observe any exceptions. It's advisable to enable <code>trace</code> or <code>debug</code>. Check the Debug tips on detailed instructions. </p>"},{"location":"COMMON_ISSUES/#incorrect-scan_subnets","title":"Incorrect SCAN_SUBNETS","text":"<p>One of the most common issues is not configuring <code>SCAN_SUBNETS</code> correctly. If this setting is misconfigured you will only see one or two devices in your devices list after a scan. Please read the subnets docs carefully to resolve this.</p>"},{"location":"COMMON_ISSUES/#duplicate-devices-and-notifications","title":"Duplicate devices and notifications","text":"<p>The app uses the MAC address as an unique identifier for devices. If a new MAC is detected a new device is added to the application and corresponding notifications are triggered. This means that if the MAC of an existing device changes, the device will be logged as a new device. You can usually prevent this from happening by changing the device configuration (in Android, iOS, or Windows) for your network. See the Random Macs guide for details. </p>"},{"location":"COMMON_ISSUES/#permissions","title":"Permissions","text":"<p>Make sure you File permissions are set correctly.</p> <ul> <li>If facing issues (AJAX errors, can't write to DB, empty screen, etc,) make sure permissions are set correctly, and check the logs under <code>/app/log</code>. </li> <li>To solve permission issues you can try setting the owner and group of the <code>app.db</code> by executing the following on the host system: <code>docker exec netalertx chown -R www-data:www-data /app/db/app.db</code>. </li> <li>If still facing issues, try to map the app.db file (\u26a0 not folder) to <code>:/app/db/app.db</code> (see docker-compose Examples for details)</li> </ul>"},{"location":"COMMON_ISSUES/#container-restarts-crashes","title":"Container restarts / crashes","text":"<ul> <li>Check the logs for details. Often a required setting for a notification method is missing. </li> </ul>"},{"location":"COMMON_ISSUES/#unable-to-resolve-host","title":"unable to resolve host","text":"<ul> <li>Check that your <code>SCAN_SUBNETS</code> variable is using the correct mask and <code>--interface</code>. See the subnets docs for details. </li> </ul>"},{"location":"COMMON_ISSUES/#invalid-json","title":"Invalid JSON","text":"<p>Check the Invalid JSON errors debug help docs on how to proceed.</p>"},{"location":"COMMON_ISSUES/#sudo-execution-failing-eg-on-arpscan-on-a-raspberry-pi-4","title":"sudo execution failing (e.g.: on arpscan) on a Raspberry Pi 4","text":"<p>sudo: unexpected child termination condition: 0</p> <p>Resolution based on this issue</p> <pre><code>wget ftp.us.debian.org/debian/pool/main/libs/libseccomp/libseccomp2_2.5.3-2_armhf.deb\nsudo dpkg -i libseccomp2_2.5.3-2_armhf.deb\n</code></pre> <p>The link above will probably break in time too. Go to https://packages.debian.org/sid/armhf/libseccomp2/download to find the new version number and put that in the url.</p>"},{"location":"COMMON_ISSUES/#only-router-and-own-device-show-up","title":"Only Router and own device show up","text":"<p>Make sure that the subnet and interface in <code>SCAN_SUBNETS</code> are correct. If your device/NAS has multiple ethernet ports, you probably need to change <code>eth0</code> to something else.</p>"},{"location":"COMMON_ISSUES/#losing-my-settings-and-devices-after-an-update","title":"Losing my settings and devices after an update","text":"<p>If you lose your devices and/or settings after an update that means you don't have the <code>/app/db</code> and <code>/app/config</code> folders mapped to a permanent storage. That means every time you update these folders are re-created. Make sure you have the volumes specified correctly in your <code>docker-compose.yml</code> or run command.</p>"},{"location":"COMMON_ISSUES/#the-application-is-slow","title":"The application is slow","text":"<p>Slowness is usually caused by incorrect settings (the app might restart, so check the <code>app.log</code>), too many background processes (disable unnecessary scanners), too long scans (limit the number of scanned devices), too many disk operations, or some maintenance plugins might have failed. See the Performance tips docs for details.</p>"},{"location":"COMMUNITY_GUIDES/","title":"Community Guides","text":"<p>Use the official installation guides at first and use community content as supplementary material. Open an issue or PR if you'd like to add your link to the list \ud83d\ude4f (Ordered by last update time)</p> <ul> <li>\u25b6 Discover &amp; Monitor Your Network with This Self-Hosted Open Source Tool - Lawrence Systems (June 2025)</li> <li>\u25b6 Home Lab Network Monitoring - Scotti-BYTE Enterprise Consulting Services (July 2024)</li> <li>\ud83d\udcc4 How to Install NetAlertX on Your Synology NAS - Marius hosting (Updated frequently)</li> <li>\ud83d\udcc4 Using the PiAlert Network Security Scanner on a Raspberry Pi - PiMyLifeUp</li> <li>\u25b6 How to Setup Pi.Alert on Your Synology NAS - Digital Aloha </li> <li>\ud83d\udcc4 \u9632\u8e6d\u7f51\u795e\u5668\uff0c\u7f51\u7edc\u5b89\u5168\u52a9\u624b | \u6781\u7a7a\u95f4\u90e8\u7f72\u7f51\u7edc\u626b\u63cf\u548c\u901a\u77e5\u7cfb\u7edf\u300eNetAlertX\u300f</li> <li>\ud83d\udcc4 \uc2dc\ub180/\ud5e4\ub180\uc5d0\uc11c \ub124\ud2b8\uc6cc\ud06c \uc2a4\uce90\ub108 Pi.Alert Docker\ub85c \uc124\uce58 \ubc0f \uc0ac\uc6a9\ud558\uae30 (July 2023)</li> <li>\ud83d\udcc4 \u7f51\u7edc\u5165\u4fb5\u63a2\u6d4b\u5668Pi.Alert (Chinese) (May 2023)</li> <li>\u25b6 Pi.Alert auf Synology &amp; Docker by - J\u00fcrgen Barth (March 2023)</li> <li>\u25b6 Top Docker Container for Home Server Security - VirtualizationHowto (March 2023)</li> <li>\u25b6 Pi.Alert or WatchYourLAN can alert you to unknown devices appearing on your WiFi or LAN network - Danie van der Merwe (November 2022)</li> </ul>"},{"location":"CUSTOM_PROPERTIES/","title":"Custom Properties for Devices","text":""},{"location":"CUSTOM_PROPERTIES/#overview","title":"Overview","text":"<p>This functionality allows you to define custom properties for devices, which can store and display additional information on the device listing page. By marking properties as \"Show\", you can enhance the user interface with quick actions, notes, or external links.</p>"},{"location":"CUSTOM_PROPERTIES/#key-features","title":"Key Features:","text":"<ul> <li>Customizable Properties: Define specific properties for each device.</li> <li>Visibility Control: Choose which properties are displayed on the device listing page.</li> <li>Interactive Elements: Include actions like links, modals, and device management directly in the interface.</li> </ul>"},{"location":"CUSTOM_PROPERTIES/#defining-custom-properties","title":"Defining Custom Properties","text":"<p>Custom properties are structured as a list of objects, where each property includes the following fields:</p> Field Description <code>CUSTPROP_icon</code> The icon (Base64-encoded HTML) displayed for the property. <code>CUSTPROP_type</code> The action type (e.g., <code>show_notes</code>, <code>link</code>, <code>delete_dev</code>). <code>CUSTPROP_name</code> A short name or title for the property. <code>CUSTPROP_args</code> Arguments for the action (e.g., URL or modal text). <code>CUSTPROP_notes</code> Additional notes or details displayed when applicable. <code>CUSTPROP_show</code> A boolean to control visibility (<code>true</code> to show on the listing page)."},{"location":"CUSTOM_PROPERTIES/#available-action-types","title":"Available Action Types","text":"<ul> <li>Show Notes: Displays a modal with a title and additional notes.</li> <li>Example: Show firmware details or custom messages.</li> <li>Link: Redirects to a specified URL in the current browser tab. (Arguments Needs to contain the full URL.)</li> <li>Link (New Tab): Opens a specified URL in a new browser tab. (Arguments Needs to contain the full URL.)</li> <li>Delete Device: Deletes the device using its MAC address.</li> <li>Run Plugin: Placeholder for executing custom plugins (not implemented yet).</li> </ul>"},{"location":"CUSTOM_PROPERTIES/#usage-on-the-device-listing-page","title":"Usage on the Device Listing Page","text":"<p>Visible properties (<code>CUSTPROP_show: true</code>) are displayed as interactive icons in the device listing. Each icon can perform one of the following actions based on the <code>CUSTPROP_type</code>:</p> <ol> <li>Modals (e.g., Show Notes):</li> <li>Displays detailed information in a popup modal.</li> <li> <p>Example: Firmware version details.</p> </li> <li> <p>Links:</p> </li> <li>Redirect to an external or internal URL.</li> <li> <p>Example: Open a device's documentation or external site.</p> </li> <li> <p>Device Actions:</p> </li> <li>Manage devices with actions like delete.</li> <li> <p>Example: Quickly remove a device from the network.</p> </li> <li> <p>Plugins:</p> </li> <li>Future placeholder for running custom plugin scripts.</li> <li>Note: Not implemented yet.</li> </ol>"},{"location":"CUSTOM_PROPERTIES/#example-use-cases","title":"Example Use Cases","text":"<ol> <li>Device Documentation Link:</li> <li> <p>Add a custom property with <code>CUSTPROP_type</code> set to <code>link</code> or <code>link_new_tab</code> to allow quick navigation to the external documentation of the device.</p> </li> <li> <p>Firmware Details:</p> </li> <li> <p>Use <code>CUSTPROP_type: show_notes</code> to display firmware versions or upgrade instructions in a modal.</p> </li> <li> <p>Device Removal:</p> </li> <li>Enable device removal functionality using <code>CUSTPROP_type: delete_dev</code>.</li> </ol>"},{"location":"CUSTOM_PROPERTIES/#notes","title":"Notes","text":"<ul> <li>Plugin Functionality: The <code>run_plugin</code> action type is currently not implemented and will show an alert if used.</li> <li>Custom Icons (Experimental \ud83e\uddea): Use Base64-encoded HTML to provide custom icons for each property. You can add your icons in Setttings via the <code>CUSTPROP_icon</code> settings </li> <li>Visibility Control: Only properties with <code>CUSTPROP_show: true</code> will appear on the listing page.</li> </ul> <p>This feature provides a flexible way to enhance device management and display with interactive elements tailored to your needs.</p>"},{"location":"DATABASE/","title":"A high-level description of the database structure","text":"<p>An overview of the most important database tables as well as an detailed overview of the Devices table. The MAC address is used as a foreign key in most cases. </p>"},{"location":"DATABASE/#devices-database-table","title":"Devices database table","text":"Field Name Description Sample Value <code>devMac</code> MAC address of the device. <code>00:1A:2B:3C:4D:5E</code> <code>devName</code> Name of the device. <code>iPhone 12</code> <code>devOwner</code> Owner of the device. <code>John Doe</code> <code>devType</code> Type of the device (e.g., phone, laptop, etc.). If set to a network type (e.g., switch), it will become selectable as a Network Parent Node. <code>Laptop</code> <code>devVendor</code> Vendor/manufacturer of the device. <code>Apple</code> <code>devFavorite</code> Whether the device is marked as a favorite. <code>1</code> <code>devGroup</code> Group the device belongs to. <code>Home Devices</code> <code>devComments</code> User comments or notes about the device. <code>Used for work purposes</code> <code>devFirstConnection</code> Timestamp of the device's first connection. <code>2025-03-22 12:07:26+11:00</code> <code>devLastConnection</code> Timestamp of the device's last connection. <code>2025-03-22 12:07:26+11:00</code> <code>devLastIP</code> Last known IP address of the device. <code>192.168.1.5</code> <code>devStaticIP</code> Whether the device has a static IP address. <code>0</code> <code>devScan</code> Whether the device should be scanned. <code>1</code> <code>devLogEvents</code> Whether events related to the device should be logged. <code>0</code> <code>devAlertEvents</code> Whether alerts should be generated for events. <code>1</code> <code>devAlertDown</code> Whether an alert should be sent when the device goes down. <code>0</code> <code>devSkipRepeated</code> Whether to skip repeated alerts for this device. <code>1</code> <code>devLastNotification</code> Timestamp of the last notification sent for this device. <code>2025-03-22 12:07:26+11:00</code> <code>devPresentLastScan</code> Whether the device was present during the last scan. <code>1</code> <code>devIsNew</code> Whether the device is marked as new. <code>0</code> <code>devLocation</code> Physical or logical location of the device. <code>Living Room</code> <code>devIsArchived</code> Whether the device is archived. <code>0</code> <code>devParentMAC</code> MAC address of the parent device (if applicable) to build the Network Tree. <code>00:1A:2B:3C:4D:5F</code> <code>devParentPort</code> Port of the parent device to which this device is connected. <code>Port 3</code> <code>devIcon</code> Icon representing the device. The value is a base64-encoded SVG or Font Awesome HTML tag. <code>PHN2ZyB...</code> <code>devGUID</code> Unique identifier for the device. <code>a2f4b5d6-7a8c-9d10-11e1-f12345678901</code> <code>devSite</code> Site or location where the device is registered. <code>Office</code> <code>devSSID</code> SSID of the Wi-Fi network the device is connected to. <code>HomeNetwork</code> <code>devSyncHubNode</code> The NetAlertX node ID used for synchronization between NetAlertX instances. <code>node_1</code> <code>devSourcePlugin</code> Source plugin that discovered the device. <code>ARPSCAN</code> <code>devCustomProps</code> Custom properties related to the device. The value is a base64-encoded JSON object. <code>PHN2ZyB...</code> <code>devFQDN</code> Fully qualified domain name. <code>raspberrypi.local</code> <code>devParentRelType</code> The type of relationship between the current device and it's parent node. By default, selecting <code>nic</code> will hide it from lists. <code>nic</code> <code>devReqNicsOnline</code> If all NICs are required to be online to mark teh current device online. <code>0</code> <p>To understand how values of these fields influuence application behavior, such as Notifications or Network topology, see also: </p> <ul> <li>Device Management</li> <li>Network Tree Topology Setup</li> <li>Notifications</li> </ul>"},{"location":"DATABASE/#other-tables-overview","title":"Other Tables overview","text":"Table name Description Sample data CurrentScan Result of the current scan Devices The main devices database that also contains the Network tree mappings. If <code>ScanCycle</code> is set to <code>0</code> device is not scanned. Events Used to collect connection/disconnection events. Online_History Used to display the <code>Device presence</code> chart Parameters Used to pass values between the frontend and backend. Plugins_Events For capturing events exposed by a plugin via the <code>last_result.log</code> file. If unique then saved into the <code>Plugins_Objects</code> table. Entries are deleted once processed and stored in the <code>Plugins_History</code> and/or <code>Plugins_Objects</code> tables. Plugins_History History of all entries from the <code>Plugins_Events</code> table Plugins_Language_Strings Language strings collected from the plugin <code>config.json</code> files used for string resolution in the frontend. Plugins_Objects Unique objects detected by individual plugins. Sessions Used to display sessions in the charts Settings Database representation of the sum of all settings from <code>app.conf</code> and plugins coming from <code>config.json</code> files."},{"location":"DEBUG_GRAPHQL/","title":"Debugging GraphQL server issues","text":"<p>The GraphQL server is an API middle layer, running on it's own port specified by <code>GRAPHQL_PORT</code>, to retrieve and show the data in the UI. It can also be used to retrieve data for custom third party integarions. Check the API documentation for details. </p> <p>The most common issue is that the GraphQL server doesn't start properly, usually due to a port conflict. If you are running multiple NetAlertX instances, make sure to use unique ports by changing the <code>GRAPHQL_PORT</code> setting. The default is <code>20212</code>.</p>"},{"location":"DEBUG_GRAPHQL/#how-to-update-the-graphql_port-in-case-of-issues","title":"How to update the <code>GRAPHQL_PORT</code> in case of issues","text":"<p>As a first troubleshooting step try changing the default <code>GRAPHQL_PORT</code> setting. Please remember NetAlertX is running on the host so any application uising the same port will cause issues. </p>"},{"location":"DEBUG_GRAPHQL/#updating-the-setting-via-the-settings-ui","title":"Updating the setting via the Settings UI","text":"<p>Ideally use the Settings UI to update the setting under General -&gt; Core -&gt; GraphQL port:</p> <p></p> <p>You might need to temporarily stop other applications or NetAlertX instances causing conflicts to update the setting. The <code>API_TOKEN</code> is used to authenticate any API calls, including GraphQL requests. </p>"},{"location":"DEBUG_GRAPHQL/#updating-the-appconf-file","title":"Updating the <code>app.conf</code> file","text":"<p>If the UI is not accessible, you can directly edit the <code>app.conf</code> file in your <code>/config</code> folder:</p> <p></p>"},{"location":"DEBUG_GRAPHQL/#using-a-docker-variable","title":"Using a docker variable","text":"<p>All application settings can also be initialized via the <code>APP_CONF_OVERRIDE</code> docker env variable. </p> <pre><code>...\n environment:\n - TZ=Europe/Berlin \n - PORT=20213\n - APP_CONF_OVERRIDE={\"GRAPHQL_PORT\":\"20214\"}\n...\n</code></pre>"},{"location":"DEBUG_GRAPHQL/#how-to-check-the-graphql-server-is-running","title":"How to check the GraphQL server is running?","text":"<p>There are several ways to check if the GraphQL server is running.</p>"},{"location":"DEBUG_GRAPHQL/#init-check","title":"Init Check","text":"<p>You can navigate to Maintenance -&gt; Init Check to see if <code>isGraphQLServerRunning</code> is ticked:</p> <p></p>"},{"location":"DEBUG_GRAPHQL/#checking-the-logs","title":"Checking the Logs","text":"<p>You can navigate to Maintenance -&gt; Logs and search for <code>graphql</code> to see if it started correctly and serving requests:</p> <p></p>"},{"location":"DEBUG_GRAPHQL/#inspecting-the-browser-console","title":"Inspecting the Browser console","text":"<p>In your browser open the dev console (usually F12) and navigate to the Network tab where you can filter GraphQL requests (e.g., reload the Devices page).</p> <p></p> <p>You can then inspect any of the POST requests by opening them in a new tab.</p> <p></p>"},{"location":"DEBUG_INVALID_JSON/","title":"How to debug the Invalid JSON response error","text":"<p>Check the the HTTP response of the failing backend call by following these steps:</p> <ul> <li>Open developer console in your browser (usually, e. g. for Chrome, key F12 on the keyboard).</li> <li>Follow the steps in this screenshot: </li> </ul> <p></p> <ul> <li>Copy the URL causing the error and enter it in the address bar of your browser directly and hit enter. The copied URLs could look something like this (notice the query strings at the end):</li> <li><code>http://&lt;NetAlertX URL&gt;:20211/api/table_devices.json?nocache=1704141103121</code></li> <li> <p><code>http://&lt;NetAlertX URL&gt;:20211/php/server/devices.php?action=getDevicesTotals</code></p> </li> <li> <p>Post the error response in the existing issue thread on GitHub or create a new issue and include the redacted response of the failing query.</p> </li> </ul> <p>For reference, the above queries should return results in the following format:</p>"},{"location":"DEBUG_INVALID_JSON/#first-url","title":"First URL:","text":"<ul> <li>Should yield a valid JSON file</li> </ul>"},{"location":"DEBUG_INVALID_JSON/#second-url","title":"Second URL:","text":""},{"location":"DEBUG_INVALID_JSON/#third-url","title":"Third URL:","text":"<p>You can copy and paste any JSON result (result of the First and Third query) into an online JSON checker, such as this one to check if it's valid.</p>"},{"location":"DEBUG_PHP/","title":"Debugging backend PHP issues","text":""},{"location":"DEBUG_PHP/#logs-in-ui","title":"Logs in UI","text":"<p>You can view recent backend PHP errors directly in the Maintenance &gt; Logs section of the UI. This provides quick access to logs without needing terminal access.</p>"},{"location":"DEBUG_PHP/#accessing-logs-directly","title":"Accessing logs directly","text":"<p>Sometimes, the UI might not be accessible. In that case, you can access the logs directly inside the container.</p>"},{"location":"DEBUG_PHP/#step-by-step","title":"Step-by-step:","text":"<ol> <li>Open a shell into the container:</li> </ol> <p><code>bash docker exec -it netalertx /bin/sh</code></p> <ol> <li>Check the NGINX error log:</li> </ol> <p><code>bash cat /var/log/nginx/error.log</code></p> <ol> <li>Check the PHP application error log:</li> </ol> <p><code>bash cat /app/log/app.php_errors.log</code></p> <p>These logs will help identify syntax issues, fatal errors, or startup problems when the UI fails to load properly.</p>"},{"location":"DEBUG_PLUGINS/","title":"Troubleshooting plugins","text":""},{"location":"DEBUG_PLUGINS/#high-level-overview","title":"High-level overview","text":"<p>If a Plugin supplies data to the main app it's done either vie a SQL query or via a script that updates the <code>last_result.log</code> file in the plugin log folder (<code>app/log/plugins/</code>).</p> <p>For a more in-depth overview on how plugins work check the Plugins development docs.</p>"},{"location":"DEBUG_PLUGINS/#prerequisites","title":"Prerequisites","text":"<ul> <li>Make sure you read and followed the specific plugin setup instructions.</li> <li>Ensure you have debug enabled (see More Logging) </li> </ul>"},{"location":"DEBUG_PLUGINS/#potential-issues","title":"Potential issues","text":"<ul> <li>Bugs</li> <li>Unexpected input (e.g. special characters in names)</li> <li>Dependencies changed how data is output</li> </ul>"},{"location":"DEBUG_PLUGINS/#incorrect-input-data","title":"Incorrect input data","text":"<p>Input data from the plugin might cause mapping issues in specific edge cases. Look for a corresponding section in the <code>app.log</code> file, for example notice the first line of the execution run of the <code>PIHOLE</code> plugin below:</p> <pre><code>17:31:05 [Scheduler] - Scheduler run for PIHOLE: YES\n17:31:05 [Plugin utils] ---------------------------------------------\n17:31:05 [Plugin utils] display_name: PiHole (Device sync)\n17:31:05 [Plugins] CMD: SELECT n.hwaddr AS Object_PrimaryID, {s-quote}null{s-quote} AS Object_SecondaryID, datetime() AS DateTime, na.ip AS Watched_Value1, n.lastQuery AS Watched_Value2, na.name AS Watched_Value3, n.macVendor AS Watched_Value4, {s-quote}null{s-quote} AS Extra, n.hwaddr AS ForeignKey FROM EXTERNAL_PIHOLE.Network AS n LEFT JOIN EXTERNAL_PIHOLE.Network_Addresses AS na ON na.network_id = n.id WHERE n.hwaddr NOT LIKE {s-quote}ip-%{s-quote} AND n.hwaddr is not {s-quote}00:00:00:00:00:00{s-quote} AND na.ip is not null\n17:31:05 [Plugins] setTyp: subnets\n17:31:05 [Plugin utils] Flattening the below array\n17:31:05 ['192.168.1.0/24 --interface=eth1']\n17:31:05 [Plugin utils] isinstance(arr, list) : False | isinstance(arr, str) : True\n17:31:05 [Plugins] Resolved value: 192.168.1.0/24 --interface=eth1\n17:31:05 [Plugins] Convert to Base64: True\n17:31:05 [Plugins] base64 value: b'MTkyLjE2OC4xLjAvMjQgLS1pbnRlcmZhY2U9ZXRoMQ=='\n17:31:05 [Plugins] Timeout: 10\n17:31:05 [Plugins] Executing: SELECT n.hwaddr AS Object_PrimaryID, 'null' AS Object_SecondaryID, datetime() AS DateTime, na.ip AS Watched_Value1, n.lastQuery AS Watched_Value2, na.name AS Watched_Value3, n.macVendor AS Watched_Value4, 'null' AS Extra, n.hwaddr AS ForeignKey FROM EXTERNAL_PIHOLE.Network AS n LEFT JOIN EXTERNAL_PIHOLE.Network_Addresses AS na ON na.network_id = n.id WHERE n.hwaddr NOT LIKE 'ip-%' AND n.hwaddr is not '00:00:00:00:00:00' AND na.ip is not null\n\ud83d\udd3b\n17:31:05 [Plugins] SUCCESS, received 2 entries\n17:31:05 [Plugins] sqlParam entries: [(0, 'PIHOLE', '01:01:01:01:01:01', 'null', 'null', '2023-12-25 06:31:05', '172.30.0.1', 0, 'aaaa', 'vvvvvvvvv', 'not-processed', 'null', 'null', '01:01:01:01:01:01'), (0, 'PIHOLE', '02:42:ac:1e:00:02', 'null', 'null', '2023-12-25 06:31:05', '172.30.0.2', 0, 'dddd', 'vvvvv2222', 'not-processed', 'null', 'null', '02:42:ac:1e:00:02')]\n17:31:05 [Plugins] Processing : PIHOLE\n17:31:05 [Plugins] Existing objects from Plugins_Objects: 4\n17:31:05 [Plugins] Logged events from the plugin run : 2\n17:31:05 [Plugins] pluginEvents count: 2\n17:31:05 [Plugins] pluginObjects count: 4\n17:31:05 [Plugins] events_to_insert count: 0\n17:31:05 [Plugins] history_to_insert count: 4\n17:31:05 [Plugins] objects_to_insert count: 0\n17:31:05 [Plugins] objects_to_update count: 4\n17:31:05 [Plugin utils] In pluginEvents there are 2 events with the status \"watched-not-changed\" \n17:31:05 [Plugin utils] In pluginObjects there are 2 events with the status \"missing-in-last-scan\" \n17:31:05 [Plugin utils] In pluginObjects there are 2 events with the status \"watched-not-changed\" \n17:31:05 [Plugins] Mapping objects to database table: CurrentScan\n17:31:05 [Plugins] SQL query for mapping: INSERT into CurrentScan ( \"cur_MAC\", \"cur_IP\", \"cur_LastQuery\", \"cur_Name\", \"cur_Vendor\", \"cur_ScanMethod\") VALUES ( ?, ?, ?, ?, ?, ?)\n17:31:05 [Plugins] SQL sqlParams for mapping: [('01:01:01:01:01:01', '172.30.0.1', 0, 'aaaa', 'vvvvvvvvv', 'PIHOLE'), ('02:42:ac:1e:00:02', '172.30.0.2', 0, 'dddd', 'vvvvv2222', 'PIHOLE')]\n\ud83d\udd3a\n17:31:05 [API] Update API starting\n17:31:06 [API] Updating table_plugins_history.json file in /api\n</code></pre> <p>The debug output between the \ud83d\udd3bred arrows\ud83d\udd3a is important for debugging (arrows added only to highlight the section on this page, they are not available in the actual debug log)</p> <p>In the above output notice the section logging how many events are produced by the plugin:</p> <pre><code>17:31:05 [Plugins] Existing objects from Plugins_Objects: 4\n17:31:05 [Plugins] Logged events from the plugin run : 2\n17:31:05 [Plugins] pluginEvents count: 2\n17:31:05 [Plugins] pluginObjects count: 4\n17:31:05 [Plugins] events_to_insert count: 0\n17:31:05 [Plugins] history_to_insert count: 4\n17:31:05 [Plugins] objects_to_insert count: 0\n17:31:05 [Plugins] objects_to_update count: 4\n</code></pre> <p>These values, if formatted correctly, will also show up in the UI:</p> <p></p>"},{"location":"DEBUG_PLUGINS/#sharing-application-state","title":"Sharing application state","text":"<p>Sometimes specific log sections are needed to debug issues. The Devices and CurrentScan table data is sometimes needed to figure out what's wrong. </p> <ol> <li>Please set <code>LOG_LEVEL</code> to <code>trace</code> (Disable it once you have the info as this produces big log files).</li> <li>Wait for the issue to occur.</li> <li>Search for <code>================ DEVICES table content ================</code> in your logs.</li> <li>Search for <code>================ CurrentScan table content ================</code> in your logs.</li> <li>Open a new issue and post (redacted) output into the issue description (or send to the netalertx@gmail.com email if sensitive data present).</li> <li>Please set <code>LOG_LEVEL</code> to <code>debug</code> or lower.</li> </ol>"},{"location":"DEBUG_TIPS/","title":"Debugging and troubleshooting","text":"<p>Please follow tips 1 - 4 to get a more detailed error. </p>"},{"location":"DEBUG_TIPS/#1-more-logging","title":"1. More Logging","text":"<p>When debugging an issue always set the highest log level:</p> <p><code>LOG_LEVEL='trace'</code></p>"},{"location":"DEBUG_TIPS/#2-surfacing-errors-when-container-restarts","title":"2. Surfacing errors when container restarts","text":"<p>Start the container via the terminal with a command similar to this one:</p> <pre><code>docker run --rm --network=host \\\n -v local/path/netalertx/config:/app/config \\\n -v local/path/netalertx/db:/app/db \\\n -e TZ=Europe/Berlin \\\n -e PORT=20211 \\\n ghcr.io/jokob-sk/netalertx:latest\n\n</code></pre> <p>\u26a0 Please note, don't use the <code>-d</code> parameter so you see the error when the container crashes. Use this error in your issue description.</p>"},{"location":"DEBUG_TIPS/#3-check-the-_dev-image-and-open-issues","title":"3. Check the _dev image and open issues","text":"<p>If possible, check if your issue got fixed in the <code>_dev</code> image before opening a new issue. The container is:</p> <p><code>ghcr.io/jokob-sk/netalertx-dev:latest</code></p> <p>\u26a0 Please backup your DB and config beforehand!</p> <p>Please also search open issues.</p>"},{"location":"DEBUG_TIPS/#4-disable-restart-behavior","title":"4. Disable restart behavior","text":"<p>To prevent a Docker container from automatically restarting in a Docker Compose file, specify the restart policy as <code>no</code>:</p> <pre><code>version: '3'\n\nservices:\n your-service:\n image: your-image:tag\n restart: no\n # Other service configurations...\n</code></pre>"},{"location":"DEBUG_TIPS/#5-sharing-application-state","title":"5. Sharing application state","text":"<p>Sometimes specific log sections are needed to debug issues. The Devices and CurrentScan table data is sometimes needed to figure out what's wrong. </p> <ol> <li>Please set <code>LOG_LEVEL</code> to <code>trace</code> (Disable it once you have the info as this produces big log files).</li> <li>Wait for the issue to occur.</li> <li>Search for <code>================ DEVICES table content ================</code> in your logs.</li> <li>Search for <code>================ CurrentScan table content ================</code> in your logs.</li> <li>Open a new issue and post (redacted) output into the issue description (or send to the netalertx@gmail.com email if sensitive data present).</li> <li>Please set <code>LOG_LEVEL</code> to <code>debug</code> or lower.</li> </ol>"},{"location":"DEBUG_TIPS/#common-issues","title":"Common issues","text":"<p>See Common issues for details. </p>"},{"location":"DEVICES_BULK_EDITING/","title":"Editing multiple devices at once","text":"<p>NetAlertX allows you to mass-edit devices via a CSV export and import feature, or directly in the UI.</p>"},{"location":"DEVICES_BULK_EDITING/#ui-multi-edit","title":"UI multi edit","text":"<p>Note</p> <p>Make sure you have your backups saved and restorable before doing any mass edits. Check Backup strategies.</p> <p>You can select devices in the Devices view by selecting devices to edit and then clicking the Multi-edit button or via the Maintenance &gt; Multi-Edit section.</p> <p></p>"},{"location":"DEVICES_BULK_EDITING/#csv-bulk-edit","title":"CSV bulk edit","text":"<p>The database and device structure may change with new releases. When using the CSV import functionality, ensure the format matches what the application expects. To avoid issues, you can first export the devices and review the column formats before importing any custom data.</p> <p>Note</p> <p>As always, backup everything, just in case.</p> <ol> <li>In Maintenance &gt; Backup / Restore click the CSV Export button. </li> <li>A <code>devices.csv</code> is generated in the <code>/config</code> folder</li> <li>Edit the <code>devices.csv</code> file however you like. </li> </ol> <p></p> <p>Note</p> <p>The file containing a list of Devices including the Network relationships between Network Nodes and connected devices. You can also trigger this by acessing this URL: <code>&lt;your netalertx url&gt;/php/server/devices.php?action=ExportCSV</code> or via the <code>CSV Backup</code> plugin. (\ud83d\udca1 You can schedule this)</p> <p></p>"},{"location":"DEVICES_BULK_EDITING/#file-encoding-format","title":"File encoding format","text":"<p>Note</p> <p>Keep Linux line endings (suggested editors: Nano, Notepad++)</p> <p></p>"},{"location":"DEVICE_DISPLAY_SETTINGS/","title":"Device Display Settings","text":"<p>This set of settings allows you to group Devices under different views. The Archived toggle allows you to exclude a Device from most listings and notifications. </p> <p></p>"},{"location":"DEVICE_DISPLAY_SETTINGS/#status-colors","title":"Status Colors","text":"<ol> <li>\ud83d\udd0c Online (Green) = A device that is no longer marked as a \"New Device\".</li> <li>\ud83d\udd0c New (Green) = A newly discovered device that is online and is still marked as a \"New Device\".</li> <li>\u2716 New (Grey) = Same as No.2 but device is now offline.</li> <li>\u2716 Offline (Grey) = A device that was not detected online in the last scan. </li> <li>\u26a0 Down (Red) = A device that has \"Alert Down\" marked and has been offline for the time set in the Setting <code>NTFPRCS_alert_down_time</code>. </li> </ol> <p>See also Notification guide.</p>"},{"location":"DEVICE_HEURISTICS/","title":"Device Heuristics: Icon and Type Guessing","text":"<p>This module is responsible for inferring the most likely device type and icon based on minimal identifying data like MAC address, vendor, IP, or device name.</p> <p>It does this using a set of heuristics defined in an external JSON rules file, which it evaluates in priority order.</p> <p>Note</p> <p>You can find the full source code of the heuristics module in the <code>device_heuristics.py</code> file.</p>"},{"location":"DEVICE_HEURISTICS/#json-rule-format","title":"JSON Rule Format","text":"<p>Rules are defined in a file called <code>device_heuristics_rules.json</code> (located under <code>/back</code>), structured like:</p> <pre><code>[\n {\n \"dev_type\": \"Phone\",\n \"icon_html\": \"&lt;i class=\\\"fa-brands fa-apple\\\"&gt;&lt;/i&gt;\",\n \"matching_pattern\": [\n { \"mac_prefix\": \"001A79\", \"vendor\": \"Apple\" }\n ],\n \"name_pattern\": [\"iphone\", \"pixel\"]\n }\n]\n</code></pre> <p>Note</p> <p>Feel free to raise a PR in case you'd like to add any rules into the <code>device_heuristics_rules.json</code> file. Please place new rules into the correct position and consider the priority of already available rules.</p>"},{"location":"DEVICE_HEURISTICS/#supported-fields","title":"Supported fields:","text":"Field Type Description <code>dev_type</code> <code>string</code> Type to assign if rule matches (e.g. <code>\"Gateway\"</code>, <code>\"Phone\"</code>) <code>icon_html</code> <code>string</code> Icon (HTML string) to assign if rule matches. Encoded to base64 at load time. <code>matching_pattern</code> <code>array</code> List of <code>{ mac_prefix, vendor }</code> objects for first strict and then loose matching <code>name_pattern</code> <code>array</code> (optional) List of lowercase substrings (used with regex) <code>ip_pattern</code> <code>array</code> (optional) Regex patterns to match IPs <p>Order in this array defines priority \u2014 rules are checked top-down and short-circuit on first match.</p>"},{"location":"DEVICE_HEURISTICS/#matching-flow-in-priority-order","title":"Matching Flow (in Priority Order)","text":"<p>The function <code>guess_device_attributes(...)</code> runs a series of matching functions in strict order:</p> <ol> <li>MAC + Vendor \u2192 <code>match_mac_and_vendor()</code></li> <li>Vendor only \u2192 <code>match_vendor()</code></li> <li>Name pattern \u2192 <code>match_name()</code></li> <li>IP pattern \u2192 <code>match_ip()</code></li> <li>Final fallback \u2192 defaults defined in the <code>NEWDEV_devIcon</code> and <code>NEWDEV_devType</code> settings.</li> </ol> <p>Note</p> <p>The app will try guessing the device type or icon if <code>devType</code> or <code>devIcon</code> are <code>\"\"</code> or <code>\"null\"</code>.</p>"},{"location":"DEVICE_HEURISTICS/#use-of-default-values","title":"Use of default values","text":"<p>The guessing process runs for every device as long as the current type or icon still matches the default values. Even if earlier heuristics return a match, the system continues evaluating additional clues \u2014 like name or IP \u2014 to try and replace placeholders.</p> <pre><code># Still considered a match attempt if current values are defaults\nif (not type_ or type_ == default_type) or (not icon or icon == default_icon):\n type_, icon = match_ip(ip, default_type, default_icon)\n</code></pre> <p>In other words: if the type or icon is still <code>\"unknown\"</code> (or matches the default), the system assumes the match isn\u2019t final \u2014 and keeps looking. It stops only when both values are non-default (defaults are defined in the <code>NEWDEV_devIcon</code> and <code>NEWDEV_devType</code> settings).</p>"},{"location":"DEVICE_HEURISTICS/#match-behavior-per-function","title":"Match Behavior (per function)","text":"<p>These functions are executed in the following order:</p>"},{"location":"DEVICE_HEURISTICS/#match_mac_and_vendormac_clean-vendor","title":"<code>match_mac_and_vendor(mac_clean, vendor, ...)</code>","text":"<ul> <li>Looks for MAC prefix and vendor substring match</li> <li>Most precise</li> <li>Stops as soon as a match is found</li> </ul>"},{"location":"DEVICE_HEURISTICS/#match_vendorvendor","title":"<code>match_vendor(vendor, ...)</code>","text":"<ul> <li>Falls back to substring match on vendor only</li> <li>Ignores rules where <code>mac_prefix</code> is present (ensures this is really a fallback)</li> </ul>"},{"location":"DEVICE_HEURISTICS/#match_namename","title":"<code>match_name(name, ...)</code>","text":"<ul> <li>Lowercase name is compared against all <code>name_pattern</code> values using regex</li> <li>Good for user-assigned labels (e.g. \"AP Office\", \"iPhone\")</li> </ul>"},{"location":"DEVICE_HEURISTICS/#match_ipip","title":"<code>match_ip(ip, ...)</code>","text":"<ul> <li>If IP is present and matches regex patterns under any rule, it returns that type/icon</li> <li>Usually used for gateways or local IP ranges</li> </ul>"},{"location":"DEVICE_HEURISTICS/#icons","title":"Icons","text":"<ul> <li>Each rule can define an <code>icon_html</code>, which is converted to a <code>icon_base64</code> on load</li> <li>If missing, it falls back to the passed-in <code>default_icon</code> (<code>NEWDEV_devIcon</code> setting)</li> <li>If a match is found but icon is still blank, default is used</li> </ul> <p>TL;DR: Type and icon must both be matched. If only one is matched, the other falls back to the default.</p>"},{"location":"DEVICE_HEURISTICS/#priority-mechanics","title":"Priority Mechanics","text":"<ul> <li>JSON rules are evaluated top-to-bottom</li> <li>Matching is first-hit wins \u2014 no scoring, no weights</li> <li>Rules that are more specific (e.g. exact MAC prefixes) should be listed earlier</li> </ul>"},{"location":"DEVICE_MANAGEMENT/","title":"NetAlertX - Device Management","text":"<p>The Main Info section is where most of the device identifiable information is stored and edited. Some of the information is autodetected via various plugins. Initial values for most of the fields can be specified in the <code>NEWDEV</code> plugin.</p> <p>Note</p> <p>You can multi-edit devices by selecting them in the main Devices view, from the Mainetence section, or via the CSV Export functionality under Maintenance. More info can be found in the Devices Bulk-editing docs.</p> <p></p>"},{"location":"DEVICE_MANAGEMENT/#main-info","title":"Main Info","text":"<ul> <li>MAC: MAC addres of the device. Not editable, unless creating a new dummy device.</li> <li>Last IP: IP addres of the device. Not editable, unless creating a new dummy device.</li> <li>Name: Friendly device name. Autodetected via various \ud83c\udd8e Name discovery plugins. The app attaches <code>(IP match)</code> if the name is discovered via an IP match and not MAC match which could mean the name could be incorrect as IPs might change.</li> <li>Icon: Partially autodetected. Select an existing or add a custom icon. You can also auto-apply the same icon on all devices of the same type. </li> <li>Owner: Device owner (The list is self-populated with existing owners and you can add custom values).</li> <li>Type: Select a device type from the dropdown list (<code>Smartphone</code>, <code>Tablet</code>, <code>Laptop</code>, <code>TV</code>, <code>router</code>, etc.) or add a new device type. If you want the device to act as a Network device (and be able to be a network node in the Network view), select a type under Network Devices or add a new Network Device type in Settings. More information can be found in the Network Setup docs. </li> <li>Vendor: The manufacturing vendor. Automatically updated by NetAlertX when empty or unknown, can be edited.</li> <li>Group: Select a group (<code>Always on</code>, <code>Personal</code>, <code>Friends</code>, etc.) or type your own Group name.</li> <li>Location: Select the location, usually a room, where the device is located (<code>Kitchen</code>, <code>Attic</code>, <code>Living room</code>, etc.) or add a custom Location. </li> <li>Comments: Add any comments for the device, such as a serial number, or maintenance information.</li> </ul> <p>Note</p> <p>Please note the above usage of the fields are only suggestions. You can use most of these fields for other purposes, such as storing the network interface, company owning a device, or similar.</p>"},{"location":"DEVICE_MANAGEMENT/#dummy-devices","title":"Dummy devices","text":"<p>You can create dummy devices from the Devices listing screen. </p> <p></p> <p>The MAC field and the Last IP field will then become editable.</p> <p></p> <p>Note</p> <p>You can couple this with the <code>ICMP</code> plugin which can be used to monitor the status of these devices, if they are actual devices reachable with the <code>ping</code> command. If not, you can use a loopback IP address so they appear online, such as <code>0.0.0.0</code> or <code>127.0.0.1</code>.</p>"},{"location":"DEVICE_MANAGEMENT/#copying-data-from-an-existing-device","title":"Copying data from an existing device.","text":"<p>To speed up device population you can also copy data from an existing device. This can be done from the Tools tab on the Device details. </p>"},{"location":"DEV_DEVCONTAINER/","title":"Devcontainer for NetAlertX Guide","text":"<p>This devcontainer is designed to mirror the production container environment as closely as possible, while providing a rich set of tools for development.</p>"},{"location":"DEV_DEVCONTAINER/#how-to-get-started","title":"How to Get Started","text":"<ol> <li> <p>Prerequisites:</p> <ul> <li>A working Docker installation that can be managed by your user. This can be Docker Desktop or Docker Engine installed via other methods (like the official get-docker script).</li> <li>Visual Studio Code installed.</li> <li>The VS Code Dev Containers extension installed.</li> </ul> </li> <li> <p>Launch the Devcontainer:</p> <ul> <li>Clone this repository.</li> <li>Open the repository folder in VS Code.</li> <li>A notification will pop up in the bottom-right corner asking to \"Reopen in Container\". Click it.</li> <li>VS Code will now build the Docker image and connect your editor to the container. Your terminal, debugger, and all tools will now be running inside this isolated environment.</li> </ul> </li> </ol>"},{"location":"DEV_DEVCONTAINER/#key-workflows-features","title":"Key Workflows &amp; Features","text":"<p>Once you're inside the container, everything is set up for you.</p>"},{"location":"DEV_DEVCONTAINER/#1-services-frontend-backend","title":"1. Services (Frontend &amp; Backend)","text":"<p>The container's startup script (<code>.devcontainer/scripts/setup.sh</code>) automatically starts the Nginx/PHP frontend and the Python backend. You can restart them at any time using the built-in tasks.</p>"},{"location":"DEV_DEVCONTAINER/#2-integrated-debugging-just-press-f5","title":"2. Integrated Debugging (Just Press F5!)","text":"<p>Debugging for both the Python backend and PHP frontend is pre-configured and ready to go.</p> <ul> <li>Python Backend (debugpy): The backend automatically starts with a debugger attached on port <code>5678</code>. Simply open a Python file (e.g., <code>server/__main__.py</code>), set a breakpoint, and press F5 (or select \"Python Backend Debug: Attach\") to connect the debugger.</li> <li>PHP Frontend (Xdebug): Xdebug listens on port <code>9003</code>. In VS Code, start listening for Xdebug connections and use a browser extension (like \"Xdebug helper\") to start a debugging session for the web UI.</li> </ul>"},{"location":"DEV_DEVCONTAINER/#3-common-tasks-f1-run-task","title":"3. Common Tasks (F1 -&gt; Run Task)","text":"<p>We've created several VS Code Tasks to simplify common operations. Access them by pressing <code>F1</code> and typing \"Tasks: Run Task\".</p> <ul> <li><code>Generate Dockerfile</code>: This is important. The actual <code>.devcontainer/Dockerfile</code> is auto-generated. If you need to change the container environment, edit <code>.devcontainer/resources/devcontainer-Dockerfile</code> and then run this task.</li> <li><code>Re-Run Startup Script</code>: Manually re-runs the <code>.devcontainer/scripts/setup.sh</code> script to re-link files and restart services.</li> <li><code>Start Backend (Python)</code> / <code>Start Frontend (nginx and PHP-FPM)</code>: Manually restart the services if needed.</li> </ul>"},{"location":"DEV_DEVCONTAINER/#4-running-tests","title":"4. Running Tests","text":"<p>The environment includes <code>pytest</code>. You can run tests directly from the VS Code Test Explorer UI or by running <code>pytest -q</code> in the integrated terminal. The necessary <code>PYTHONPATH</code> is already configured so that tests can correctly import the server modules.</p>"},{"location":"DEV_DEVCONTAINER/#how-to-maintain-this-devcontainer","title":"How to Maintain This Devcontainer","text":"<p>The setup is designed to be easy to manage. Here are the core principles:</p> <ul> <li>Don't Edit <code>Dockerfile</code> Directly: The main <code>.devcontainer/Dockerfile</code> is a combination of the project's root <code>Dockerfile</code> and a special dev-only stage. To add new tools or dependencies, edit <code>.devcontainer/resources/devcontainer-Dockerfile</code> and then run the <code>Generate Dockerfile</code> task.</li> <li>Build-Time vs. Run-Time Setup:<ul> <li>For changes that can be baked into the image (like installing a new package with <code>apk add</code>), add them to the resource Dockerfile.</li> <li>For changes that must happen when the container starts (like creating symlinks, setting permissions, or starting services), use <code>.devcontainer/scripts/setup.sh</code>.</li> </ul> </li> <li>Project Conventions: The <code>.github/copilot-instructions.md</code> file is an excellent resource to help AI and humans understand the project's architecture, conventions, and how to use existing helper functions instead of hardcoding values.</li> </ul> <p>This setup provides a powerful and consistent foundation for all current and future contributors to NetAlertX.</p>"},{"location":"DEV_ENV_SETUP/","title":"Development Environment Setup","text":"<p>I truly appreciate all contributions! To help keep this project maintainable, this guide provides an overview of project priorities, key design considerations, and overall philosophy. It also includes instructions for setting up your environment so you can start contributing right away.</p>"},{"location":"DEV_ENV_SETUP/#development-guidelines","title":"Development Guidelines","text":"<p>Before starting development, please review the following guidelines.</p>"},{"location":"DEV_ENV_SETUP/#priority-order-highest-to-lowest","title":"Priority Order (Highest to Lowest)","text":"<ol> <li>\ud83d\udd3c Fixing core bugs that lack workarounds </li> <li>\ud83d\udd35 Adding core functionality that unlocks other features (e.g., plugins) </li> <li>\ud83d\udd35 Refactoring to enable faster development </li> <li>\ud83d\udd3d UI improvements (PRs welcome, but low priority) </li> </ol>"},{"location":"DEV_ENV_SETUP/#design-philosophy","title":"Design Philosophy","text":"<p>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. </p> <p>For details, see: - Plugins Development (includes video) - Settings System </p> <p>Focus on core functionality and integrate with existing tools rather than reinventing the wheel. </p> <p>Examples: - Using Apprise for notifications instead of implementing multiple separate gateways - Implementing regex-based validation instead of one-off validation for each setting </p> <p>Note</p> <p>UI changes have lower priority. PRs are welcome, but please keep them small and focused.</p>"},{"location":"DEV_ENV_SETUP/#development-environment-set-up","title":"Development Environment Set Up","text":"<p>Tip</p> <p>There is also a ready to use devcontainer available.</p> <p>The following steps will guide you to set up your environment for local development and to run a custom docker build on your system. For most changes the container doesn't need to be rebuild which speeds up the development significantly.</p> <p>Note</p> <p>Replace <code>/development</code> with the path where your code files will be stored. The default container name is <code>netalertx</code> so there might be a conflict with your running containers.</p>"},{"location":"DEV_ENV_SETUP/#1-download-the-code","title":"1. Download the code:","text":"<ul> <li><code>mkdir /development</code></li> <li><code>cd /development &amp;&amp; git clone https://github.com/jokob-sk/NetAlertX.git</code></li> </ul>"},{"location":"DEV_ENV_SETUP/#2-create-a-dev-env_dev-file","title":"2. Create a DEV .env_dev file","text":"<p><code>touch /development/.env_dev &amp;&amp; sudo nano /development/.env_dev</code></p> <p>The file content should be following, with your custom values.</p> <pre><code>#--------------------------------\n#NETALERTX\n#--------------------------------\nTZ=Europe/Berlin\nPORT=22222 # make sure this port is unique on your whole network\nDEV_LOCATION=/development/NetAlertX\nAPP_DATA_LOCATION=/volume/docker_appdata\n# Make sure your GRAPHQL_PORT setting has a port that is unique on your whole host network\nAPP_CONF_OVERRIDE={\"GRAPHQL_PORT\":\"22223\"} \n# ALWAYS_FRESH_INSTALL=true # uncommenting this will always delete the content of /config and /db dirs on boot to simulate a fresh install\n</code></pre>"},{"location":"DEV_ENV_SETUP/#3-create-db-and-config-dirs","title":"3. Create /db and /config dirs","text":"<p>Create a folder <code>netalertx</code> in the <code>APP_DATA_LOCATION</code> (in this example in <code>/volume/docker_appdata</code>) with 2 subfolders <code>db</code> and <code>config</code>. </p> <ul> <li><code>mkdir /volume/docker_appdata/netalertx</code></li> <li><code>mkdir /volume/docker_appdata/netalertx/db</code></li> <li><code>mkdir /volume/docker_appdata/netalertx/config</code></li> </ul>"},{"location":"DEV_ENV_SETUP/#4-run-the-container","title":"4. Run the container","text":"<ul> <li><code>cd /development/NetAlertX &amp;&amp; sudo docker-compose --env-file ../.env_dev</code></li> </ul> <p>You can then modify the python script without restarting/rebuilding the container every time. Additionally, you can trigger a plugin run via the UI:</p> <p></p>"},{"location":"DEV_ENV_SETUP/#tips","title":"Tips","text":"<p>A quick cheat sheet of useful commands. </p>"},{"location":"DEV_ENV_SETUP/#removing-the-container-and-image","title":"Removing the container and image","text":"<p>A command to stop, remove the container and the image (replace <code>netalertx</code> and <code>netalertx-netalertx</code> with the appropriate values)</p> <ul> <li><code>sudo docker container stop netalertx ; sudo docker container rm netalertx ; sudo docker image rm netalertx-netalertx</code></li> </ul>"},{"location":"DEV_ENV_SETUP/#restart-the-server-backend","title":"Restart the server backend","text":"<p>Most code changes can be tested without rebuilding the container. When working on the python server backend, you only need to restart the server.</p> <ol> <li>You can usually restart the backend via Maintenance &gt; Logs &gt; Restart server</li> </ol> <p></p> <ol> <li> <p>If above doesn't work, SSH into the container and kill &amp; restart the main script loop </p> </li> <li> <p><code>sudo docker exec -it netalertx /bin/bash</code></p> </li> <li> <p><code>pkill -f \"python /app/server\" &amp;&amp; python /app/server &amp;</code></p> </li> <li> <p>If none of the above work, restart the docker container. </p> </li> <li> <p>This is usually the last resort as sometimes the Docker engine becomes unresponsive and the whole engine needs to be restarted. </p> </li> </ol>"},{"location":"DEV_ENV_SETUP/#contributing-pull-requests","title":"Contributing &amp; Pull Requests","text":""},{"location":"DEV_ENV_SETUP/#before-submitting-a-pr-please-ensure","title":"Before submitting a PR, please ensure:","text":"<p>\u2714 Changes are backward-compatible with existing installs. \u2714 No unnecessary changes are made. \u2714 New features are reusable, not narrowly scoped. \u2714 Features are implemented via plugins if possible. </p>"},{"location":"DEV_ENV_SETUP/#mandatory-test-cases","title":"Mandatory Test Cases","text":"<ul> <li>Fresh install (no DB/config).</li> <li>Existing DB/config compatibility.</li> <li> <p>Notification testing:</p> <ul> <li>Email </li> <li>Apprise (e.g., Telegram) </li> <li>Webhook (e.g., Discord) </li> <li>MQTT (e.g., Home Assistant) </li> </ul> </li> <li> <p>Updating Settings and their persistence.</p> </li> <li>Updating a Device</li> <li>Plugin functionality.</li> <li>Error log inspection.</li> </ul> <p>Note</p> <p>Always run all available tests as per the Testing documentation.</p>"},{"location":"DOCKER_COMPOSE/","title":"<code>docker-compose.yaml</code> Examples","text":"<p>Note</p> <p>The container needs to run in <code>network_mode:\"host\"</code>. This also means that not all functionality is supported on a Windows host as Docker for Windows doesn't support this networking option.</p>"},{"location":"DOCKER_COMPOSE/#example-1","title":"Example 1","text":"<pre><code>services:\n netalertx:\n container_name: netalertx\n # use the below line if you want to test the latest dev image\n # image: \"ghcr.io/jokob-sk/netalertx-dev:latest\" \n image: \"ghcr.io/jokob-sk/netalertx:latest\" \n network_mode: \"host\" \n restart: unless-stopped\n volumes:\n - local_path/config:/app/config\n - local_path/db:/app/db \n # (optional) useful for debugging if you have issues setting up the container\n - local_path/logs:/app/log\n # (API: OPTION 1) use for performance\n - type: tmpfs\n target: /app/api\n # (API: OPTION 2) use when debugging issues \n # - local_path/api:/app/api\n environment:\n - TZ=Europe/Berlin \n - PORT=20211\n</code></pre> <p>To run the container execute: <code>sudo docker-compose up -d</code></p>"},{"location":"DOCKER_COMPOSE/#example-2","title":"Example 2","text":"<p>Example by SeimuS.</p> <pre><code>services:\n netalertx:\n container_name: NetAlertX\n hostname: NetAlertX\n privileged: true\n # use the below line if you want to test the latest dev image\n # image: \"ghcr.io/jokob-sk/netalertx-dev:latest\" \n image: ghcr.io/jokob-sk/netalertx:latest\n environment:\n - TZ=Europe/Bratislava\n restart: always\n volumes:\n - ./netalertx/db:/app/db\n - ./netalertx/config:/app/config\n network_mode: host\n</code></pre> <p>To run the container execute: <code>sudo docker-compose up -d</code></p>"},{"location":"DOCKER_COMPOSE/#example-3","title":"Example 3","text":"<p><code>docker-compose.yml</code> </p> <pre><code>services:\n netalertx:\n container_name: netalertx\n # use the below line if you want to test the latest dev image\n # image: \"ghcr.io/jokob-sk/netalertx-dev:latest\" \n image: \"ghcr.io/jokob-sk/netalertx:latest\" \n network_mode: \"host\" \n restart: unless-stopped\n volumes:\n - ${APP_CONFIG_LOCATION}/netalertx/config:/app/config\n - ${APP_DATA_LOCATION}/netalertx/db/:/app/db/ \n # (optional) useful for debugging if you have issues setting up the container\n - ${LOGS_LOCATION}:/app/log\n # (API: OPTION 1) use for performance\n - type: tmpfs\n target: /app/api\n # (API: OPTION 2) use when debugging issues \n # - local/path/api:/app/api\n environment:\n - TZ=${TZ} \n - PORT=${PORT}\n</code></pre> <p><code>.env</code> file</p> <pre><code>#GLOBAL PATH VARIABLES\n\nAPP_DATA_LOCATION=/path/to/docker_appdata\nAPP_CONFIG_LOCATION=/path/to/docker_config\nLOGS_LOCATION=/path/to/docker_logs\n\n#ENVIRONMENT VARIABLES\n\nTZ=Europe/Paris\nPORT=20211\n\n#DEVELOPMENT VARIABLES\n\nDEV_LOCATION=/path/to/local/source/code\n</code></pre> <p>To run the container execute: <code>sudo docker-compose --env-file /path/to/.env up</code></p>"},{"location":"DOCKER_COMPOSE/#example-4-docker-swarm","title":"Example 4: Docker swarm","text":"<p>Notice how the host network is defined in a swarm setup:</p> <pre><code>services:\n netalertx:\n # Use the below line if you want to test the latest dev image\n # image: \"jokobsk/netalertx-dev:latest\"\n image: \"ghcr.io/jokob-sk/netalertx:latest\"\n volumes:\n - /mnt/MYSERVER/netalertx/config:/config:rw\n - /mnt/MYSERVER/netalertx/db:/netalertx/db:rw\n - /mnt/MYSERVER/netalertx/logs:/netalertx/front/log:rw\n environment:\n - TZ=Europe/London\n - PORT=20211\n networks:\n - outside\n deploy:\n mode: replicated\n replicas: 1\n restart_policy:\n condition: on-failure\n\nnetworks:\n outside:\n external:\n name: \"host\"\n\n\n</code></pre>"},{"location":"DOCKER_COMPOSE/#example-5-same-as-3-but-with-a-top-level-root-directory-also-works-in-portainer-as-is","title":"Example 5: same as 3 but with a top-level root directory; also works in Portainer as-is","text":"<p><code>docker-compose.yml</code> </p> <pre><code>services:\n netalertx:\n container_name: netalertx\n # use the below line if you want to test the latest dev image instead of the stable release\n # image: \"ghcr.io/jokob-sk/netalertx-dev:latest\" \n image: \"ghcr.io/jokob-sk/netalertx:latest\" \n\n network_mode: \"host\" \n restart: unless-stopped\n volumes:\n - ${APP_FOLDER}/netalertx/config:/app/config\n - ${APP_FOLDER}/netalertx/db:/app/db \n # (optional) useful for debugging if you have issues setting up the container\n - ${APP_FOLDER}/netalertx/log:/app/log\n # (API: OPTION 1) default -&gt; use for performance\n - type: tmpfs\n target: /app/api\n # (API: OPTION 2) use when debugging issues \n # - ${APP_FOLDER}/netalertx/api:/app/api\n environment:\n\n - TZ=${TZ}\n - PORT=${PORT}\n - PUID=${PUID}\n - PGID=${PGID}\n - LISTEN_ADDR=${LISTEN_ADDR}\n</code></pre> <p><code>.env</code> file</p> <pre><code>APP_FOLDER=/path/to/local/NetAlertX/location\n\n#ENVIRONMENT VARIABLES\n\nPUID=200\nPGID=300\n\nTZ=America/New_York\nLISTEN_ADDR=0.0.0.0\nPORT=20211\n#GLOBAL PATH VARIABLE\n\n# you may want to create a dedicated user and group to run the container with \n# sudo groupadd -g 300 nax-g \n# sudo useradd -u 200 -g 300 nax-u\n# mkdir -p $APP_FOLDER/{db,config,log} \n# chown -R 200:300 $APP_FOLDER\n# chmod -R 775 $APP_FOLDER\n\n# DEVELOPMENT VARIABLES\n# you can create multiple env files called .env.dev1, .env.dev2 etc and use them by running:\n# docker compose --env-file .env.dev1 up -d\n# you can then clone multiple dev copies of NetAlertX just make sure to change the APP_FOLDER and PORT variables in each .env.devX file\n\n</code></pre> <p>To run the container execute: <code>sudo docker-compose --env-file /path/to/.env up</code></p>"},{"location":"DOCKER_PORTAINER/","title":"Deploying NetAlertX in Portainer (via Stacks)","text":"<p>This guide shows you how to set up NetAlertX using Portainer\u2019s Stacks feature.</p> <p></p>"},{"location":"DOCKER_PORTAINER/#1-prepare-your-host","title":"1. Prepare Your Host","text":"<p>Before deploying, make sure you have a folder on your Docker host for NetAlertX data. Replace <code>APP_FOLDER</code> with your preferred location, for example <code>/opt</code> here:</p> <pre><code>mkdir -p /opt/netalertx/config\nmkdir -p /opt/netalertx/db\nmkdir -p /opt/netalertx/log\n</code></pre>"},{"location":"DOCKER_PORTAINER/#2-open-portainer-stacks","title":"2. Open Portainer Stacks","text":"<ol> <li>Log in to your Portainer UI.</li> <li>Navigate to Stacks \u2192 Add stack.</li> <li>Give your stack a name (e.g., <code>netalertx</code>).</li> </ol>"},{"location":"DOCKER_PORTAINER/#3-paste-the-stack-configuration","title":"3. Paste the Stack Configuration","text":"<p>Copy and paste the following YAML into the Web editor:</p> <pre><code>services:\n netalertx:\n container_name: netalertx\n\n # Use this line for stable release\n image: \"ghcr.io/jokob-sk/netalertx:latest\" \n\n # Or, use this for the latest development build\n # image: \"ghcr.io/jokob-sk/netalertx-dev:latest\" \n\n network_mode: \"host\"\n restart: unless-stopped\n\n volumes:\n - ${APP_FOLDER}/netalertx/config:/app/config\n - ${APP_FOLDER}/netalertx/db:/app/db\n # Optional: logs (useful for debugging setup issues, comment out for performance)\n - ${APP_FOLDER}/netalertx/log:/app/log\n\n # API storage options:\n # (Option 1) tmpfs (default, best performance)\n - type: tmpfs\n target: /app/api\n\n # (Option 2) bind mount (useful for debugging)\n # - ${APP_FOLDER}/netalertx/api:/app/api\n\n environment:\n - TZ=${TZ}\n - PORT=${PORT}\n - APP_CONF_OVERRIDE=${APP_CONF_OVERRIDE}\n</code></pre>"},{"location":"DOCKER_PORTAINER/#4-configure-environment-variables","title":"4. Configure Environment Variables","text":"<p>In the Environment variables section of Portainer, add the following:</p> <ul> <li><code>APP_FOLDER=/opt</code> (or wherever you created the directories in step 1)</li> <li><code>TZ=Europe/Berlin</code> (replace with your timezone)</li> <li><code>PORT=22022</code> (or another port if needed)</li> <li><code>APP_CONF_OVERRIDE={\"GRAPHQL_PORT\":\"22023\"}</code> (optional advanced settings)</li> </ul>"},{"location":"DOCKER_PORTAINER/#5-deploy-the-stack","title":"5. Deploy the Stack","text":"<ol> <li>Scroll down and click Deploy the stack.</li> <li>Portainer will pull the image and start NetAlertX.</li> <li>Once running, access the app at:</li> </ol> <pre><code>http://&lt;your-docker-host-ip&gt;:22022\n</code></pre>"},{"location":"DOCKER_PORTAINER/#6-verify-and-troubleshoot","title":"6. Verify and Troubleshoot","text":"<ul> <li>Check logs via Portainer \u2192 Containers \u2192 <code>netalertx</code> \u2192 Logs.</li> <li>Logs are stored under <code>${APP_FOLDER}/netalertx/log</code> if you enabled that volume.</li> </ul> <p>Once the application is running, configure it by reading the initial setup guide, or troubleshoot common issues. </p>"},{"location":"DOCKER_SWARM/","title":"Docker Swarm Deployment Guide (IPvlan)","text":"<p>This guide describes how to deploy NetAlertX in a Docker Swarm environment using an <code>ipvlan</code> network. This enables the container to receive a LAN IP address directly, which is ideal for network monitoring.</p>"},{"location":"DOCKER_SWARM/#step-1-create-an-ipvlan-config-only-network-on-all-nodes","title":"\u2699\ufe0f Step 1: Create an IPvlan Config-Only Network on All Nodes","text":"<p>Run this command on each node in the Swarm.</p> <pre><code>docker network create -d ipvlan \\\n --subnet=192.168.1.0/24 \\ # \ud83d\udd27 Replace with your LAN subnet\n --gateway=192.168.1.1 \\ # \ud83d\udd27 Replace with your LAN gateway\n -o ipvlan_mode=l2 \\\n -o parent=eno1 \\ # \ud83d\udd27 Replace with your network interface (e.g., eth0, eno1)\n --config-only \\\n ipvlan-swarm-config\n</code></pre>"},{"location":"DOCKER_SWARM/#step-2-create-the-swarm-scoped-ipvlan-network-one-time-setup","title":"\ud83d\udda5\ufe0f Step 2: Create the Swarm-Scoped IPvlan Network (One-Time Setup)","text":"<p>Run this on one Swarm manager node only.</p> <pre><code>docker network create -d ipvlan \\\n --scope swarm \\\n --config-from ipvlan-swarm-config \\\n swarm-ipvlan\n</code></pre>"},{"location":"DOCKER_SWARM/#step-3-deploy-netalertx-with-docker-compose","title":"\ud83e\uddfe Step 3: Deploy NetAlertX with Docker Compose","text":"<p>Use the following Compose snippet to deploy NetAlertX with a static LAN IP assigned via the <code>swarm-ipvlan</code> network.</p> <pre><code>services:\n netalertx:\n image: ghcr.io/jokob-sk/netalertx:latest\n ports:\n - 20211:20211\n volumes:\n - /mnt/YOUR_SERVER/netalertx/config:/app/config:rw\n - /mnt/YOUR_SERVER/netalertx/db:/netalertx/app/db:rw\n - /mnt/YOUR_SERVER/netalertx/logs:/netalertx/app/log:rw\n environment:\n - TZ=Europe/London\n - PORT=20211\n networks:\n swarm-ipvlan:\n ipv4_address: 192.168.1.240 # \u26a0\ufe0f Choose a free IP from your LAN\n deploy:\n mode: replicated\n replicas: 1\n restart_policy:\n condition: on-failure\n placement:\n constraints:\n - node.role == manager # \ud83d\udd04 Or use: node.labels.netalertx == true\n\nnetworks:\n swarm-ipvlan:\n external: true\n</code></pre>"},{"location":"DOCKER_SWARM/#notes","title":"\u2705 Notes","text":"<ul> <li>The <code>ipvlan</code> setup allows NetAlertX to have a direct IP on your LAN.</li> <li>Replace <code>eno1</code> with your interface, IP addresses, and volume paths to match your environment.</li> <li>Make sure the assigned IP (<code>192.168.1.240</code> above) is not in use or managed by DHCP.</li> <li>You may also use a node label constraint instead of <code>node.role == manager</code> for more control.</li> </ul>"},{"location":"FILE_PERMISSIONS/","title":"Managing File Permissions for NetAlertX on Nginx with Docker","text":"<p>Tip</p> <p>If you are facing permission issues, try to start the container without mapping your volumes. If that works, then the issue is permission related. You can try e.g., the following command: <code>docker run -d --rm --network=host \\ -e TZ=Europe/Berlin \\ -e PUID=200 -e PGID=200 \\ -e PORT=20211 \\ ghcr.io/jokob-sk/netalertx:latest</code></p> <p>NetAlertX runs on an Nginx web server. On Alpine Linux, Nginx operates as the <code>nginx</code> user (if PUID and GID environment variables are not specified, nginx user UID will be set to 102, and its supplementary group <code>www-data</code> ID to 82). Consequently, files accessed or written by the NetAlertX application are owned by <code>nginx:www-data</code>.</p> <p>Upon starting, NetAlertX changes nginx user UID and www-data GID to specified values (or defaults), and the ownership of files on the host system mapped to <code>/app/config</code> and <code>/app/db</code> in the container to <code>nginx:www-data</code>. This ensures that Nginx can access and write to these files. Since the user in the Docker container is mapped to a user on the host system by ID:GID, the files in <code>/app/config</code> and <code>/app/db</code> on the host system are owned by a user with the same ID and GID (defaults are ID 102 and GID 82). On different systems, this ID:GID may belong to different users, or there may not be a group with ID 82 at all.</p> <p>Option to set specific user UID and GID can be useful for host system users needing to access these files (e.g., backup scripts).</p>"},{"location":"FILE_PERMISSIONS/#permissions-table-for-individual-folders","title":"Permissions Table for Individual Folders","text":"Folder User User ID Group Group ID Permissions Notes <code>/app/config</code> nginx PUID (default 102) www-data PGID (default 82) rwxr-xr-x Ensure <code>nginx</code> can read/write; other users can read if in <code>www-data</code> <code>/app/db</code> nginx PUID (default 102) www-data PGID (default 82) rwxr-xr-x Same as above"},{"location":"FIX_OFFLINE_DETECTION/","title":"Troubleshooting: Devices Show Offline When They Are Online","text":"<p>In some network setups, certain devices may intermittently appear as offline in NetAlertX, even though they are connected and responsive. This issue is often more noticeable with devices that have higher IP addresses within the subnet.</p> <p>Note</p> <p>Network presence graph showing increased drop outs before enabling additional <code>ICMP</code> scans and continuous online presence after following this guide. This graph shows a sudden spike in drop outs probably caused by a device software update. </p>"},{"location":"FIX_OFFLINE_DETECTION/#symptoms","title":"Symptoms","text":"<ul> <li>Devices sporadically show as offline in the presence timeline.</li> <li>This behavior often affects devices with higher IPs (e.g., <code>192.168.1.240+</code>).</li> <li>Presence data appears inconsistent or unreliable despite the device being online.</li> </ul>"},{"location":"FIX_OFFLINE_DETECTION/#cause","title":"Cause","text":"<p>This issue is typically related to scanning limitations:</p> <ul> <li>ARP scan timeouts may prevent full subnet coverage.</li> <li> <p>Sole reliance on ARP can result in missed detections:</p> </li> <li> <p>Some devices (like iPhones) suppress or reject frequent ARP requests.</p> </li> <li> <p>ARP responses may be blocked or delayed due to power-saving features or OS behavior.</p> </li> <li> <p>Scanning frequency conflicts, where devices ignore repeated scans within a short period.</p> </li> </ul>"},{"location":"FIX_OFFLINE_DETECTION/#recommended-fixes","title":"Recommended Fixes","text":"<p>To improve presence accuracy and reduce false offline states:</p>"},{"location":"FIX_OFFLINE_DETECTION/#increase-arp-scan-timeout","title":"\u2705 Increase ARP Scan Timeout","text":"<p>Extend the ARP scanner timeout and DURATION to ensure full subnet coverage:</p> <pre><code>ARPSCAN_RUN_TIMEOUT=360\nARPSCAN_DURATION=30\n</code></pre> <p>Adjust based on your network size and device count.</p>"},{"location":"FIX_OFFLINE_DETECTION/#add-icmp-ping-scanning","title":"\u2705 Add ICMP (Ping) Scanning","text":"<p>Enable the <code>ICMP</code> scan plugin to complement ARP detection. ICMP is often more reliable for detecting active hosts, especially when ARP fails. </p>"},{"location":"FIX_OFFLINE_DETECTION/#use-multiple-detection-methods","title":"\u2705 Use Multiple Detection Methods","text":"<p>A combined approach greatly improves detection robustness:</p> <ul> <li><code>ARPSCAN</code> (default)</li> <li><code>ICMP</code> (ping)</li> <li><code>NMAPDEV</code> (nmap)</li> </ul> <p>This hybrid strategy increases reliability, especially for down detection and alerting. See other plugins that might be compatible with your setup. See benefits and drawbacks of individual scan methods in their respective docs. </p>"},{"location":"FIX_OFFLINE_DETECTION/#results","title":"Results","text":"<p>After increasing the ARP timeout and adding ICMP scanning (on select IP ranges), users typically report:</p> <ul> <li>More consistent presence graphs</li> <li>Fewer false offline events</li> <li>Better coverage across all IP ranges</li> </ul>"},{"location":"FIX_OFFLINE_DETECTION/#summary","title":"Summary","text":"Setting Recommendation <code>ARPSCAN_RUN_TIMEOUT</code> Increase to ensure scans reach all IPs <code>ICMP</code> Scan Enable to detect devices ARP might miss Multi-method Scanning Use a mix of ARP, ICMP, and NMAP-based methods <p>Tip: Each environment is unique. Consider fine-tuning scan settings based on your network size, device behavior, and desired detection accuracy.</p> <p>Let us know in the NetAlertX Discussions if you have further feedback or edge cases.</p> <p>See also Remote Networks for more advanced setups. </p>"},{"location":"FRONTEND_DEVELOPMENT/","title":"Frontend development","text":"<p>This page contains tips for frontend development when extending NetAlertX. Guiding principles are:</p> <ol> <li>Maintainability</li> <li>Extendability</li> <li>Reusability</li> <li>Placing more functionality into Plugins and enhancing core Plugins functionality</li> </ol> <p>That means that, when writing code, focus on reusing what's available instead of writing quick fixes. Or creating reusable functions, instead of bespoke functionaility. </p>"},{"location":"FRONTEND_DEVELOPMENT/#examples","title":"\ud83d\udd0d Examples","text":"<p>Some examples how to apply the above:</p> <p>Example 1</p> <p>I want to implement a scan fucntion. Options would be:</p> <ol> <li>To add a manual scan functionality to the <code>deviceDetails.php</code> page. </li> <li>To create a separate page that handles the execution of the scan.</li> <li>To create a configurable Plugin.</li> </ol> <p>From the above, number 3 would be the most appropriate solution. Then followed by number 2. Number 1 would be approved only in special circumstances.</p> <p>Example 2</p> <p>I want to change the behavior of the application. Options to implement this could be:</p> <ol> <li>Hard-code the changes in the code.</li> <li>Implement the changes and add settings to influence the behavior in the <code>initialize.py</code> file so the user can adjust these.</li> <li>Implement the changes and add settings via a setting-only plugin.</li> <li>Implement the changes in a way so the behavior can be toggled on each plugin so the core capabilities of Plugins get extended.</li> </ol> <p>From the above, number 4 would be the most appropriate solution. Then followed by number 3. Number 1 or 2 would be approved only in special circumstances.</p>"},{"location":"FRONTEND_DEVELOPMENT/#frontend-tips","title":"\ud83d\udca1 Frontend tips","text":"<p>Some useful frontend JavaScript functions:</p> <ul> <li><code>getDevDataByMac(macAddress, devicesColumn)</code> - method to retrieve any device data (database column) based on MAC address in the frontend </li> <li><code>getString(string stringKey)</code> - method to retrieve translated strings in the frontend </li> <li><code>getSetting(string stringKey)</code> - method to retrieve settings in the frontend </li> </ul> <p>Check the common.js file for more frontend functions.</p>"},{"location":"HELPER_SCRIPTS/","title":"NetAlertX Community Helper Scripts Overview","text":"<p>This page provides an overview of community-contributed scripts for NetAlertX. These scripts are not actively maintained and are provided as-is.</p>"},{"location":"HELPER_SCRIPTS/#community-scripts","title":"Community Scripts","text":"<p>You can find all scripts in this scripts GitHub folder.</p> Script Name Description Author Version Release Date New Devices Checkmk Script Checks for new devices in NetAlertX and reports status to Checkmk. N/A 1.0 08-Jan-2025 DB Cleanup Script Queries and removes old device-related entries from the database. laxduke 1.0 23-Dec-2024 OPNsense DHCP Lease Converter Retrieves DHCP lease data from OPNsense and converts it to <code>dnsmasq</code> format. im-redactd 1.0 24-Feb-2025"},{"location":"HELPER_SCRIPTS/#important-notes","title":"Important Notes","text":"<p>Note</p> <p>These scripts are community-supplied and not actively maintained. Use at your own discretion.</p> <p>For detailed usage instructions, refer to each script's documentation in each scripts GitHub folder.</p>"},{"location":"HOME_ASSISTANT/","title":"Home Assistant integration overview","text":"<p>NetAlertX comes with MQTT support, allowing you to show all detected devices as devices in Home Assistant. It also supplies a collection of stats, such as number of online devices.</p> <p>Tip</p> <p>You can install NetAlertX also as a Home Assistant addon via the alexbelgium/hassio-addons repository. This is only possible if you run a supervised instance of Home Assistant. If not, you can still run NetAlertX in a separate Docker container and follow this guide to configure MQTT.</p>"},{"location":"HOME_ASSISTANT/#note","title":"\u26a0 Note","text":"<ul> <li>Please note that discovery takes about ~10s per device.</li> <li>Deleting of devices is not handled automatically. Please use MQTT Explorer to delete devices in the broker (Home Assistant), if needed. </li> <li>For optimization reasons, the devices are not always fully synchronized. You can delete Plugin objects as described in the MQTT plugin docs to force a full synchronization.</li> </ul>"},{"location":"HOME_ASSISTANT/#guide","title":"\ud83e\udded Guide","text":"<p>\ud83d\udca1 This guide was tested only with the Mosquitto MQTT broker</p> <ol> <li> <p>Enable Mosquitto MQTT in Home Assistant by following the documentation</p> </li> <li> <p>Configure a user name and password on your broker.</p> </li> <li> <p>Note down the following details that you will need to configure NetAlertX:</p> <ul> <li>MQTT host url (usually your Home Assistant IP)</li> <li>MQTT broker port</li> <li>User</li> <li>Password</li> </ul> </li> <li> <p>Open the NetAlertX &gt; Settings &gt; MQTT settings group</p> <ul> <li>Enable MQTT</li> <li>Fill in the details from above</li> <li>Fill in remaining settings as per description</li> <li>set MQTT_RUN to schedule or on_notification depending on requirements</li> </ul> </li> </ol> <p> </p>"},{"location":"HOME_ASSISTANT/#screenshots","title":"\ud83d\udcf7 Screenshots","text":""},{"location":"HOME_ASSISTANT/#troubleshooting","title":"Troubleshooting","text":"<p>If you can't see all devices detected, run <code>sudo arp-scan --interface=eth0 192.168.1.0/24</code> (change these based on your setup, read Subnets docs for details). This command has to be executed the NetAlertX container, not in the Home Assistant container.</p> <p>You can access the NetAlertX container via Portainer on your host or via ssh. The container name will be something like <code>addon_db21ed7f_netalertx</code> (you can copy the <code>db21ed7f_netalertx</code> part from the browser when accessing the UI of NetAlertX). </p>"},{"location":"HOME_ASSISTANT/#accessing-the-netalertx-container-via-ssh","title":"Accessing the NetAlertX container via SSH","text":"<ol> <li>Log into your Home Assistant host via SSH</li> </ol> <pre><code>local@local:~ $ ssh pi@192.168.1.9\n</code></pre> <ol> <li>Find the NetAlertX container name, in this case <code>addon_db21ed7f_netalertx</code></li> </ol> <pre><code>pi@raspberrypi:~ $ sudo docker container ls | grep netalertx\n06c540d97f67 ghcr.io/alexbelgium/netalertx-armv7:25.3.1 \"/init\" 6 days ago Up 6 days (healthy) addon_db21ed7f_netalertx\n</code></pre> <ol> <li>SSH into the NetAlertX cointainer</li> </ol> <pre><code>pi@raspberrypi:~ $ sudo docker exec -it addon_db21ed7f_netalertx /bin/sh\n/ #\n</code></pre> <ol> <li>Execute a test <code>asrp-scan</code> scan</li> </ol> <pre><code>/ # sudo arp-scan --ignoredups --retry=6 192.168.1.0/24 --interface=eth0\nInterface: eth0, type: EN10MB, MAC: dc:a6:32:73:8a:b1, IPv4: 192.168.1.9\nStarting arp-scan 1.10.0 with 256 hosts (https://github.com/royhills/arp-scan)\n192.168.1.1 74:ac:b9:54:09:fb Ubiquiti Networks Inc.\n192.168.1.21 74:ac:b9:ad:c3:30 Ubiquiti Networks Inc.\n192.168.1.58 1c:69:7a:a2:34:7b EliteGroup Computer Systems Co., LTD\n192.168.1.57 f4:92:bf:a3:f3:56 Ubiquiti Networks Inc.\n...\n</code></pre> <p>If your result doesn't contain results similar to the above, double check your subnet, interface and if you are dealing with an inaccessible network segment, read the Remote networks documentation.</p>"},{"location":"HW_INSTALL/","title":"How to install NetAlertX on the server hardware","text":"<p>To download and install NetAlertX on the hardware/server directly use the <code>curl</code> or <code>wget</code> commands at the bottom of this page.</p> <p>Note</p> <p>This is an Experimental feature \ud83e\uddea and it relies on community support.</p> <p>\ud83d\ude4f Looking for maintainers for this installation method \ud83d\ude42 Current community volunteers: - slammingprogramming - ingoratsdorf</p> <p>There is no guarantee that the install script or any other script will gracefully handle other installed software. Data loss is a possibility, it is recommended to install NetAlertX using the supplied Docker image.</p> <p>Warning</p> <p>A warning to the installation method below: Piping to bash is controversial and may</p> <p>be dangerous, as you cannot see the code that's about to be executed on your system.</p> <p>If you trust this repo, you can download the install script via one of the methods (curl/wget) below and it will fo its best to install NetAlertX on your system.</p> <p>Alternatively you can download the installation script from the repository and check the code yourself.</p> <p>NetAlertX will be installed in <code>/app</code> and run on port number <code>20211</code>.</p> <p>Some facts about what and where something will be changed/installed by the HW install setup (may not contain everything!):</p> <ul> <li>dependencies will be installed from the respective system repos</li> <li>required python modules will be installed</li> <li><code>/app</code> directory will be deleted and newly created</li> <li><code>/app</code> will contain the whole repository (downloaded by the install script)</li> <li>The default NGINX site <code>/etc/nginx/sites-enabled/default</code> will be disabled (sym-link deleted or backed up to <code>sites-available</code>)</li> <li><code>/var/www/html/netalertx</code> directory will be deleted and newly created</li> <li><code>/etc/nginx/conf.d/netalertx.conf</code> will be sym-linked to the appropriate installer location (depending on your system installer script)</li> <li>Some files (IEEE device vendors info, ...) will be created in the directory where the installation script is executed</li> </ul>"},{"location":"HW_INSTALL/#limitations","title":"Limitations","text":"<ul> <li>No system service is provided. NetAlertX must be started using <code>/app/install/&lt;system&gt;/start.&lt;system&gt;.sh</code>.</li> <li>No checks for other running software is done.</li> <li>Only tested to work on the system listed in the install directory.</li> <li>EXPERIMENTAL and not recommended way to install NetAlertX.</li> </ul> <p>Tip</p> <p>If the below fails try grabbing and installing one of the previous releases and run the installation from the zip package.</p> <p>These commands will download the <code>install.debian12.sh</code> script from the GitHub repository, make it executable with <code>chmod</code>, and then run it using <code>./install.debian12.sh</code>.</p> <p>Make sure you have the necessary permissions to execute the script.</p>"},{"location":"HW_INSTALL/#debian-12-bookworm","title":"\ud83d\udce5 Debian 12 (Bookworm)","text":""},{"location":"HW_INSTALL/#installation-via-curl","title":"Installation via curl","text":"<pre><code>curl -o install.debian12.sh https://raw.githubusercontent.com/jokob-sk/NetAlertX/main/install/debian12/install.debian12.sh &amp;&amp; sudo chmod +x install.debian12.sh &amp;&amp; sudo ./install.debian12.sh\n</code></pre>"},{"location":"HW_INSTALL/#installation-via-wget","title":"Installation via wget","text":"<pre><code>wget https://raw.githubusercontent.com/jokob-sk/NetAlertX/main/install/debian12/install.debian12.sh -O install.debian12.sh &amp;&amp; sudo chmod +x install.debian12.sh &amp;&amp; sudo ./install.debian12.sh\n</code></pre>"},{"location":"HW_INSTALL/#ubuntu-24-noble-numbat","title":"\ud83d\udce5 Ubuntu 24 (Noble Numbat)","text":""},{"location":"HW_INSTALL/#installation-via-curl_1","title":"Installation via curl","text":"<pre><code>curl -o install.sh https://raw.githubusercontent.com/jokob-sk/NetAlertX/main/install/ubuntu24/install.sh &amp;&amp; sudo chmod +x install.sh &amp;&amp; sudo ./install.sh\n</code></pre>"},{"location":"HW_INSTALL/#installation-via-wget_1","title":"Installation via wget","text":"<pre><code>wget https://raw.githubusercontent.com/jokob-sk/NetAlertX/main/install/ubuntu24/install.sh -O install.sh &amp;&amp; sudo chmod +x install.sh &amp;&amp; sudo ./install.sh\n</code></pre>"},{"location":"HW_INSTALL/#bare-metal-proxmox","title":"\ud83d\udce5 Bare Metal - Proxmox","text":"<p>Note</p> <p>Use this on a clean LXC/VM for Debian 13 OR Ubuntu 24. The Scipt will detect OS and build acordingly.</p>"},{"location":"HW_INSTALL/#installation-via-wget_2","title":"Installation via wget","text":"<pre><code>wget https://raw.githubusercontent.com/jokob-sk/NetAlertX/main/install/proxmox/proxmox-install-netalertx.sh -O proxmox-install-netalertx.sh &amp;&amp; chmod +x proxmox-install-netalertx.sh &amp;&amp; ./proxmox-install-netalertx.sh\n</code></pre>"},{"location":"ICONS/","title":"Icons","text":""},{"location":"ICONS/#icons-overview","title":"Icons overview","text":"<p>Icons are used to visually distinguish devices in the app in most of the device listing tables and the network tree. </p> <p></p>"},{"location":"ICONS/#icons-support","title":"Icons Support","text":"<p>Two types of icons are suported:</p> <ul> <li>Free Font Awesome icons (up-to v 6.4.0)</li> <li>SVG icons (for example from iconify.design)</li> </ul> <p>You can assign icons individually on each device in the Details tab.</p>"},{"location":"ICONS/#adding-new-icons","title":"Adding new icons","text":"<ol> <li>You can get an SVG or a Font awesome HTML code</li> </ol> <p>Copying the SVG (for example from iconify.design): </p> <p></p> <p>Copying the HTML code from Font Awesome.</p> <p></p> <ol> <li>Navigate to the device you want to use the icon on and click the \"+\" icon:</li> </ol> <p></p> <ol> <li>Paste in the copied HTML or SVG code and click \"OK\":</li> </ol> <p></p> <ol> <li>\"Save\" the device</li> </ol> <p>Note</p> <p>If you want to mass-apply an icon to all devices of the same device type (Field: Type), you can click the mass-copy button (next to the \"+\" button). A confirmation prompt is displayed. If you proceed, icons of all devices set to the same device type as the current device, will be overwritten with the current device's icon.</p> <ul> <li>The dropdown contains all icons already used in the app for device icons. You might need to navigate away or refresh the page once you add a new icon. </li> </ul>"},{"location":"ICONS/#font-awesome-pro-icons","title":"Font Awesome Pro icons","text":"<p>If you own the premium package of Font Awesome icons you can mount it in your Docker container the following way:</p> <pre><code>/font-awesome:/app/front/lib/font-awesome:ro\n</code></pre> <p>You can use the full range of Font Awesome icons afterwards. </p>"},{"location":"INITIAL_SETUP/","title":"\u26a1 Quick Start Guide","text":"<p>Get NetAlertX up and running in a few simple steps.</p>"},{"location":"INITIAL_SETUP/#1-configure-scanner-plugins","title":"1. Configure Scanner Plugin(s)","text":"<p>Tip</p> <p>Enable additional plugins under Settings \u2192 <code>LOADED_PLUGINS</code>. Make sure to save your changes and reload the page to activate them. </p> <p>Initial configuration: <code>ARPSCAN</code>, <code>INTRNT</code></p> <p>Note</p> <p><code>ARPSCAN</code> and <code>INTRNT</code> scan the current network. You can complement them with other <code>\ud83d\udd0d dev scanner</code> plugins like <code>NMAPDEV</code>, or import devices using <code>\ud83d\udce5 importer</code> plugins. See the Subnet &amp; VLAN Setup Guide and Remote Networks for advanced configurations.</p>"},{"location":"INITIAL_SETUP/#2-choose-a-publisher-plugin","title":"2. Choose a Publisher Plugin","text":"<p>Initial configuration: <code>SMTP</code></p> <p>Note</p> <p>Configure your SMTP settings or enable additional <code>\u25b6\ufe0f publisher</code> plugins to send alerts. For more flexibility, try \ud83d\udcda <code>_publisher_apprise</code>, which supports over 80 notification services.</p>"},{"location":"INITIAL_SETUP/#3-set-up-a-network-topology-diagram","title":"3. Set Up a Network Topology Diagram","text":"<p>Initial configuration: The app auto-selects a root node (MAC <code>internet</code>) and attempts to identify other network devices by vendor or name.</p> <p>Note</p> <p>Visualize and manage your network using the Network Guide. Some plugins (e.g., <code>UNFIMP</code>) build the topology automatically, or you can use Custom Workflows to generate it based on your own rules.</p>"},{"location":"INITIAL_SETUP/#4-configure-notifications","title":"4. Configure Notifications","text":"<p>Initial configuration: Notifies on <code>new_devices</code>, <code>down_devices</code>, and <code>events</code> as defined in <code>NTFPRCS_INCLUDED_SECTIONS</code>.</p> <p>Note</p> <p>Notification settings support global, plugin-specific, and per-device rules. For fine-tuning, refer to the Notification Guide.</p>"},{"location":"INITIAL_SETUP/#5-set-up-workflows","title":"5. Set Up Workflows","text":"<p>Initial configuration: N/A</p> <p>Note</p> <p>Automate responses to device status changes, group management, topology updates, and more. See the Workflows Guide to simplify your network operations.</p>"},{"location":"INITIAL_SETUP/#6-backup-your-configuration","title":"6. Backup Your Configuration","text":"<p>Initial configuration: The <code>CSVBCKP</code> plugin creates a daily backup to <code>/config/devices.csv</code>.</p> <p>Note</p> <p>For a complete backup strategy, follow the Backup Guide.</p>"},{"location":"INITIAL_SETUP/#7-optional-create-custom-plugins","title":"7. (Optional) Create Custom Plugins","text":"<p>Initial configuration: N/A</p> <p>Note</p> <p>Build your own scanner, importer, or publisher plugin. See the Plugin Development Guide and included video tutorials.</p>"},{"location":"INITIAL_SETUP/#recommended-guides","title":"\ud83d\udcc1 Recommended Guides","text":"<ul> <li>\ud83d\udcd8 PiHole Setup Guide</li> <li>\ud83d\udcd8 CSV Import Method</li> <li>\ud83d\udcd8 Community Guides (Chinese, Korean, German, French)</li> </ul>"},{"location":"INITIAL_SETUP/#troubleshooting-help","title":"\ud83d\udee0\ufe0f Troubleshooting &amp; Help","text":"<p>Before opening a new issue:</p> <ul> <li>\ud83d\udcd8 Common Issues</li> <li>\ud83e\uddf0 Debugging Tips</li> <li>\u2705 Browse resolved GitHub issues</li> </ul> <p>Let me know if you want a condensed README version, separate pages for each section, or UI copy based on this!</p>"},{"location":"INSTALLATION/","title":"Installation","text":""},{"location":"INSTALLATION/#installation-options","title":"Installation options","text":"<p>NetAlertX can be installed several ways. The best supported option is Docker, followed by a supervised Home Assistant instance, as an Unraid app, and lastly, on bare metal. </p> <ul> <li>[Installation] Docker (recommended) </li> <li>[Installation] Home Assistant </li> <li>[Installation] Unraid App </li> <li>[Installation] Bare metal (experimental - looking for maintainers) </li> </ul>"},{"location":"INSTALLATION/#help","title":"Help","text":"<p>If facing issues, please spend a few minutes seraching. </p> <ul> <li>Check common issues</li> <li>Have a look at Community guides </li> <li>Search closed or open issues or discussions </li> <li>Check Discord</li> </ul> <p>Note</p> <p>If you can't find a solution anywhere, ask in Discord if you think it's a quick question, otherwise open a new issue. Please fill in as much as possible to speed up the help process. </p>"},{"location":"LOGGING/","title":"Logging","text":"<p>NetAlertX comes with several logs that help to identify application issues. </p> <p>For plugin-specific log debugging, please read the Debug Plugins guide.</p> <p>When debugging any issue, increase the <code>LOG_LEVEL</code> Setting as per the Debug tips documentation.</p>"},{"location":"LOGGING/#main-logs","title":"Main logs","text":"<p>You can find most of the logs exposed in the UI under Maintenance -&gt; Logs. </p> <p>If the UI is inaccessible, you can access them under <code>/app/log</code>.</p> <p></p> <p>In the Maintennace -&gt; Logs you can Purge logs, download the full log file or Filter the lines with some substring to narrow down your search. </p>"},{"location":"LOGGING/#plugin-logging","title":"Plugin logging","text":"<p>If a Plugin supplies data to the main app it's done either vie a SQL query or via a script that updates the <code>last_result.log</code> file in the plugin log folder (<code>app/log/plugins/</code>). These files are processed at the end of the scan and deleted on successful processing.</p> <p>The data is in most of the cases then displayed in the application under Integrations -&gt; Plugins (or Device -&gt; Plugins if the plugin is supplying device-specific data). </p> <p></p>"},{"location":"MIGRATION/","title":"Migration form PiAlert to NetAlertX","text":"<p>Warning</p> <p>Follow this guide only after you you downloaded and started a version of NetAlertX prior to v25.6.7 (e.g. <code>docker pull ghcr.io/jokob-sk/netalertx:25.5.24</code>) at least once after previously using the PiAlert image. Later versions don't support migration and devices and settings will have to migrated manually, e.g. via CSV import.</p>"},{"location":"MIGRATION/#steps","title":"STEPS:","text":"<p>Tip</p> <p>In short: The application will auto-migrate the database, config, and all device information. A ticker message on top will be displayed until you update your docker mount points. It's always good to have a backup strategy in place.</p> <ol> <li>Backup your current config and database (optional <code>devices.csv</code> to have a backup) (See bellow tip if facing issues)</li> <li>Stop the container </li> <li>Update the Docker file mount locations in your <code>docker-compose.yml</code> or docker run command (See bellow New Docker mount locations). </li> <li>Rename the DB and conf files to <code>app.db</code> and <code>app.conf</code> and place them in the appropriate location.</li> <li>Start the Container</li> </ol> <p>Tip</p> <p>If you have troubles accessing past backups, config or database files you can copy them into the newly mapped directories, for example by running this command in the container: <code>cp -r /app/config /home/pi/pialert/config/old_backup_files</code>. This should create a folder in the <code>config</code> directory called <code>old_backup_files</code> conatining all the files in that location. Another approach is to map the old location and the new one at the same time to copy things over.</p>"},{"location":"MIGRATION/#new-docker-mount-locations","title":"New Docker mount locations","text":"<p>The application installation folder in the docker container has changed from <code>/home/pi/pialert</code> to <code>/app</code>. That means the new mount points are:</p> Old mount point New mount point <code>/home/pi/pialert/config</code> <code>/app/config</code> <code>/home/pi/pialert/db</code> <code>/app/db</code> <p>If you were mounting files directly, please note the file names have changed:</p> Old file name New file name <code>pialert.conf</code> <code>app.conf</code> <code>pialert.db</code> <code>app.db</code> <p>Note</p> <p>The application uses symlinks linking the old db and config locations to the new ones, so data loss should not occur. Backup strategies are still recommended to backup your setup.</p>"},{"location":"MIGRATION/#examples","title":"Examples","text":"<p>Examples of docker files with the new mount points.</p>"},{"location":"MIGRATION/#example-1-mapping-folders","title":"Example 1: Mapping folders","text":""},{"location":"MIGRATION/#old-docker-composeyml","title":"Old docker-compose.yml","text":"<pre><code>services:\n pialert:\n container_name: pialert\n # use the below line if you want to test the latest dev image\n # image: \"ghcr.io/jokob-sk/netalertx-dev:latest\" \n image: \"jokobsk/pialert:latest\" \n network_mode: \"host\" \n restart: unless-stopped\n volumes:\n - local/path/config:/home/pi/pialert/config \n - local/path/db:/home/pi/pialert/db \n # (optional) useful for debugging if you have issues setting up the container\n - local/path/logs:/home/pi/pialert/front/log\n environment:\n - TZ=Europe/Berlin \n - PORT=20211\n</code></pre>"},{"location":"MIGRATION/#new-docker-composeyml","title":"New docker-compose.yml","text":"<pre><code>services:\n netalertx: # \u26a0 This has changed (\ud83d\udfe1optional) \n container_name: netalertx # \u26a0 This has changed (\ud83d\udfe1optional) \n # use the below line if you want to test the latest dev image\n # image: \"ghcr.io/jokob-sk/netalertx-dev:latest\" \n image: \"ghcr.io/jokob-sk/netalertx:latest\" # \u26a0 This has changed (\ud83d\udfe1optional/\ud83d\udd3arequired in future) \n network_mode: \"host\" \n restart: unless-stopped\n volumes:\n - local/path/config:/app/config # \u26a0 This has changed (\ud83d\udd3arequired) \n - local/path/db:/app/db # \u26a0 This has changed (\ud83d\udd3arequired) \n # (optional) useful for debugging if you have issues setting up the container\n - local/path/logs:/app/log # \u26a0 This has changed (\ud83d\udfe1optional) \n environment:\n - TZ=Europe/Berlin \n - PORT=20211\n</code></pre>"},{"location":"MIGRATION/#example-2-mapping-files","title":"Example 2: Mapping files","text":"<p>Note</p> <p>The recommendation is to map folders as in Example 1, map files directly only when needed.</p>"},{"location":"MIGRATION/#old-docker-composeyml_1","title":"Old docker-compose.yml","text":"<pre><code>services:\n pialert:\n container_name: pialert\n # use the below line if you want to test the latest dev image\n # image: \"ghcr.io/jokob-sk/netalertx-dev:latest\" \n image: \"jokobsk/pialert:latest\" \n network_mode: \"host\" \n restart: unless-stopped\n volumes:\n - local/path/config/pialert.conf:/home/pi/pialert/config/pialert.conf \n - local/path/db/pialert.db:/home/pi/pialert/db/pialert.db \n # (optional) useful for debugging if you have issues setting up the container\n - local/path/logs:/home/pi/pialert/front/log\n environment:\n - TZ=Europe/Berlin \n - PORT=20211\n</code></pre>"},{"location":"MIGRATION/#new-docker-composeyml_1","title":"New docker-compose.yml","text":"<pre><code>services:\n netalertx: # \u26a0 This has changed (\ud83d\udfe1optional) \n container_name: netalertx # \u26a0 This has changed (\ud83d\udfe1optional) \n # use the below line if you want to test the latest dev image\n # image: \"ghcr.io/jokob-sk/netalertx-dev:latest\" \n image: \"ghcr.io/jokob-sk/netalertx:latest\" # \u26a0 This has changed (\ud83d\udfe1optional/\ud83d\udd3arequired in future) \n network_mode: \"host\" \n restart: unless-stopped\n volumes:\n - local/path/config/app.conf:/app/config/app.conf # \u26a0 This has changed (\ud83d\udd3arequired) \n - local/path/db/app.db:/app/db/app.db # \u26a0 This has changed (\ud83d\udd3arequired) \n # (optional) useful for debugging if you have issues setting up the container\n - local/path/logs:/app/log # \u26a0 This has changed (\ud83d\udfe1optional) \n environment:\n - TZ=Europe/Berlin \n - PORT=20211\n</code></pre>"},{"location":"NAME_RESOLUTION/","title":"Device Name Resolution","text":"<p>Name resolution in NetAlertX relies on multiple plugins to resolve device names from IP addresses. If you are seeing <code>(name not found)</code> as device names, follow these steps to diagnose and fix the issue.</p> <p>Tip</p> <p>Before proceeding, make sure Reverse DNS is enabled on your network. You can control how names are handled and cleaned using the <code>NEWDEV_NAME_CLEANUP_REGEX</code> setting. To auto-update Fully Qualified Domain Names (FQDN), enable the <code>REFRESH_FQDN</code> setting.</p>"},{"location":"NAME_RESOLUTION/#required-plugins","title":"Required Plugins","text":"<p>For best results, ensure the following name resolution plugins are enabled:</p> <ul> <li>AVAHISCAN \u2013 Uses mDNS/Avahi to resolve local network names.</li> <li>NBTSCAN \u2013 Queries NetBIOS to find device names.</li> <li>NSLOOKUP \u2013 Performs standard DNS lookups.</li> <li>DIGSCAN \u2013 Performs Name Resolution with the Dig utility (DNS).</li> </ul> <p>You can check which plugins are active in your Settings section and enable any that are missing.</p> <p>There are other plugins that can supply device names as well, but they rely on bespoke hardware and services. See Plugins overview for details and look for plugins with name discovery (\ud83c\udd8e) features.</p>"},{"location":"NAME_RESOLUTION/#checking-logs","title":"Checking Logs","text":"<p>If names are not resolving, check the logs for errors or timeouts.</p> <p>See how to explore logs in the Logging guide.</p> <p>Logs will show which plugins attempted resolution and any failures encountered.</p>"},{"location":"NAME_RESOLUTION/#adjusting-timeout-settings","title":"Adjusting Timeout Settings","text":"<p>If resolution is slow or failing due to timeouts, increase the timeout settings in your configuration, for example.</p> <pre><code>NSLOOKUP_RUN_TIMEOUT = 30\n</code></pre> <p>Raising the timeout may help if your network has high latency or slow DNS responses.</p>"},{"location":"NAME_RESOLUTION/#checking-plugin-objects","title":"Checking Plugin Objects","text":"<p>Each plugin stores results in its respective object. You can inspect these objects to see if they contain valid name resolution data.</p> <p>See Logging guide and Debug plugins guides for details.</p> <p>If the object contains no results, the issue may be with DNS settings or network access.</p>"},{"location":"NAME_RESOLUTION/#improving-name-resolution","title":"Improving name resolution","text":"<p>For more details how to improve name resolution refer to the Reverse DNS Documentation.</p>"},{"location":"NETWORK_TREE/","title":"Network Topology","text":""},{"location":"NETWORK_TREE/#how-to-set-up-your-network-page","title":"How to Set Up Your Network Page","text":"<p>The Network page lets you map how devices connect \u2014 visually and logically. It\u2019s especially useful for planning infrastructure, assigning parent-child relationships, and spotting gaps.</p> <p></p> <p>To get started, you\u2019ll need to define at least one root node and mark certain devices as network nodes (like Switches or Routers).</p> <p>Start by creating a root device with the MAC address <code>Internet</code>, if the application didn\u2019t create one already. This special MAC address (<code>Internet</code>) is required for the root network node \u2014 no other value is currently supported. Set its Type to a valid network type \u2014 such as <code>Router</code> or <code>Gateway</code>.</p> <p>Tip</p> <p>If you don\u2019t have one, use the Create new device button on the Devices page to add a root device.</p>"},{"location":"NETWORK_TREE/#quick-setup","title":"\u26a1 Quick Setup","text":"<ol> <li>Open the device you want to use as a network node (e.g. a Switch).</li> <li>Set its Type to one of the following: <code>AP</code>, <code>Firewall</code>, <code>Gateway</code>, <code>PLC</code>, <code>Powerline</code>, <code>Router</code>, <code>Switch</code>, <code>USB LAN Adapter</code>, <code>USB WIFI Adapter</code>, <code>WLAN</code> (Or add custom types under Settings \u2192 General \u2192 <code>NETWORK_DEVICE_TYPES</code>.)</li> <li>Save the device.</li> <li>Go to the Network page \u2014 supported device types will appear as tabs.</li> <li>Use the Assign button to connect unassigned devices to a network node.</li> <li>If the Port is <code>0</code> or empty, a Wi-Fi icon is shown. Otherwise, an Ethernet icon appears.</li> </ol> <p>Note</p> <p>Use bulk editing with CSV Export to fix <code>Internet</code> root assignments or update many devices at once.</p>"},{"location":"NETWORK_TREE/#example-setting-up-a-raspberrypi-as-a-switch","title":"Example: Setting up a <code>raspberrypi</code> as a Switch","text":"<p>Let\u2019s walk through setting up a device named <code>raspberrypi</code> to act as a network Switch that other devices connect through.</p>"},{"location":"NETWORK_TREE/#1-set-device-type-and-parent","title":"1. Set Device Type and Parent","text":"<ul> <li>Go to the Devices page </li> <li>Open the device detail view for <code>raspberrypi</code></li> <li>In the Type dropdown, select <code>Switch</code></li> </ul> <ul> <li>Optionally assign a Parent Node (where this device connects to) and the Relationship type of the connection. The <code>nic</code> relationship type can affect parent notifications \u2014 see the setting description and Notifications documentation for more.</li> </ul> <p>Note</p> <p>Only certain device types can act as network nodes: <code>AP</code>, <code>Firewall</code>, <code>Gateway</code>, <code>Hypervisor</code>, <code>PLC</code>, <code>Powerline</code>, <code>Router</code>, <code>Switch</code>, <code>USB LAN Adapter</code>, <code>USB WIFI Adapter</code>, <code>WLAN</code> You can add custom types via the <code>NETWORK_DEVICE_TYPES</code> setting.</p> <ul> <li>Click Save</li> </ul>"},{"location":"NETWORK_TREE/#2-confirm-the-device-appears-as-a-network-node","title":"2. Confirm The Device Appears as a Network Node","text":"<p>You can confirm that <code>raspberrypi</code> now acts as a network device in two places:</p> <ul> <li>Navigate to a different device and verify that <code>raspberrypi</code> now appears as an option for a Parent Node:</li> </ul> <p></p> <ul> <li>Go to the Network page \u2014 you'll now see a <code>raspberrypi</code> tab, meaning it's recognized as a network node (Switch):</li> </ul> <p></p> <ul> <li>You can now assign other devices to it.</li> </ul>"},{"location":"NETWORK_TREE/#3-assign-connected-devices","title":"3. Assign Connected Devices","text":"<ul> <li>Use the Assign button to link other devices (e.g. PCs) to <code>raspberrypi</code>.</li> <li>After assigning, connected devices will appear beneath the <code>raspberrypi</code> switch node. </li> </ul> <ul> <li>Relationship lines may vary in color based on the selected Relationship type. These are editable on the device details page where you can also assign a parent node.</li> </ul> <p>Hovering over devices in the tree reveals connection details and tooltips for quick inspection.</p> <p>Note</p> <p>Selecting certain relationship types hides the device in the default device views. You can change this behavior by adjusting the <code>UI_hide_rel_types</code> setting, which by default is set to <code>[\"nic\",\"virtual\"]</code>. This means devices with <code>devParentRelType</code> set to <code>nic</code> or <code>virtual</code> will not be shown. All devices, regardless of relationship type, are always accessible in the All devices view.</p>"},{"location":"NETWORK_TREE/#summary","title":"\u2705 Summary","text":"<p>To configure devices on the Network page:</p> <ul> <li>Ensure a device with MAC <code>Internet</code> is set up as the root</li> <li>Assign valid Type values to switches, routers, and other supported nodes that represent network devices</li> <li>Use the Assign button to connect devices logically to their parent node</li> </ul> <p>Need to reset or undo changes? Use backups or bulk editing to manage devices at scale. You can also automate device assignment with Workflows.</p>"},{"location":"NOTIFICATIONS/","title":"Notifications \ud83d\udce7","text":"<p>There are 4 ways how to influence notifications:</p> <ol> <li>On the device itself</li> <li>On the settings of the plugin</li> <li>Globally</li> <li>Ignoring devices</li> </ol> <p>Note</p> <p>It's recommended to use the same schedule interval for all plugins responsible for scanning devices, otherwise false positives might be reported if different devices are discovered by different plugins. Check the Settings &gt; Enabled settings section for a warning: </p>"},{"location":"NOTIFICATIONS/#device-settings","title":"Device settings \ud83d\udcbb","text":"<p>The following device properties influence notifications. You can:</p> <ol> <li>Alert Events - Enables alerts of connections, disconnections, IP changes (down and down reconnected notifications are still sent even if this is disabled).</li> <li>Alert Down - Alerts when a device goes down. This setting overrides a disabled Alert Events setting, so you will get a notification of a device going down even if you don't have Alert Events ticked. Disabling this will disable down and down reconnected notifications on the device.</li> <li>Skip repeated notifications, if for example you know there is a temporary issue and want to pause the same notification for this device for a given time.</li> <li>Require NICs Online - Indicates whether this device should be considered online only if all associated NICs (devices with the <code>nic</code> relationship type) are online. If disabled, the device is considered online if any NIC is online. If a NIC is online it sets the parent (this) device's status to online irrespectivelly of the detected device's status. The Relationship type is set on the childern device.</li> </ol> <p>Note</p> <p>Please read through the NTFPRCS plugin documentation to understand how device and global settings influence the notification processing.</p>"},{"location":"NOTIFICATIONS/#plugin-settings","title":"Plugin settings \ud83d\udd0c","text":"<p>On almost all plugins there are 2 core settings, <code>&lt;plugin&gt;_WATCH</code> and <code>&lt;plugin&gt;_REPORT_ON</code>. </p> <ol> <li><code>&lt;plugin&gt;_WATCH</code> specifies the columns which the app should watch. If watched columns change the device state is considered changed. This changed status is then used to decide to send out notifications based on the <code>&lt;plugin&gt;_REPORT_ON</code> setting. </li> <li><code>&lt;plugin&gt;_REPORT_ON</code> let's you specify on which events the app should notify you. This is related to the <code>&lt;plugin&gt;_WATCH</code> setting. So if you select <code>watched-changed</code> and in <code>&lt;plugin&gt;_WATCH</code> you only select <code>Watched_Value1</code>, then a notification is triggered if <code>Watched_Value1</code> is changed from the previous value, but no notification is send if <code>Watched_Value2</code> changes. </li> </ol> <p>Click the Read more in the docs. Link at the top of each plugin to get more details on how the given plugin works. </p>"},{"location":"NOTIFICATIONS/#global-settings","title":"Global settings \u2699","text":"<p>In Notification Processing settings, you can specify blanket rules. These allow you to specify exceptions to the Plugin and Device settings and will override those.</p> <ol> <li>Notify on (<code>NTFPRCS_INCLUDED_SECTIONS</code>) allows you to specify which events trigger notifications. Usual setups will have <code>new_devices</code>, <code>down_devices</code>, and possibly <code>down_reconnected</code> set. Including <code>plugin</code> (dependenton the Plugin <code>&lt;plugin&gt;_WATCH</code> and <code>&lt;plugin&gt;_REPORT_ON</code> settings) and <code>events</code> (dependent on the on-device Alert Events setting) might be too noisy for most setups. More info in the NTFPRCS plugin on what events these selections include. </li> <li>Alert down after (<code>NTFPRCS_alert_down_time</code>) is useful if you want to wait for some time before the system sends out a down notification for a device. This is related to the on-device Alert down setting and only devices with this checked will trigger a down notification.</li> <li>A filter to allow you to set device-specific exceptions to New devices being added to the app.</li> <li>A filter to allow you to set device-specific exceptions to generated Events.</li> </ol>"},{"location":"NOTIFICATIONS/#ignoring-devices","title":"Ignoring devices \ud83d\udd15","text":"<p>You can completely ignore detected devices globally. This could be because your instance detects docker containers, you want to ignore devices from a specific manufacturer via MAC rules or you want to ignore devices on a specific IP range. </p> <ol> <li>Ignored MACs (<code>NEWDEV_ignored_MACs</code>) - List of MACs to ignore.</li> <li>Ignored IPs (<code>NEWDEV_ignored_IPs</code>) - List of IPs to ignore. </li> </ol>"},{"location":"PERFORMANCE/","title":"Performance Optimization Guide","text":"<p>There are several ways to improve the application's performance. The application has been tested on a range of devices, from a Raspberry Pi 4 to NAS and NUC systems. If you are running the application on a lower-end device, carefully fine-tune the performance settings to ensure an optimal user experience.</p>"},{"location":"PERFORMANCE/#common-causes-of-slowness","title":"Common Causes of Slowness","text":"<p>Performance issues are usually caused by:</p> <ul> <li>Incorrect settings \u2013 The app may restart unexpectedly. Check <code>app.log</code> under Maintenance \u2192 Logs for details.</li> <li>Too many background processes \u2013 Disable unnecessary scanners.</li> <li>Long scan durations \u2013 Limit the number of scanned devices.</li> <li>Excessive disk operations \u2013 Optimize scanning and logging settings.</li> <li>Failed maintenance plugins \u2013 Ensure maintenance tasks are running properly.</li> </ul> <p>The application performs regular maintenance and database cleanup. If these tasks fail, performance may degrade.</p>"},{"location":"PERFORMANCE/#database-and-log-file-size","title":"Database and Log File Size","text":"<p>A large database or oversized log files can slow down performance. You can check database and table sizes on the Maintenance page.</p> <p></p> <p>Note</p> <ul> <li>For ~100 devices, the database should be around 50MB.</li> <li>No table should exceed 10,000 rows in a healthy system.</li> <li>These numbers vary based on network activity and settings.</li> </ul>"},{"location":"PERFORMANCE/#maintenance-plugins","title":"Maintenance Plugins","text":"<p>Two plugins help maintain the application\u2019s performance:</p>"},{"location":"PERFORMANCE/#1-database-cleanup-dbclnp","title":"1. Database Cleanup (DBCLNP)","text":"<ul> <li>Responsible for database maintenance.</li> <li>Check settings in the DB Cleanup Plugin Docs.</li> <li>Ensure it\u2019s not failing by checking logs.</li> <li>Adjust the schedule (<code>DBCLNP_RUN_SCHD</code>) and timeout (<code>DBCLNP_RUN_TIMEOUT</code>) if needed.</li> </ul>"},{"location":"PERFORMANCE/#2-maintenance-maint","title":"2. Maintenance (MAINT)","text":"<ul> <li>Handles log cleanup and other maintenance tasks.</li> <li>Check settings in the Maintenance Plugin Docs.</li> <li>Ensure it\u2019s running correctly by checking logs.</li> <li>Adjust the schedule (<code>MAINT_RUN_SCHD</code>) and timeout (<code>MAINT_RUN_TIMEOUT</code>) if needed.</li> </ul>"},{"location":"PERFORMANCE/#scan-frequency-and-coverage","title":"Scan Frequency and Coverage","text":"<p>Frequent scans increase resource usage, network traffic, and database read/write cycles.</p>"},{"location":"PERFORMANCE/#optimizations","title":"Optimizations","text":"<ul> <li>Increase scan intervals (<code>&lt;PLUGIN&gt;_RUN_SCHD</code>) on busy networks or low-end hardware.</li> <li>Extend scan timeouts (<code>&lt;PLUGIN&gt;_RUN_TIMEOUT</code>) to prevent failures.</li> <li>Reduce the subnet size \u2013 e.g., from <code>/16</code> to <code>/24</code> to lower scan loads.</li> </ul> <p>Some plugins have additional options to limit the number of scanned devices. If certain plugins take too long to complete, check if you can optimize scan times by selecting a scan range. </p> <p>For example, the ICMP plugin allows you to specify a regular expression to scan only IPs that match a specific pattern.</p>"},{"location":"PERFORMANCE/#storing-temporary-files-in-memory","title":"Storing Temporary Files in Memory","text":"<p>On systems with slower I/O speeds, you can optimize performance by storing temporary files in memory. This primarily applies to the <code>/app/api</code> and <code>/app/log</code> folders.</p> <p>Using <code>tmpfs</code> reduces disk writes and improves performance. However, it should be disabled if persistent logs or API data storage are required.</p> <p>Below is an optimized <code>docker-compose.yml</code> snippet:</p> <pre><code>version: \"3\"\nservices:\n netalertx:\n container_name: netalertx\n # Uncomment the line below to test the latest dev image\n # image: \"ghcr.io/jokob-sk/netalertx-dev:latest\"\n image: \"ghcr.io/jokob-sk/netalertx:latest\" \n network_mode: \"host\" \n restart: unless-stopped\n volumes:\n - local/path/config:/app/config\n - local/path/db:/app/db \n # (Optional) Useful for debugging setup issues\n - local/path/logs:/app/log\n # (API: OPTION 1) Store temporary files in memory (recommended for performance)\n - type: tmpfs # \u25c0 \ud83d\udd3a\n target: /app/api # \u25c0 \ud83d\udd3a\n # (API: OPTION 2) Store API data on disk (useful for debugging)\n # - local/path/api:/app/api\n environment:\n - TZ=Europe/Berlin \n - PORT=20211\n\n</code></pre>"},{"location":"PIHOLE_GUIDE/","title":"Integration with PiHole","text":"<p>NetAlertX comes with 2 plugins suitable for integarting with your existing PiHole instace. One plugin is using a direct SQLite DB connection, the other leverages the DHCP.leases file generated by PiHole. You can combine both approaches and also supplement it with other plugins. </p>"},{"location":"PIHOLE_GUIDE/#approach-1-dhcplss-plugin-import-devices-from-the-pihole-dhcp-leases-file","title":"Approach 1: <code>DHCPLSS</code> Plugin - Import devices from the PiHole DHCP leases file","text":""},{"location":"PIHOLE_GUIDE/#settings","title":"Settings","text":"Setting Description Recommended value <code>DHCPLSS_RUN</code> When the plugin should run. <code>schedule</code> <code>DHCPLSS_RUN_SCHD</code> If you run multiple device scanner plugins, align the schedules of all plugins to the same value. <code>*/5 * * * *</code> <code>DHCPLSS_paths_to_check</code> You need to map the value in this setting in the <code>docker-compose.yml</code> file. The in-container path must contain <code>pihole</code> so it's parsed correctly. <code>['/etc/pihole/dhcp.leases']</code> <p>Check the DHCPLSS plugin readme for details</p>"},{"location":"PIHOLE_GUIDE/#docker-compose-changes","title":"docker-compose changes","text":"Path Description <code>:/etc/pihole/dhcp.leases</code> PiHole's <code>dhcp.leases</code> file. Required if you want to use PiHole <code>dhcp.leases</code> file. This has to be matched with a corresponding <code>DHCPLSS_paths_to_check</code> setting entry (the path in the container must contain <code>pihole</code>)"},{"location":"PIHOLE_GUIDE/#approach-2-pihole-plugin-import-devices-directly-from-the-pihole-database","title":"Approach 2: <code>PIHOLE</code> Plugin - Import devices directly from the PiHole database","text":"Setting Description Recommended value <code>PIHOLE_RUN</code> When the plugin should run. <code>schedule</code> <code>PIHOLE_RUN_SCHD</code> If you run multiple device scanner plugins, align the schedules of all plugins to the same value. <code>*/5 * * * *</code> <code>PIHOLE_DB_PATH</code> You need to map the value in this setting in the <code>docker-compose.yml</code> file. <code>/etc/pihole/pihole-FTL.db</code> <p>Check the PiHole plugin readme for details</p>"},{"location":"PIHOLE_GUIDE/#docker-compose-changes_1","title":"docker-compose changes","text":"Path Description <code>:/etc/pihole/pihole-FTL.db</code> PiHole's <code>pihole-FTL.db</code> database file. <p>Check out other plugins that can help you discover more about your network or check how to scan Remote networks.</p>"},{"location":"PLUGINS/","title":"\ud83d\udd0c Plugins","text":"<p>NetAlertX supports additional plugins to extend its functionality, each with its own settings and options. Plugins can be loaded via the General -&gt; <code>LOADED_PLUGINS</code> setting. For custom plugin development, refer to the Plugin development guide. </p> <p>Note</p> <p>Please check this Plugins debugging guide and the corresponding Plugin documentation in the below table if you are facing issues.</p>"},{"location":"PLUGINS/#quick-start","title":"\u26a1 Quick start","text":"<p>Tip</p> <p>You can load additional Plugins via the General -&gt; <code>LOADED_PLUGINS</code> setting. You need to save the settings for the new plugins to load (cache/page reload may be necessary). </p> <ol> <li>Pick your <code>\ud83d\udd0d dev scanner</code> plugin (e.g. <code>ARPSCAN</code> or <code>NMAPDEV</code>), or import devices into the application with an <code>\ud83d\udce5 importer</code> plugin. (See Enabling plugins below)</li> <li>Pick a <code>\u25b6\ufe0f publisher</code> plugin, if you want to send notifications. If you don't see a publisher you'd like to use, look at the \ud83d\udcda_publisher_apprise plugin which is a proxy for over 80 notification services. </li> <li>Setup your Network topology diagram</li> <li>Fine-tune Notifications</li> <li>Setup Workflows</li> <li>Backup your setup</li> <li>Contribute and Create custom plugins</li> </ol>"},{"location":"PLUGINS/#plugin-types","title":"Plugin types","text":"Plugin type Icon Description When to run Required Data source ? publisher \u25b6\ufe0f Sending notifications to services. <code>on_notification</code> \u2716 Script dev scanner \ud83d\udd0d Create devices in the app, manages online/offline device status. <code>schedule</code> \u2716 Script / SQLite DB name discovery \ud83c\udd8e Discovers names of devices via various protocols. <code>before_name_updates</code>, <code>schedule</code> \u2716 Script importer \ud83d\udce5 Importing devices from another service. <code>schedule</code> \u2716 Script / SQLite DB system \u2699 Providing core system functionality. <code>schedule</code> / always on \u2716/\u2714 Script / Template other \u267b Other plugins misc \u2716 Script / Template"},{"location":"PLUGINS/#features","title":"Features","text":"Icon Description \ud83d\udda7 Auto-imports the network topology diagram \ud83d\udd04 Has the option to sync some data back into the plugin source"},{"location":"PLUGINS/#available-plugins","title":"Available Plugins","text":"<p>Device-detecting plugins insert values into the <code>CurrentScan</code> database table. The plugins that are not required are safe to ignore, however, it makes sense to have at least some device-detecting plugins enabled, such as <code>ARPSCAN</code> or <code>NMAPDEV</code>. </p> ID Plugin docs Type Description Features Required <code>APPRISE</code> _publisher_apprise \u25b6\ufe0f Apprise notification proxy <code>ARPSCAN</code> arp_scan \ud83d\udd0d ARP-scan on current network <code>AVAHISCAN</code> avahi_scan \ud83c\udd8e Avahi (mDNS-based) name resolution <code>ASUSWRT</code> asuswrt_import \ud83d\udd0d Import connected devices from AsusWRT <code>CSVBCKP</code> csv_backup \u2699 CSV devices backup <code>CUSTPROP</code> custom_props \u2699 Managing custom device properties values Yes <code>DBCLNP</code> db_cleanup \u2699 Database cleanup Yes* <code>DDNS</code> ddns_update \u2699 DDNS update <code>DHCPLSS</code> dhcp_leases \ud83d\udd0d/\ud83d\udce5/\ud83c\udd8e Import devices from DHCP leases <code>DHCPSRVS</code> dhcp_servers \u267b DHCP servers <code>DIGSCAN</code> dig_scan \ud83c\udd8e Dig (DNS) Name resolution <code>FREEBOX</code> freebox \ud83d\udd0d/\u267b/\ud83c\udd8e Pull data and names from Freebox/Iliadbox <code>ICMP</code> icmp_scan \u267b ICMP (ping) status checker <code>INTRNT</code> internet_ip \ud83d\udd0d Internet IP scanner <code>INTRSPD</code> internet_speedtest \u267b Internet speed test <code>IPNEIGH</code> ipneigh \ud83d\udd0d Scan ARP (IPv4) and NDP (IPv6) tables <code>LUCIRPC</code> luci_import \ud83d\udd0d Import connected devices from OpenWRT <code>MAINT</code> maintenance \u2699 Maintenance of logs, etc. <code>MQTT</code> _publisher_mqtt \u25b6\ufe0f MQTT for synching to Home Assistant <code>NBTSCAN</code> nbtscan_scan \ud83c\udd8e Nbtscan (NetBIOS-based) name resolution <code>NEWDEV</code> newdev_template \u2699 New device template Yes <code>NMAP</code> nmap_scan \u267b Nmap port scanning &amp; discovery <code>NMAPDEV</code> nmap_dev_scan \ud83d\udd0d Nmap dev scan on current network <code>NSLOOKUP</code> nslookup_scan \ud83c\udd8e NSLookup (DNS-based) name resolution <code>NTFPRCS</code> notification_processing \u2699 Notification processing Yes <code>NTFY</code> _publisher_ntfy \u25b6\ufe0f NTFY notifications <code>OMDSDN</code> omada_sdn_imp \ud83d\udce5/\ud83c\udd8e \u274c UNMAINTAINED use <code>OMDSDNOPENAPI</code> \ud83d\udda7 \ud83d\udd04 <code>OMDSDNOPENAPI</code> omada_sdn_openapi \ud83d\udce5/\ud83c\udd8e OMADA TP-Link import via OpenAPI \ud83d\udda7 <code>PIHOLE</code> pihole_scan \ud83d\udd0d/\ud83c\udd8e/\ud83d\udce5 Pi-hole device import &amp; sync <code>PUSHSAFER</code> _publisher_pushsafer \u25b6\ufe0f Pushsafer notifications <code>PUSHOVER</code> _publisher_pushover \u25b6\ufe0f Pushover notifications <code>SETPWD</code> set_password \u2699 Set password Yes <code>SMTP</code> _publisher_email \u25b6\ufe0f Email notifications <code>SNMPDSC</code> snmp_discovery \ud83d\udd0d/\ud83d\udce5 SNMP device import &amp; sync <code>SYNC</code> sync \ud83d\udd0d/\u2699/\ud83d\udce5 Sync &amp; import from NetAlertX instances \ud83d\udda7 \ud83d\udd04 Yes <code>TELEGRAM</code> _publisher_telegram \u25b6\ufe0f Telegram notifications <code>UI</code> ui_settings \u267b UI specific settings Yes <code>UNFIMP</code> unifi_import \ud83d\udd0d/\ud83d\udce5/\ud83c\udd8e UniFi device import &amp; sync \ud83d\udda7 <code>UNIFIAPI</code> unifi_api_import \ud83d\udd0d/\ud83d\udce5/\ud83c\udd8e UniFi device import (SM API, multi-site) <code>VNDRPDT</code> vendor_update \u2699 Vendor database update <code>WEBHOOK</code> _publisher_webhook \u25b6\ufe0f Webhook notifications <code>WEBMON</code> website_monitor \u267b Website down monitoring <code>WOL</code> wake_on_lan \u267b Automatic wake-on-lan <p>* The database cleanup plugin (<code>DBCLNP</code>) is not required but the app will become unusable after a while if not executed. \u274c marked for removal/unmaintained - looking for help \u231aIt's recommended to use the same schedule interval for all plugins responsible for discovering new devices.</p>"},{"location":"PLUGINS/#enabling-plugins","title":"Enabling plugins","text":"<p>Plugins can be enabled via Settings, and can be disabled as needed. </p> <ol> <li>Research which plugin you'd like to use, enable <code>DISCOVER_PLUGINS</code> and load the required plugins in Settings via the <code>LOADED_PLUGINS</code> setting.</li> <li>Save the changes and review the Settings of the newly loaded plugins. </li> <li>Change the <code>&lt;prefix&gt;_RUN</code> Setting to the recommended or custom value as per the documentation of the given setting <ul> <li>If using <code>schedule</code> on a <code>\ud83d\udd0d dev scanner</code> plugin, make sure the schedules are the same across all <code>\ud83d\udd0d dev scanner</code> plugins</li> </ul> </li> </ol>"},{"location":"PLUGINS/#disabling-unloading-and-ignoring-plugins","title":"Disabling, Unloading and Ignoring plugins","text":"<ol> <li>Change the <code>&lt;prefix&gt;_RUN</code> Setting to <code>disabled</code> if you want to disable the plugin, but keep the settings</li> <li>If you want to speed up the application, you can unload the plugin by unselecting it in the <code>LOADED_PLUGINS</code> setting.<ul> <li>Careful, once you save the Settings Unloaded plugin settings will be lost (old <code>app.conf</code> files are kept in the <code>/config</code> folder) </li> </ul> </li> <li>You can completely ignore plugins by placing a <code>ignore_plugin</code> file into the plugin directory. Ignored plugins won't show up in the <code>LOADED_PLUGINS</code> setting.</li> </ol>"},{"location":"PLUGINS/#developing-new-custom-plugins","title":"\ud83c\udd95 Developing new custom plugins","text":"<p>If you want to develop a custom plugin, please read this Plugin development guide.</p>"},{"location":"PLUGINS_DEV/","title":"Creating a custom plugin","text":"<p>NetAlertX comes with a plugin system to feed events from third-party scripts into the UI and then send notifications, if desired. The highlighted core functionality this plugin system supports, is:</p> <ul> <li>dynamic creation of a simple UI to interact with the discovered objects,</li> <li>filtering of displayed values in the Devices UI</li> <li>surface settings of plugins in the UI, </li> <li>different column types for reported values to e.g. link back to a device</li> <li>import objects into existing NetAlertX database tables </li> </ul> <p>(Currently, update/overwriting of existing objects is only supported for devices via the <code>CurrentScan</code> table.)</p> <p>Note</p> <p>For a high-level overview of how the <code>config.json</code> is used and it's lifecycle check the config.json Lifecycle in NetAlertX Guide.</p>"},{"location":"PLUGINS_DEV/#watch-the-video","title":"\ud83c\udfa5 Watch the video:","text":"<p>Tip</p> <p>Read this guide Development environment setup guide to set up your local environment for development. \ud83d\udc69\u200d\ud83d\udcbb</p> <p></p>"},{"location":"PLUGINS_DEV/#screenshots","title":"\ud83d\udcf8 Screenshots","text":""},{"location":"PLUGINS_DEV/#use-cases","title":"Use cases","text":"<p>Example use cases for plugins could be:</p> <ul> <li>Monitor a web service and alert me if it's down</li> <li>Import devices from dhcp.leases files instead/complementary to using PiHole or arp-scans</li> <li>Creating ad-hoc UI tables from existing data in the NetAlertX database, e.g. to show all open ports on devices, to list devices that disconnected in the last hour, etc.</li> <li>Using other device discovery methods on the network and importing the data as new devices</li> <li>Creating a script to create FAKE devices based on user input via custom settings</li> <li>...at this point the limitation is mostly the creativity rather than the capability (there might be edge cases and a need to support more form controls for user input off custom settings, but you probably get the idea)</li> </ul> <p>If you wish to develop a plugin, please check the existing plugin structure. Once the settings are saved by the user they need to be removed from the <code>app.conf</code> file manually if you want to re-initialize them from the <code>config.json</code> of the plugin. </p>"},{"location":"PLUGINS_DEV/#disclaimer","title":"\u26a0 Disclaimer","text":"<p>Please read the below carefully if you'd like to contribute with a plugin yourself. This documentation file might be outdated, so double-check the sample plugins as well. </p>"},{"location":"PLUGINS_DEV/#plugin-file-structure-overview","title":"Plugin file structure overview","text":"<p>\u26a0\ufe0fFolder name must be the same as the code name value in: <code>\"code_name\": \"&lt;value&gt;\"</code> Unique prefix needs to be unique compared to the other settings prefixes, e.g.: the prefix <code>APPRISE</code> is already in use. </p> File Required (plugin type) Description <code>config.json</code> yes Contains the plugin configuration (manifest) including the settings available to the user. <code>script.py</code> no The Python script itself. You may call any valid linux command. <code>last_result.&lt;prefix&gt;.log</code> no The file used to interface between NetAlertX and the plugin. Required for a script plugin if you want to feed data into the app. Stored in the <code>/api/log/plugins/</code> <code>script.log</code> no Logging output (recommended) <code>README.md</code> yes Any setup considerations or overview <p>More on specifics below.</p>"},{"location":"PLUGINS_DEV/#column-order-and-values-plugins-interface-contract","title":"Column order and values (plugins interface contract)","text":"<p>Important</p> <p>Spend some time reading and trying to understand the below table. This is the interface between the Plugins and the core application. The application expets 9 or 13 values The first 9 values are mandatory. The next 4 values (<code>HelpVal1</code> to <code>HelpVal4</code>) are optional. However, if you use any of these optional values (e.g., <code>HelpVal1</code>), you need to supply all optional values (e.g., <code>HelpVal2</code>, <code>HelpVal3</code>, and <code>HelpVal4</code>). If a value is not used, it should be padded with <code>null</code>.</p> Order Represented Column Value Required Description 0 <code>Object_PrimaryID</code> yes The primary ID used to group Events under. 1 <code>Object_SecondaryID</code> no Optional secondary ID to create a relationship beween other entities, such as a MAC address 2 <code>DateTime</code> yes When the event occured in the format <code>2023-01-02 15:56:30</code> 3 <code>Watched_Value1</code> yes A value that is watched and users can receive notifications if it changed compared to the previously saved entry. For example IP address 4 <code>Watched_Value2</code> no As above 5 <code>Watched_Value3</code> no As above 6 <code>Watched_Value4</code> no As above 7 <code>Extra</code> no Any other data you want to pass and display in NetAlertX and the notifications 8 <code>ForeignKey</code> no A foreign key that can be used to link to the parent object (usually a MAC address) 9 <code>HelpVal1</code> no (optional) A helper value 10 <code>HelpVal2</code> no (optional) A helper value 11 <code>HelpVal3</code> no (optional) A helper value 12 <code>HelpVal4</code> no (optional) A helper value <p>Note</p> <p>De-duplication is run once an hour on the <code>Plugins_Objects</code> database table and duplicate entries with the same value in columns <code>Object_PrimaryID</code>, <code>Object_SecondaryID</code>, <code>Plugin</code> (auto-filled based on <code>unique_prefix</code> of the plugin), <code>UserData</code> (can be populated with the <code>\"type\": \"textbox_save\"</code> column type) are removed.</p>"},{"location":"PLUGINS_DEV/#configjson-structure","title":"config.json structure","text":"<p>The <code>config.json</code> file is the manifest of the plugin. It contains mainly settings definitions and the mapping of Plugin objects to NetAlertX objects. </p>"},{"location":"PLUGINS_DEV/#execution-order","title":"Execution order","text":"<p>The execution order is used to specify when a plugin is executed. This is useful if a plugin has access and surfaces more information than others. If a device is detected by 2 plugins and inserted into the <code>CurrentScan</code> table, the plugin with the higher priority (e.g.: <code>Level_0</code> is a higher priority than <code>Level_1</code>) will insert it's values first. These values (devices) will be then prioritized over any values inserted later.</p> <pre><code>{\n \"execution_order\" : \"Layer_0\"\n}\n</code></pre>"},{"location":"PLUGINS_DEV/#supported-data-sources","title":"Supported data sources","text":"<p>Currently, these data sources are supported (valid <code>data_source</code> value). </p> Name <code>data_source</code> value Needs to return a \"table\"* Overview (more details on this page below) Script <code>script</code> no Executes any linux command in the <code>CMD</code> setting. NetAlertX DB query <code>app-db-query</code> yes Executes a SQL query on the NetAlertX database in the <code>CMD</code> setting. Template <code>template</code> no Used to generate internal settings, such as default values. External SQLite DB query <code>sqlite-db-query</code> yes Executes a SQL query from the <code>CMD</code> setting on an external SQLite database mapped in the <code>DB_PATH</code> setting. Plugin type <code>plugin_type</code> no Specifies the type of the plugin and in which section the Plugin settings are displayed ( one of <code>general/system/scanner/other/publisher</code> ). <ul> <li>\"Needs to return a \"table\" means that the application expects a <code>last_result.&lt;prefix&gt;.log</code> file with some results. It's not a blocker, however warnings in the <code>app.log</code> might be logged.</li> </ul> <p>\ud83d\udd0eExample <code>json \"data_source\": \"app-db-query\"</code> If you want to display plugin objects or import devices into the app, data sources have to return a \"table\" of the exact structure as outlined above.</p> <p>You can show or hide the UI on the \"Plugins\" page and \"Plugins\" tab for a plugin on devices via the <code>show_ui</code> property:</p> <p>\ud83d\udd0eExample <code>json \"show_ui\": true,</code></p>"},{"location":"PLUGINS_DEV/#data_source-script","title":"\"data_source\": \"script\"","text":"<p>If the <code>data_source</code> is set to <code>script</code> the <code>CMD</code> setting (that you specify in the <code>settings</code> array section in the <code>config.json</code>) contains an executable Linux command, that usually generates a <code>last_result.&lt;prefix&gt;.log</code> file (not required if you don't import any data into the app). The <code>last_result.&lt;prefix&gt;.log</code> file needs to be saved in <code>/api/log/plugins</code>. </p> <p>Important</p> <p>A lot of the work is taken care of by the <code>plugin_helper.py</code> library. You don't need to manage the <code>last_result.&lt;prefix&gt;.log</code> file if using the helper objects. Check other <code>script.py</code> of other plugins for details.</p> <p>The content of the <code>last_result.&lt;prefix&gt;.log</code> file needs to contain the columns as defined in the \"Column order and values\" section above. The order of columns can't be changed. After every scan it should contain only the results from the latest scan/execution. </p> <ul> <li>The format of the <code>last_result.&lt;prefix&gt;.log</code> is a <code>csv</code>-like file with the pipe <code>|</code> as a separator. </li> <li>9 (nine) values need to be supplied, so every line needs to contain 8 pipe separators. Empty values are represented by <code>null</code>. </li> <li>Don't render \"headers\" for these \"columns\". Every scan result/event entry needs to be on a new line.</li> <li>You can find which \"columns\" need to be present, and if the value is required or optional, in the \"Column order and values\" section. </li> <li>The order of these \"columns\" can't be changed.</li> </ul>"},{"location":"PLUGINS_DEV/#last_resultprefixlog-examples","title":"\ud83d\udd0e last_result.prefix.log examples","text":"<p>Valid CSV:</p> <pre><code>\nhttps://www.google.com|null|2023-01-02 15:56:30|200|0.7898|null|null|null|null\nhttps://www.duckduckgo.com|192.168.0.1|2023-01-02 15:56:30|200|0.9898|null|null|Best search engine|ff:ee:ff:11:ff:11\n\n</code></pre> <p>Invalid CSV with different errors on each line:</p> <pre><code>\nhttps://www.google.com|null|2023-01-02 15:56:30|200|0.7898||null|null|null\nhttps://www.duckduckgo.com|null|2023-01-02 15:56:30|200|0.9898|null|null|Best search engine|\n|https://www.duckduckgo.com|null|2023-01-02 15:56:30|200|0.9898|null|null|Best search engine|null\nnull|192.168.1.1|2023-01-02 15:56:30|200|0.9898|null|null|Best search engine\nhttps://www.duckduckgo.com|192.168.1.1|2023-01-02 15:56:30|null|0.9898|null|null|Best search engine\nhttps://www.google.com|null|2023-01-02 15:56:30|200|0.7898|||\nhttps://www.google.com|null|2023-01-02 15:56:30|200|0.7898|\n\n</code></pre>"},{"location":"PLUGINS_DEV/#data_source-app-db-query","title":"\"data_source\": \"app-db-query\"","text":"<p>If the <code>data_source</code> is set to <code>app-db-query</code>, the <code>CMD</code> setting needs to contain a SQL query rendering the columns as defined in the \"Column order and values\" section above. The order of columns is important. </p> <p>This SQL query is executed on the <code>app.db</code> SQLite database file. </p> <p>\ud83d\udd0eExample</p> <p>SQL query example:</p> <p><code>SQL SELECT dv.devName as Object_PrimaryID, cast(dv.devLastIP as VARCHAR(100)) || ':' || cast( SUBSTR(ns.Port ,0, INSTR(ns.Port , '/')) as VARCHAR(100)) as Object_SecondaryID, datetime() as DateTime, ns.Service as Watched_Value1, ns.State as Watched_Value2, 'null' as Watched_Value3, 'null' as Watched_Value4, ns.Extra as Extra, dv.devMac as ForeignKey FROM (SELECT * FROM Nmap_Scan) ns LEFT JOIN (SELECT devName, devMac, devLastIP FROM Devices) dv ON ns.MAC = dv.devMac</code></p> <p>Required <code>CMD</code> setting example with above query (you can set <code>\"type\": \"label\"</code> if you want it to make uneditable in the UI):</p> <p><code>json { \"function\": \"CMD\", \"type\": {\"dataType\":\"string\", \"elements\": [{\"elementType\" : \"input\", \"elementOptions\" : [] ,\"transformers\": []}]}, \"default_value\":\"SELECT dv.devName as Object_PrimaryID, cast(dv.devLastIP as VARCHAR(100)) || ':' || cast( SUBSTR(ns.Port ,0, INSTR(ns.Port , '/')) as VARCHAR(100)) as Object_SecondaryID, datetime() as DateTime, ns.Service as Watched_Value1, ns.State as Watched_Value2, 'null' as Watched_Value3, 'null' as Watched_Value4, ns.Extra as Extra FROM (SELECT * FROM Nmap_Scan) ns LEFT JOIN (SELECT devName, devMac, devLastIP FROM Devices) dv ON ns.MAC = dv.devMac\", \"options\": [], \"localized\": [\"name\", \"description\"], \"name\" : [{ \"language_code\":\"en_us\", \"string\" : \"SQL to run\" }], \"description\": [{ \"language_code\":\"en_us\", \"string\" : \"This SQL query is used to populate the coresponding UI tables under the Plugins section.\" }] }</code></p>"},{"location":"PLUGINS_DEV/#data_source-template","title":"\"data_source\": \"template\"","text":"<p>In most cases, it is used to initialize settings. Check the <code>newdev_template</code> plugin for details.</p>"},{"location":"PLUGINS_DEV/#data_source-sqlite-db-query","title":"\"data_source\": \"sqlite-db-query\"","text":"<p>You can execute a SQL query on an external database connected to the current NetAlertX database via a temporary <code>EXTERNAL_&lt;unique prefix&gt;.</code> prefix. </p> <p>For example for <code>PIHOLE</code> (<code>\"unique_prefix\": \"PIHOLE\"</code>) it is <code>EXTERNAL_PIHOLE.</code>. The external SQLite database file has to be mapped in the container to the path specified in the <code>DB_PATH</code> setting:</p> <p>\ud83d\udd0eExample</p> <p><code>json ... { \"function\": \"DB_PATH\", \"type\": {\"dataType\":\"string\", \"elements\": [{\"elementType\" : \"input\", \"elementOptions\" : [{\"readonly\": \"true\"}] ,\"transformers\": []}]}, \"default_value\":\"/etc/pihole/pihole-FTL.db\", \"options\": [], \"localized\": [\"name\", \"description\"], \"name\" : [{ \"language_code\":\"en_us\", \"string\" : \"DB Path\" }], \"description\": [{ \"language_code\":\"en_us\", \"string\" : \"Required setting for the &lt;code&gt;sqlite-db-query&lt;/code&gt; plugin type. Is used to mount an external SQLite database and execute the SQL query stored in the &lt;code&gt;CMD&lt;/code&gt; setting.\" }] } ...</code></p> <p>The actual SQL query you want to execute is then stored as a <code>CMD</code> setting, similar to a Plugin of the <code>app-db-query</code> plugin type. The format has to adhere to the format outlined in the \"Column order and values\" section above. </p> <p>\ud83d\udd0eExample</p> <p>Notice the <code>EXTERNAL_PIHOLE.</code> prefix.</p> <p><code>json { \"function\": \"CMD\", \"type\": {\"dataType\":\"string\", \"elements\": [{\"elementType\" : \"input\", \"elementOptions\" : [] ,\"transformers\": []}]}, \"default_value\":\"SELECT hwaddr as Object_PrimaryID, cast('http://' || (SELECT ip FROM EXTERNAL_PIHOLE.network_addresses WHERE network_id = id ORDER BY lastseen DESC, ip LIMIT 1) as VARCHAR(100)) || ':' || cast( SUBSTR((SELECT name FROM EXTERNAL_PIHOLE.network_addresses WHERE network_id = id ORDER BY lastseen DESC, ip LIMIT 1), 0, INSTR((SELECT name FROM EXTERNAL_PIHOLE.network_addresses WHERE network_id = id ORDER BY lastseen DESC, ip LIMIT 1), '/')) as VARCHAR(100)) as Object_SecondaryID, datetime() as DateTime, macVendor as Watched_Value1, lastQuery as Watched_Value2, (SELECT name FROM EXTERNAL_PIHOLE.network_addresses WHERE network_id = id ORDER BY lastseen DESC, ip LIMIT 1) as Watched_Value3, 'null' as Watched_Value4, '' as Extra, hwaddr as ForeignKey FROM EXTERNAL_PIHOLE.network WHERE hwaddr NOT LIKE 'ip-%' AND hwaddr &lt;&gt; '00:00:00:00:00:00'; \", \"options\": [], \"localized\": [\"name\", \"description\"], \"name\" : [{ \"language_code\":\"en_us\", \"string\" : \"SQL to run\" }], \"description\": [{ \"language_code\":\"en_us\", \"string\" : \"This SQL query is used to populate the coresponding UI tables under the Plugins section. This particular one selects data from a mapped PiHole SQLite database and maps it to the corresponding Plugin columns.\" }] }</code></p>"},{"location":"PLUGINS_DEV/#filters","title":"\ud83d\udd73 Filters","text":"<p>Plugin entries can be filtered in the UI based on values entered into filter fields. The <code>txtMacFilter</code> textbox/field contains the Mac address of the currently viewed device, or simply a Mac address that's available in the <code>mac</code> query string (<code>&lt;url&gt;?mac=aa:22:aa:22:aa:22:aa</code>).</p> Property Required Description <code>compare_column</code> yes Plugin column name that's value is used for comparison (Left side of the equation) <code>compare_operator</code> yes JavaScript comparison operator <code>compare_field_id</code> yes The <code>id</code> of a input text field containing a value is used for comparison (Right side of the equation) <code>compare_js_template</code> yes JavaScript code used to convert left and right side of the equation. <code>{value}</code> is replaced with input values. <code>compare_use_quotes</code> yes If <code>true</code> then the end result of the <code>compare_js_template</code> i swrapped in <code>\"</code> quotes. Use to compare strings. <p>Filters are only applied if a filter is specified, and the <code>txtMacFilter</code> is not <code>undefined</code>, or empty (<code>--</code>).</p> <p>\ud83d\udd0eExample:</p> <p><code>json \"data_filters\": [ { \"compare_column\" : \"Object_PrimaryID\", \"compare_operator\" : \"==\", \"compare_field_id\": \"txtMacFilter\", \"compare_js_template\": \"'{value}'.toString()\", \"compare_use_quotes\": true } ],</code></p> <ol> <li>On the <code>pluginsCore.php</code> page is an input field with the id <code>txtMacFilter</code>:</li> </ol> <p><code>html &lt;input class=\"form-control\" id=\"txtMacFilter\" type=\"text\" value=\"--\"&gt;</code></p> <ol> <li> <p>This input field is initialized via the <code>&amp;mac=</code> query string.</p> </li> <li> <p>The app then proceeds to use this Mac value from this field and compares it to the value of the <code>Object_PrimaryID</code> database field. The <code>compare_operator</code> is <code>==</code>.</p> </li> <li> <p>Both values, from the database field <code>Object_PrimaryID</code> and from the <code>txtMacFilter</code> are wrapped and evaluated with the <code>compare_js_template</code>, that is <code>'{value}.toString()'</code>.</p> </li> <li> <p><code>compare_use_quotes</code> is set to <code>true</code> so <code>'{value}'.toString()</code> is wrappe dinto <code>\"</code> quotes.</p> </li> <li> <p>This results in for example this code: </p> </li> </ol> <p><code>javascript // left part of the expression coming from compare_column and right from the input field // notice the added quotes ()\") around the left and right part of teh expression \"eval('ac:82:ac:82:ac:82\".toString()')\" == \"eval('ac:82:ac:82:ac:82\".toString()')\"</code> </p>"},{"location":"PLUGINS_DEV/#mapping-the-plugin-results-into-a-database-table","title":"\ud83d\uddfa Mapping the plugin results into a database table","text":"<p>Plugin results are always inserted into the standard <code>Plugin_Objects</code> database table. Optionally, NetAlertX can take the results of the plugin execution, and insert these results into an additional database table. This is enabled by with the property <code>\"mapped_to_table\"</code> in the <code>config.json</code> file. The mapping of the columns is defined in the <code>database_column_definitions</code> array.</p> <p>Note</p> <p>If results are mapped to the <code>CurrentScan</code> table, the data is then included into the regular scan loop, so for example notification for devices are sent out.</p> <p>\ud83d\udd0d Example:</p> <p>For example, this approach is used to implement the <code>DHCPLSS</code> plugin. The script parses all supplied \"dhcp.leases\" files, gets the results in the generic table format outlined in the \"Column order and values\" section above, takes individual values, and inserts them into the <code>CurrentScan</code> database table in the NetAlertX database. All this is achieved by:</p> <ol> <li>Specifying the database table into which the results are inserted by defining <code>\"mapped_to_table\": \"CurrentScan\"</code> in the root of the <code>config.json</code> file as shown below:</li> </ol> <p><code>json { \"code_name\": \"dhcp_leases\", \"unique_prefix\": \"DHCPLSS\", ... \"data_source\": \"script\", \"localized\": [\"display_name\", \"description\", \"icon\"], \"mapped_to_table\": \"CurrentScan\", ... }</code> 2. Defining the target column with the <code>mapped_to_column</code> property for individual columns in the <code>database_column_definitions</code> array of the <code>config.json</code> file. For example in the <code>DHCPLSS</code> plugin, I needed to map the value of the <code>Object_PrimaryID</code> column returned by the plugin, to the <code>cur_MAC</code> column in the NetAlertX database table <code>CurrentScan</code>. Notice the <code>\"mapped_to_column\": \"cur_MAC\"</code> key-value pair in the sample below.</p> <p><code>json { \"column\": \"Object_PrimaryID\", \"mapped_to_column\": \"cur_MAC\", \"css_classes\": \"col-sm-2\", \"show\": true, \"type\": \"device_mac\", \"default_value\":\"\", \"options\": [], \"localized\": [\"name\"], \"name\":[{ \"language_code\":\"en_us\", \"string\" : \"MAC address\" }] }</code></p> <ol> <li>That's it. The app takes care of the rest. It loops thru the objects discovered by the plugin, takes the results line-by-line, and inserts them into the database table specified in <code>\"mapped_to_table\"</code>. The columns are translated from the generic plugin columns to the target table columns via the <code>\"mapped_to_column\"</code> property in the column definitions.</li> </ol> <p>Note</p> <p>You can create a column mapping with a default value via the <code>mapped_to_column_data</code> property. This means that the value of the given column will always be this value. That also means that the <code>\"column\": \"NameDoesntMatter\"</code> is not important as there is no database source column.</p> <p>\ud83d\udd0d Example:</p> <p><code>json { \"column\": \"NameDoesntMatter\", \"mapped_to_column\": \"cur_ScanMethod\", \"mapped_to_column_data\": { \"value\": \"DHCPLSS\" }, \"css_classes\": \"col-sm-2\", \"show\": true, \"type\": \"device_mac\", \"default_value\":\"\", \"options\": [], \"localized\": [\"name\"], \"name\":[{ \"language_code\":\"en_us\", \"string\" : \"MAC address\" }] }</code></p>"},{"location":"PLUGINS_DEV/#params","title":"params","text":"<p>Important</p> <p>An esier way to access settings in scripts is the <code>get_setting_value</code> method. ```python from helper import get_setting_value</p> <p>... NTFY_TOPIC = get_setting_value('NTFY_TOPIC') ...</p> <p>```</p> <p>The <code>params</code> array in the <code>config.json</code> is used to enable the user to change the parameters of the executed script. For example, the user wants to monitor a specific URL. </p> <p>\ud83d\udd0e Example: Passing user-defined settings to a command. Let's say, you want to have a script, that is called with a user-defined parameter called <code>urls</code>: </p> <p><code>bash root@server# python3 /app/front/plugins/website_monitor/script.py urls=https://google.com,https://duck.com</code></p> <ul> <li>You can allow the user to add URLs to a setting with the <code>function</code> property set to a custom name, such as <code>urls_to_check</code> (this is not a reserved name from the section \"Supported settings <code>function</code> values\" below). </li> <li>You specify the parameter <code>urls</code> in the <code>params</code> section of the <code>config.json</code> the following way (<code>WEBMON_</code> is the plugin prefix automatically added to all the settings):</li> </ul> <pre><code>{\n \"params\" : [\n {\n \"name\" : \"urls\",\n \"type\" : \"setting\",\n \"value\" : \"WEBMON_urls_to_check\"\n }]\n}\n</code></pre> <ul> <li>Then you use this setting as an input parameter for your command in the <code>CMD</code> setting. Notice <code>urls={urls}</code> in the below json:</li> </ul> <pre><code> {\n \"function\": \"CMD\",\n \"type\": {\"dataType\":\"string\", \"elements\": [{\"elementType\" : \"input\", \"elementOptions\" : [] ,\"transformers\": []}]},\n \"default_value\":\"python3 /app/front/plugins/website_monitor/script.py urls={urls}\",\n \"options\": [],\n \"localized\": [\"name\", \"description\"],\n \"name\" : [{\n \"language_code\":\"en_us\",\n \"string\" : \"Command\"\n }],\n \"description\": [{\n \"language_code\":\"en_us\",\n \"string\" : \"Command to run\"\n }]\n }\n</code></pre> <p>During script execution, the app will take the command <code>\"python3 /app/front/plugins/website_monitor/script.py urls={urls}\"</code>, take the <code>{urls}</code> wildcard and replace it with the value from the <code>WEBMON_urls_to_check</code> setting. This is because:</p> <ol> <li>The app checks the <code>params</code> entries</li> <li>It finds <code>\"name\" : \"urls\"</code></li> <li>Checks the type of the <code>urls</code> params and finds <code>\"type\" : \"setting\"</code></li> <li>Gets the setting name from <code>\"value\" : \"WEBMON_urls_to_check\"</code> </li> <li>IMPORTANT: in the <code>config.json</code> this setting is identified by <code>\"function\":\"urls_to_check\"</code>, not <code>\"function\":\"WEBMON_urls_to_check\"</code></li> <li>You can also use a global setting, or a setting from a different plugin </li> <li>The app gets the user defined value from the setting with the code name <code>WEBMON_urls_to_check</code></li> <li>let's say the setting with the code name <code>WEBMON_urls_to_check</code> contains 2 values entered by the user: </li> <li><code>WEBMON_urls_to_check=['https://google.com','https://duck.com']</code></li> <li>The app takes the value from <code>WEBMON_urls_to_check</code> and replaces the <code>{urls}</code> wildcard in the setting where <code>\"function\":\"CMD\"</code>, so you go from:</li> <li><code>python3 /app/front/plugins/website_monitor/script.py urls={urls}</code></li> <li>to</li> <li><code>python3 /app/front/plugins/website_monitor/script.py urls=https://google.com,https://duck.com</code> </li> </ol> <p>Below are some general additional notes, when defining <code>params</code>: </p> <ul> <li><code>\"name\":\"name_value\"</code> - is used as a wildcard replacement in the <code>CMD</code> setting value by using curly brackets <code>{name_value}</code>. The wildcard is replaced by the result of the <code>\"value\" : \"param_value\"</code> and <code>\"type\":\"type_value\"</code> combo configuration below.</li> <li><code>\"type\":\"&lt;sql|setting&gt;\"</code> - is used to specify the type of the params, currently only 2 supported (<code>sql</code>,<code>setting</code>).</li> <li><code>\"type\":\"sql\"</code> - will execute the SQL query specified in the <code>value</code> property. The sql query needs to return only one column. The column is flattened and separated by commas (<code>,</code>), e.g: <code>SELECT devMac from DEVICES</code> -&gt; <code>Internet,74:ac:74:ac:74:ac,44:44:74:ac:74:ac</code>. This is then used to replace the wildcards in the <code>CMD</code> setting. </li> <li><code>\"type\":\"setting\"</code> - The setting code name. A combination of the value from <code>unique_prefix</code> + <code>_</code> + <code>function</code> value, or otherwise the code name you can find in the Settings page under the Setting display name, e.g. <code>PIHOLE_RUN</code>. </li> <li><code>\"value\": \"param_value\"</code> - Needs to contain a setting code name or SQL query without wildcards.</li> <li><code>\"timeoutMultiplier\" : true</code> - used to indicate if the value should multiply the max timeout for the whole script run by the number of values in the given parameter.</li> <li><code>\"base64\": true</code> - use base64 encoding to pass the value to the script (e.g. if there are spaces)</li> </ul> <p>\ud83d\udd0eExample:</p> <p><code>json { \"params\" : [{ \"name\" : \"ips\", \"type\" : \"sql\", \"value\" : \"SELECT devLastIP from DEVICES\", \"timeoutMultiplier\" : true }, { \"name\" : \"macs\", \"type\" : \"sql\", \"value\" : \"SELECT devMac from DEVICES\" }, { \"name\" : \"timeout\", \"type\" : \"setting\", \"value\" : \"NMAP_RUN_TIMEOUT\" }, { \"name\" : \"args\", \"type\" : \"setting\", \"value\" : \"NMAP_ARGS\", \"base64\" : true }] }</code></p>"},{"location":"PLUGINS_DEV/#setting-object-structure","title":"\u2699 Setting object structure","text":"<p>Note</p> <p>The settings flow and when Plugin specific settings are applied is described under the Settings system.</p> <p>Required attributes are:</p> Property Description <code>\"function\"</code> Specifies the function the setting drives or a simple unique code name. See Supported settings function values for options. <code>\"type\"</code> Specifies the form control used for the setting displayed in the Settings page and what values are accepted. Supported options include: - <code>{\"dataType\":\"string\", \"elements\": [{\"elementType\" : \"input\", \"elementOptions\" : [{\"type\":\"password\"}] ,\"transformers\": [\"sha256\"]}]}</code> <code>\"localized\"</code> A list of properties on the current JSON level that need to be localized. <code>\"name\"</code> Displayed on the Settings page. An array of localized strings. See Localized strings below. <code>\"description\"</code> Displayed on the Settings page. An array of localized strings. See Localized strings below. (optional) <code>\"events\"</code> Specifies whether to generate an execution button next to the input field of the setting. Supported values: - <code>\"test\"</code> - For notification plugins testing - <code>\"run\"</code> - Regular plugins testing (optional) <code>\"override_value\"</code> Used to determine a user-defined override for the setting. Useful for template-based plugins, where you can choose to leave the current value or override it with the value defined in the setting. (Work in progress) (optional) <code>\"events\"</code> Used to trigger the plugin. Usually used on the <code>RUN</code> setting. Not fully tested in all scenarios. Will show a play button next to the setting. After clicking, an event is generated for the backend in the <code>Parameters</code> database table to process the front-end event on the next run."},{"location":"PLUGINS_DEV/#ui-component-types-documentation","title":"UI Component Types Documentation","text":"<p>This section outlines the structure and types of UI components, primarily used to build HTML forms or interactive elements dynamically. Each UI component has a <code>\"type\"</code> which defines its structure, behavior, and rendering options.</p>"},{"location":"PLUGINS_DEV/#ui-component-json-structure","title":"UI Component JSON Structure","text":"<p>The UI component is defined as a JSON object containing a list of <code>elements</code>. Each element specifies how it should behave, with properties like <code>elementType</code>, <code>elementOptions</code>, and any associated <code>transformers</code> to modify the data. The example below demonstrates how a component with two elements (<code>span</code> and <code>select</code>) is structured:</p> <pre><code>{\n \"function\": \"devIcon\",\n \"type\": {\n \"dataType\": \"string\",\n \"elements\": [\n {\n \"elementType\": \"span\",\n \"elementOptions\": [\n { \"cssClasses\": \"input-group-addon iconPreview\" },\n { \"getStringKey\": \"Gen_SelectToPreview\" },\n { \"customId\": \"NEWDEV_devIcon_preview\" }\n ],\n \"transformers\": []\n },\n {\n \"elementType\": \"select\",\n \"elementHasInputValue\": 1,\n \"elementOptions\": [\n { \"cssClasses\": \"col-xs-12\" },\n {\n \"onChange\": \"updateIconPreview(this)\"\n },\n { \"customParams\": \"NEWDEV_devIcon,NEWDEV_devIcon_preview\" }\n ],\n \"transformers\": []\n } \n ]\n }\n}\n\n</code></pre>"},{"location":"PLUGINS_DEV/#rendering-logic","title":"Rendering Logic","text":"<p>The code snippet provided demonstrates how the elements are iterated over to generate their corresponding HTML. Depending on the <code>elementType</code>, different HTML tags (like <code>&lt;select&gt;</code>, <code>&lt;input&gt;</code>, <code>&lt;textarea&gt;</code>, <code>&lt;button&gt;</code>, etc.) are created with the respective attributes such as <code>onChange</code>, <code>my-data-type</code>, and <code>class</code> based on the provided <code>elementOptions</code>. Events can also be attached to elements like buttons or select inputs.</p>"},{"location":"PLUGINS_DEV/#key-element-types","title":"Key Element Types","text":"<ul> <li><code>select</code>: Renders a dropdown list. Additional options like <code>isMultiSelect</code> and event handlers (e.g., <code>onChange</code>) can be attached.</li> <li><code>input</code>: Handles various types of input fields, including checkboxes, text, and others, with customizable attributes like <code>readOnly</code>, <code>placeholder</code>, etc.</li> <li><code>button</code>: Generates clickable buttons with custom event handlers (<code>onClick</code>), icons, or labels.</li> <li><code>textarea</code>: Creates a multi-line input box for text input.</li> <li><code>span</code>: Used for inline text or content with customizable classes and data attributes.</li> </ul> <p>Each element may also have associated events (e.g., running a scan or triggering a notification) defined under <code>Events</code>.</p>"},{"location":"PLUGINS_DEV/#supported-settings-function-values","title":"Supported settings <code>function</code> values","text":"<p>You can have any <code>\"function\": \"my_custom_name\"</code> custom name, however, the ones listed below have a specific functionality attached to them. </p> Setting Description <code>RUN</code> (required) Specifies when the service is executed. Supported Options: - \"disabled\" - do not run - \"once\" - run on app start or on settings saved - \"schedule\" - if included, then a <code>RUN_SCHD</code> setting needs to be specified to determine the schedule - \"always_after_scan\" - run always after a scan is finished - \"before_name_updates\" - run before device names are updated (for name discovery plugins) - \"on_new_device\" - run when a new device is detected - \"before_config_save\" - run before the config is marked as saved. Useful if your plugin needs to modify the <code>app.conf</code> file. <code>RUN_SCHD</code> (required if you include \"schedule\" in the above <code>RUN</code> function) Cron-like scheduling is used if the <code>RUN</code> setting is set to <code>schedule</code>. <code>CMD</code> (required) Specifies the command that should be executed. <code>API_SQL</code> (not implemented) Generates a <code>table_</code> + <code>code_name</code> + <code>.json</code> file as per API docs. <code>RUN_TIMEOUT</code> (optional) Specifies the maximum execution time of the script. If not specified, a default value of 10 seconds is used to prevent hanging. <code>WATCH</code> (optional) Specifies which database columns are watched for changes for this particular plugin. If not specified, no notifications are sent. <code>REPORT_ON</code> (optional) Specifies when to send a notification. Supported options are: - <code>new</code> means a new unique (unique combination of PrimaryId and SecondaryId) object was discovered. - <code>watched-changed</code> - means that selected <code>Watched_ValueN</code> columns changed - <code>watched-not-changed</code> - reports even on events where selected <code>Watched_ValueN</code> did not change - <code>missing-in-last-scan</code> - if the object is missing compared to previous scans <p>\ud83d\udd0e Example:</p> <p><code>json { \"function\": \"RUN\", \"type\": {\"dataType\":\"string\", \"elements\": [{\"elementType\" : \"select\", \"elementOptions\" : [] ,\"transformers\": []}]}, \"default_value\":\"disabled\", \"options\": [\"disabled\", \"once\", \"schedule\", \"always_after_scan\", \"on_new_device\"], \"localized\": [\"name\", \"description\"], \"name\" :[{ \"language_code\":\"en_us\", \"string\" : \"When to run\" }], \"description\": [{ \"language_code\":\"en_us\", \"string\" : \"Enable a regular scan of your services. If you select &lt;code&gt;schedule&lt;/code&gt; the scheduling settings from below are applied. If you select &lt;code&gt;once&lt;/code&gt; the scan is run only once on start of the application (container) for the time specified in &lt;a href=\\\"#WEBMON_RUN_TIMEOUT\\\"&gt;&lt;code&gt;WEBMON_RUN_TIMEOUT&lt;/code&gt; setting&lt;/a&gt;.\" }] }</code></p>"},{"location":"PLUGINS_DEV/#localized-strings","title":"\ud83c\udf0dLocalized strings","text":"<ul> <li><code>\"language_code\":\"&lt;en_us|es_es|de_de&gt;\"</code> - code name of the language string. Only these three are currently supported. At least the <code>\"language_code\":\"en_us\"</code> variant has to be defined. </li> <li><code>\"string\"</code> - The string to be displayed in the given language.</li> </ul> <p>\ud83d\udd0e Example:</p> <p>```json</p> <pre><code>{\n \"language_code\":\"en_us\",\n \"string\" : \"When to run\"\n}\n</code></pre> <p>```</p>"},{"location":"PLUGINS_DEV/#ui-settings-in-database_column_definitions","title":"UI settings in database_column_definitions","text":"<p>The UI will adjust how columns are displayed in the UI based on the resolvers definition of the <code>database_column_definitions</code> object. These are the supported form controls and related functionality:</p> <ul> <li>Only columns with <code>\"show\": true</code> and also with at least an English translation will be shown in the UI.</li> </ul> Supported Types Description <code>label</code> Displays a column only. <code>textarea_readonly</code> Generates a read only text area and cleans up the text to display it somewhat formatted with new lines preserved. See below for information on <code>threshold</code>, <code>replace</code>. <code>options</code> Property Used in conjunction with types like <code>threshold</code>, <code>replace</code>, <code>regex</code>. <code>options_params</code> Property Used in conjunction with a <code>\"options\": \"[{value}]\"</code> template and <code>text.select</code>/<code>list.select</code>. Can specify SQL query (needs to return 2 columns <code>SELECT devName as name, devMac as id</code>) or Setting (not tested) to populate the dropdown. Check example below or have a look at the <code>NEWDEV</code> plugin <code>config.json</code> file. <code>threshold</code> The <code>options</code> array contains objects ordered from the lowest <code>maximum</code> to the highest. The corresponding <code>hexColor</code> is used for the value background color if it's less than the specified <code>maximum</code> but more than the previous one in the <code>options</code> array. <code>replace</code> The <code>options</code> array contains objects with an <code>equals</code> property, which is compared to the \"value.\" If the values are the same, the string in <code>replacement</code> is displayed in the UI instead of the actual \"value\". <code>regex</code> Applies a regex to the value. The <code>options</code> array contains objects with an <code>type</code> (must be set to <code>regex</code>) and <code>param</code> (must contain the regex itself) property. Type Definitions <code>device_mac</code> The value is considered to be a MAC address, and a link pointing to the device with the given MAC address is generated. <code>device_ip</code> The value is considered to be an IP address. A link pointing to the device with the given IP is generated. The IP is checked against the last detected IP address and translated into a MAC address, which is then used for the link itself. <code>device_name_mac</code> The value is considered to be a MAC address, and a link pointing to the device with the given MAC is generated. The link label is resolved as the target device name. <code>url</code> The value is considered to be a URL, so a link is generated. <code>textbox_save</code> Generates an editable and saveable text box that saves values in the database. Primarily intended for the <code>UserData</code> database column in the <code>Plugins_Objects</code> table. <code>url_http_https</code> Generates two links with the <code>https</code> and <code>http</code> prefix as lock icons. <code>eval</code> Evaluates as JavaScript. Use the variable <code>value</code> to use the given column value as input (e.g. <code>'&lt;b&gt;${value}&lt;b&gt;'</code> (replace ' with ` in your code) ) <p>Note</p> <p>Supports chaining. You can chain multiple resolvers with <code>.</code>. For example <code>regex.url_http_https</code>. This will apply the <code>regex</code> resolver and then the <code>url_http_https</code> resolver.</p> <pre><code> \"function\": \"devType\",\n \"type\": {\"dataType\":\"string\", \"elements\": [{\"elementType\" : \"select\", \"elementOptions\" : [] ,\"transformers\": []}]},\n \"maxLength\": 30,\n \"default_value\": \"\",\n \"options\": [\"{value}\"],\n \"options_params\" : [\n {\n \"name\" : \"value\",\n \"type\" : \"sql\",\n \"value\" : \"SELECT '' as id, '' as name UNION SELECT devType as id, devType as name FROM (SELECT devType FROM Devices UNION SELECT 'Smartphone' UNION SELECT 'Tablet' UNION SELECT 'Laptop' UNION SELECT 'PC' UNION SELECT 'Printer' UNION SELECT 'Server' UNION SELECT 'NAS' UNION SELECT 'Domotic' UNION SELECT 'Game Console' UNION SELECT 'SmartTV' UNION SELECT 'Clock' UNION SELECT 'House Appliance' UNION SELECT 'Phone' UNION SELECT 'AP' UNION SELECT 'Gateway' UNION SELECT 'Firewall' UNION SELECT 'Switch' UNION SELECT 'WLAN' UNION SELECT 'Router' UNION SELECT 'Other') AS all_devices ORDER BY id;\"\n },\n {\n \"name\" : \"uilang\",\n \"type\" : \"setting\",\n \"value\" : \"UI_LANG\"\n }\n ]\n</code></pre> <pre><code>{\n \"column\": \"Watched_Value1\",\n \"css_classes\": \"col-sm-2\",\n \"show\": true,\n \"type\": \"threshold\", \n \"default_value\":\"\",\n \"options\": [\n {\n \"maximum\": 199,\n \"hexColor\": \"#792D86\" \n },\n {\n \"maximum\": 299,\n \"hexColor\": \"#5B862D\"\n },\n {\n \"maximum\": 399,\n \"hexColor\": \"#7D862D\"\n },\n {\n \"maximum\": 499,\n \"hexColor\": \"#BF6440\"\n },\n {\n \"maximum\": 599,\n \"hexColor\": \"#D33115\"\n }\n ],\n \"localized\": [\"name\"],\n \"name\":[{\n \"language_code\":\"en_us\",\n \"string\" : \"Status code\"\n }]\n }, \n {\n \"column\": \"Status\",\n \"show\": true,\n \"type\": \"replace\", \n \"default_value\":\"\",\n \"options\": [\n {\n \"equals\": \"watched-not-changed\",\n \"replacement\": \"&lt;i class='fa-solid fa-square-check'&gt;&lt;/i&gt;\"\n },\n {\n \"equals\": \"watched-changed\",\n \"replacement\": \"&lt;i class='fa-solid fa-triangle-exclamation'&gt;&lt;/i&gt;\"\n },\n {\n \"equals\": \"new\",\n \"replacement\": \"&lt;i class='fa-solid fa-circle-plus'&gt;&lt;/i&gt;\"\n }\n ],\n \"localized\": [\"name\"],\n \"name\":[{\n \"language_code\":\"en_us\",\n \"string\" : \"Status\"\n }]\n },\n {\n \"column\": \"Watched_Value3\",\n \"css_classes\": \"col-sm-1\",\n \"show\": true,\n \"type\": \"regex.url_http_https\", \n \"default_value\":\"\",\n \"options\": [\n {\n \"type\": \"regex\",\n \"param\": \"([\\\\d.:]+)\"\n } \n ],\n \"localized\": [\"name\"],\n \"name\":[{\n \"language_code\":\"en_us\",\n \"string\" : \"HTTP/s links\"\n },\n {\n \"language_code\":\"es_es\",\n \"string\" : \"N/A\"\n }]\n }\n</code></pre>"},{"location":"PLUGINS_DEV_CONFIG/","title":"Plugin Config","text":""},{"location":"PLUGINS_DEV_CONFIG/#configjson-lifecycle-in-netalertx","title":"config.json Lifecycle in NetAlertX","text":"<p>This document describes on a high level how <code>config.json</code> is read, processed, and used by the NetAlertX core and plugins. It also outlines the plugin output contract and the main plugin types.</p> <p>Note</p> <p>For a deep-dive on the specific configuration options and sections of the <code>config.json</code> plugin manifest, consult the Plugins Development Guide.</p>"},{"location":"PLUGINS_DEV_CONFIG/#1-loading","title":"1. Loading","text":"<ul> <li>On startup, the app core loads <code>config.json</code> for each plugin.</li> <li>The <code>config.json</code> represents a plugin manifest, that contains metadata and runtime settings.</li> </ul>"},{"location":"PLUGINS_DEV_CONFIG/#2-validation","title":"2. Validation","text":"<ul> <li>The core checks that each required settings key (such as <code>RUN</code>) for a plugin exists.</li> <li>Invalid or missing values may be replaced with defaults, or the plugin may be disabled.</li> </ul>"},{"location":"PLUGINS_DEV_CONFIG/#3-preparation","title":"3. Preparation","text":"<ul> <li>The plugin\u2019s settings (paths, commands, parameters) are prepared.</li> <li>Database mappings (<code>mapped_to_table</code>, <code>database_column_definitions</code>) for data ingestion into the core app are parsed.</li> </ul>"},{"location":"PLUGINS_DEV_CONFIG/#4-execution","title":"4. Execution","text":"<ul> <li>Plugins can be run at different core app execution points, such as on schedule, once on start, after a notification, etc. </li> <li>At runtime, the scheduler triggers plugins according to their <code>interval</code>.</li> <li>The plugin executes its command or script.</li> </ul>"},{"location":"PLUGINS_DEV_CONFIG/#5-parsing","title":"5. Parsing","text":"<ul> <li>Plugin output is expected in pipe (<code>|</code>)-delimited format.</li> <li>The core parses lines into fields, matching the plugin interface contract.</li> </ul>"},{"location":"PLUGINS_DEV_CONFIG/#6-mapping","title":"6. Mapping","text":"<ul> <li>Each parsed field is moved into the <code>Plugins_</code> database tables and can be mapped into a configured database table.</li> <li>Controlled by <code>database_column_definitions</code> and <code>mapped_to_table</code>.</li> <li>Example: <code>Object_PrimaryID \u2192 Devices.MAC</code>.</li> </ul>"},{"location":"PLUGINS_DEV_CONFIG/#6a-plugin-output-contract","title":"6a. Plugin Output Contract","text":"<p>Each plugin must output results in the plugin interface contract format, pipe (<code>|</code>)-delimited values, in the column order described under Plugin Interface Contract</p>"},{"location":"PLUGINS_DEV_CONFIG/#ids","title":"IDs","text":"<ul> <li><code>Object_PrimaryID</code> and <code>Object_SecondaryID</code> identify the record (e.g. <code>MAC|IP</code>).</li> </ul>"},{"location":"PLUGINS_DEV_CONFIG/#watched-values-watched_value14","title":"Watched values (<code>Watched_Value1\u20134</code>)","text":"<ul> <li>Used by the core to detect changes between runs.</li> <li>Changes here can trigger notifications.</li> </ul>"},{"location":"PLUGINS_DEV_CONFIG/#extra-value-extra","title":"Extra value (<code>Extra</code>)","text":"<ul> <li>Optional, extra field.</li> <li>Stored in the database but not used for alerts.</li> </ul>"},{"location":"PLUGINS_DEV_CONFIG/#helper-values-helper_value13","title":"Helper values (<code>Helper_Value1\u20133</code>)","text":"<ul> <li>Added for cases where more than IDs + watched + extra are needed.</li> <li>Can be made visible in the UI.</li> <li>Stored in the database but not used for alerts.</li> </ul>"},{"location":"PLUGINS_DEV_CONFIG/#mapping-matters","title":"Mapping matters","text":"<ul> <li>While the plugin output is free-form, the <code>database_column_definitions</code> and <code>mapped_to_table</code> settings in <code>config.json</code> determine the target columns and data types in NetAlertX.</li> </ul>"},{"location":"PLUGINS_DEV_CONFIG/#7-persistence","title":"7. Persistence","text":"<ul> <li>Data is upserted into the database.</li> <li>Conflicts are resolved using <code>Object_PrimaryID</code> + <code>Object_SecondaryID</code>.</li> </ul>"},{"location":"PLUGINS_DEV_CONFIG/#8-plugin-types-and-expected-outputs","title":"8. Plugin Types and Expected Outputs","text":"<p>Beyond the <code>data_source</code> setting, plugins fall into functional categories. Each has its own input requirements and output expectations:</p>"},{"location":"PLUGINS_DEV_CONFIG/#device-discovery-plugins","title":"Device discovery plugins","text":"<ul> <li>Inputs: <code>N/A</code>, subnet, or API for discovery service, or similar.</li> <li>Outputs: At minimum <code>MAC</code> and <code>IP</code> that results in a new or updated device records in the <code>Devices</code> table.</li> <li>Mapping: Must be mapped to the <code>CurrentScan</code> table via <code>database_column_definitions</code> and <code>data_filters</code>.</li> <li>Examples: ARP-scan, NMAP device discovery (e.g., <code>ARPSCAN</code>, <code>NMAPDEV</code>).</li> </ul>"},{"location":"PLUGINS_DEV_CONFIG/#device-data-enrichment-plugins","title":"Device-data enrichment plugins","text":"<ul> <li>Inputs: Device identifier (usually <code>MAC</code>, <code>IP</code>).</li> <li>Outputs: Additional data for that device (e.g. open ports).</li> <li>Mapping: Controlled via <code>database_column_definitions</code> and <code>data_filters</code>.</li> <li>Examples: Ports, MQTT messages (e.g., <code>NMAP</code>, <code>MQTT</code>)</li> </ul>"},{"location":"PLUGINS_DEV_CONFIG/#name-resolver-plugins","title":"Name resolver plugins","text":"<ul> <li>Inputs: Device identifiers (MAC, IP, or hostname).</li> <li>Outputs: Updated <code>devName</code> and <code>devFQDN</code> fields.</li> <li>Mapping: Not expected.</li> <li>Note: Currently requires core app modification to add new plugins, not fully driven by the plugins\u2019 <code>config.json</code>.</li> <li>Examples: Avahiscan (e.g., <code>NBTSCAN</code>, <code>NSLOOKUP</code>).</li> </ul>"},{"location":"PLUGINS_DEV_CONFIG/#generic-plugins","title":"Generic plugins","text":"<ul> <li>Inputs: Whatever the script or query provides.</li> <li>Outputs: Data shown only in Integrations \u2192 Plugins, not tied to devices.</li> <li>Mapping: Not expected.</li> <li>Examples: External monitoring data (e.g., <code>INTRSPD</code>)</li> </ul>"},{"location":"PLUGINS_DEV_CONFIG/#configuration-only-plugins","title":"Configuration-only plugins","text":"<ul> <li>Inputs/Outputs: None at runtime.</li> <li>Mapping: Not expected. </li> <li>Examples: Used to provide additional settings or execute scripts (e.g., <code>MAINT</code>, <code>CSVBCKP</code>).</li> </ul>"},{"location":"PLUGINS_DEV_CONFIG/#9-post-processing","title":"9. Post-Processing","text":"<ul> <li>Notifications are generated if watched values change.</li> <li>UI is updated with new or updated records.</li> <li>All values that are configured to be shown in teh UI appear in the Plugins section.</li> </ul>"},{"location":"PLUGINS_DEV_CONFIG/#10-summary","title":"10. Summary","text":"<p>The lifecycle of <code>config.json</code> entries is:</p> <p>Load \u2192 Validate \u2192 Prepare \u2192 Execute \u2192 Parse \u2192 Map \u2192 Persist \u2192 Post-process</p> <p>Plugins must follow the output contract, and their category (discovery, specific, resolver, generic, config-only) defines what inputs they require and what outputs are expected.</p>"},{"location":"RANDOM_MAC/","title":"Privacy &amp; Random MAC's","text":"<p>Some operating systems incorporate randomize MAC addresses to improve privacy.</p> <p>This functionality allows you to hide the real MAC of the device and assign a random MAC when we connect to WIFI networks.</p> <p>This behavior is especially useful when connecting to WIFI's that we do not know, but it is totally useless when connecting to our own WIFI's or known networks.</p> <p>I recommend disabling this on-device functionality when connecting our devices to our own WIFI's, this way, NetAlertX will be able to identify the device, and it will not identify it as a new device every so often (every time iOS or Android randomizes the MAC).</p> <p>Random MACs are recognized by the characters \"2\", \"6\", \"A\", or \"E\" as the 2nd character in the Mac address. You can disable specific prefixes to be detected as random MAC addresses by specifying the <code>UI_NOT_RANDOM_MAC</code> setting.</p>"},{"location":"RANDOM_MAC/#windows","title":"Windows","text":"<ul> <li>How to Disable MAC Randomization on Windows</li> </ul>"},{"location":"RANDOM_MAC/#ios","title":"IOS","text":"<ul> <li>Use private Wi-Fi addresses in iOS 14</li> </ul>"},{"location":"RANDOM_MAC/#android","title":"Android","text":"<ul> <li>How to Disable MAC Randomization in Android 10</li> <li>How do I disable random Wi-Fi MAC address on Android 10</li> </ul>"},{"location":"REMOTE_NETWORKS/","title":"Scanning Remote or Inaccessible Networks","text":"<p>By design, local network scanners such as <code>arp-scan</code> use ARP (Address Resolution Protocol) to map IP addresses to MAC addresses on the local network. Since ARP operates at Layer 2 (Data Link Layer), it typically works only within a single broadcast domain, usually limited to a single router or network segment.</p> <p>Note</p> <p>Ping and <code>ARPSCAN</code> use different protocols so even if you can ping devices it doesn't mean <code>ARPSCAN</code> can detect them.</p> <p>To scan multiple locally accessible network segments, add them as subnets according to the subnets documentation. If <code>ARPSCAN</code> is not suitable for your setup, read on.</p>"},{"location":"REMOTE_NETWORKS/#complex-use-cases","title":"Complex Use Cases","text":"<p>The following network setups might make some devices undetectable with <code>ARPSCAN</code>. Check the specific setup to understand the cause and find potential workarounds to report on these devices.</p>"},{"location":"REMOTE_NETWORKS/#wi-fi-extenders","title":"Wi-Fi Extenders","text":"<p>Wi-Fi extenders typically create a separate network or subnet, which can prevent network scanning tools like <code>arp-scan</code> from detecting devices behind the extender.</p> <p>Possible workaround: Scan the specific subnet that the extender uses, if it is separate from the main network.</p>"},{"location":"REMOTE_NETWORKS/#vpns","title":"VPNs","text":"<p>ARP operates at Layer 2 (Data Link Layer) and works only within a local area network (LAN). VPNs, which operate at Layer 3 (Network Layer), route traffic between networks, preventing ARP requests from discovering devices outside the local network.</p> <p>VPNs use virtual interfaces (e.g., <code>tun0</code>, <code>tap0</code>) to encapsulate traffic, bypassing ARP-based discovery. Additionally, many VPNs use NAT, which masks individual devices behind a shared IP address.</p> <p>Possible workaround: Configure the VPN to bridge networks instead of routing to enable ARP, though this depends on the VPN setup and security requirements.</p>"},{"location":"REMOTE_NETWORKS/#other-workarounds","title":"Other Workarounds","text":"<p>The following workarounds should work for most complex network setups.</p>"},{"location":"REMOTE_NETWORKS/#supplementing-plugins","title":"Supplementing Plugins","text":"<p>You can use supplementary plugins that employ alternate methods. Protocols used by the <code>SNMPDSC</code> or <code>DHCPLSS</code> plugins are widely supported on different routers and can be effective as workarounds. Check the plugins list to find a plugin that works with your router and network setup.</p>"},{"location":"REMOTE_NETWORKS/#multiple-netalertx-instances","title":"Multiple NetAlertX Instances","text":"<p>If you have servers in different networks, you can set up separate NetAlertX instances on those subnets and synchronize the results into one instance using the <code>SYNC</code> plugin.</p>"},{"location":"REMOTE_NETWORKS/#manual-entry","title":"Manual Entry","text":"<p>If you don't need to discover new devices and only need to report on their status (<code>online</code>, <code>offline</code>, <code>down</code>), you can manually enter devices and check their status using the <code>ICMP</code> plugin, which uses the <code>ping</code> command internally.</p> <p>For more information on how to add devices manually (or dummy devices), refer to the Device Management documentation.</p> <p>To create truly dummy devices, you can use a loopback IP address (e.g., <code>0.0.0.0</code> or <code>127.0.0.1</code>) so they appear online.</p>"},{"location":"REMOTE_NETWORKS/#nmap-and-fake-mac-addresses","title":"NMAP and Fake MAC Addresses","text":"<p>Scanning remote networks with NMAP is possible (via the <code>NMAPDEV</code> plugin), but since it cannot retrieve the MAC address, you need to enable the <code>NMAPDEV_FAKE_MAC</code> setting. This will generate a fake MAC address based on the IP address, allowing you to track devices. However, this can lead to inconsistencies, especially if the IP address changes or a previously logged device is rediscovered. If this setting is disabled, only the IP address will be discovered, and devices with missing MAC addresses will be skipped.</p> <p>Check the NMAPDEV plugin for details</p>"},{"location":"REVERSE_DNS/","title":"Reverse DNS","text":""},{"location":"REVERSE_DNS/#setting-up-better-name-discovery-with-reverse-dns","title":"Setting up better name discovery with Reverse DNS","text":"<p>If you are running a DNS server, such as AdGuard, set up Private reverse DNS servers for a better name resolution on your network. Enabling this setting will enable NetAlertX to execute dig and nslookup commands to automatically resolve device names based on their IP addresses.</p> <p>Tip</p> <p>Before proceeding, ensure that name resolution plugins are enabled. You can customize how names are cleaned using the <code>NEWDEV_NAME_CLEANUP_REGEX</code> setting. To auto-update Fully Qualified Domain Names (FQDN), enable the <code>REFRESH_FQDN</code> setting.</p> <p>Example 1: Reverse DNS <code>disabled</code></p> <p><code>jokob@Synology-NAS:/$ nslookup 192.168.1.58 ** server can't find 58.1.168.192.in-addr.arpa: NXDOMAIN</code></p> <p>Example 2: Reverse DNS <code>enabled</code></p> <p><code>jokob@Synology-NAS:/$ nslookup 192.168.1.58 45.1.168.192.in-addr.arpa name = jokob-NUC.localdomain.</code></p>"},{"location":"REVERSE_DNS/#enabling-reverse-dns-in-adguard","title":"Enabling reverse DNS in AdGuard","text":"<ol> <li>Navigate to Settings -&gt; DNS Settings</li> <li>Locate Private reverse DNS servers</li> <li>Enter your router IP address, such as <code>192.168.1.1</code></li> <li>Make sure you have Use private reverse DNS resolvers ticked.</li> <li>Click Apply to save your settings.</li> </ol>"},{"location":"REVERSE_DNS/#specifying-the-dns-in-the-container","title":"Specifying the DNS in the container","text":"<p>You can specify the DNS server in the docker-compose to improve name resolution on your network. </p> <pre><code>services:\n netalertx:\n container_name: netalertx\n image: \"ghcr.io/jokob-sk/netalertx:latest\"\n restart: unless-stopped\n volumes:\n - /home/netalertx/config:/app/config\n - /home/netalertx/db:/app/db\n - /home/netalertx/log:/app/log\n environment:\n - TZ=Europe/Berlin\n - PORT=20211\n network_mode: host\n dns: # specifying the DNS servers used for the container\n - 10.8.0.1\n - 10.8.0.17\n</code></pre>"},{"location":"REVERSE_DNS/#using-a-custom-resolvconf-file","title":"Using a custom resolv.conf file","text":"<p>You can configure a custom /etc/resolv.conf file in docker-compose.yml and set the nameserver to your LAN DNS server (e.g.: Pi-Hole). See the relevant resolv.conf man entry for details. </p>"},{"location":"REVERSE_DNS/#docker-composeyml","title":"docker-compose.yml:","text":"<pre><code>version: \"3\"\nservices:\n netalertx:\n container_name: netalertx\n image: \"ghcr.io/jokob-sk/netalertx:latest\"\n restart: unless-stopped\n volumes:\n - ./config/app.conf:/app/config/app.conf\n - ./db:/app/db\n - ./log:/app/log\n - ./config/resolv.conf:/etc/resolv.conf # Mapping the /resolv.conf file for better name resolution\n environment:\n - TZ=Europe/Berlin\n - PORT=20211\n ports:\n - \"20211:20211\"\n network_mode: host\n</code></pre>"},{"location":"REVERSE_DNS/#configresolvconf","title":"./config/resolv.conf:","text":"<p>The most important below is the <code>nameserver</code> entry (you can add multiple):</p> <pre><code>nameserver 192.168.178.11\noptions edns0 trust-ad\nsearch example.com\n</code></pre>"},{"location":"REVERSE_PROXY/","title":"Reverse Proxy Configuration","text":"<p>Submitted by amazing cvc90 \ud83d\ude4f</p> <p>Note</p> <p>There are various NGINX config files for NetAlertX, some for the bare-metal install, currently Debian 12 and Ubuntu 24 (<code>netalertx.conf</code>), and one for the docker container (<code>netalertx.template.conf</code>).</p> <p>The first one you can find in the respective bare metal installer folder <code>/app/install/\\&lt;system\\&gt;/netalertx.conf</code>. The docker one can be found in the install folder. Map, or use, the one appropriate for your setup.</p> <p></p>"},{"location":"REVERSE_PROXY/#nginx-http-configuration-direct-path","title":"NGINX HTTP Configuration (Direct Path)","text":"<ol> <li> <p>On your NGINX server, create a new file called /etc/nginx/sites-available/netalertx</p> </li> <li> <p>In this file, paste the following code:</p> </li> </ol> <pre><code> server { \n listen 80; \n server_name netalertx; \n proxy_preserve_host on; \n proxy_pass http://localhost:20211/; \n proxy_pass_reverse http://localhost:20211/; \n }\n</code></pre> <ol> <li>Activate the new website by running the following command:</li> </ol> <p><code>nginx -s reload</code> or <code>systemctl restart nginx</code></p> <ol> <li> <p>Check your config with <code>nginx -t</code>. If there are any issues, it will tell you.</p> </li> <li> <p>Once NGINX restarts, you should be able to access the proxy website at http://netalertx/</p> </li> </ol> <p></p>"},{"location":"REVERSE_PROXY/#nginx-http-configuration-sub-path","title":"NGINX HTTP Configuration (Sub Path)","text":"<ol> <li> <p>On your NGINX server, create a new file called /etc/nginx/sites-available/netalertx</p> </li> <li> <p>In this file, paste the following code:</p> </li> </ol> <pre><code> server { \n listen 80; \n server_name netalertx; \n proxy_preserve_host on; \n location ^~ /netalertx/ {\n proxy_pass http://localhost:20211/;\n proxy_pass_reverse http://localhost:20211/; \n proxy_redirect ~^/(.*)$ /netalertx/$1;\n rewrite ^/netalertx/?(.*)$ /$1 break; \n }\n }\n</code></pre> <ol> <li> <p>Check your config with <code>nginx -t</code>. If there are any issues, it will tell you.</p> </li> <li> <p>Activate the new website by running the following command:</p> </li> </ol> <p><code>nginx -s reload</code> or <code>systemctl restart nginx</code></p> <ol> <li>Once NGINX restarts, you should be able to access the proxy website at http://netalertx/netalertx/</li> </ol> <p></p>"},{"location":"REVERSE_PROXY/#nginx-http-configuration-sub-path-with-module-ngx_http_sub_module","title":"NGINX HTTP Configuration (Sub Path) with module ngx_http_sub_module","text":"<ol> <li> <p>On your NGINX server, create a new file called /etc/nginx/sites-available/netalertx</p> </li> <li> <p>In this file, paste the following code:</p> </li> </ol> <pre><code> server { \n listen 80; \n server_name netalertx; \n proxy_preserve_host on; \n location ^~ /netalertx/ {\n proxy_pass http://localhost:20211/;\n proxy_pass_reverse http://localhost:20211/; \n proxy_redirect ~^/(.*)$ /netalertx/$1;\n rewrite ^/netalertx/?(.*)$ /$1 break;\n sub_filter_once off;\n sub_filter_types *;\n sub_filter 'href=\"/' 'href=\"/netalertx/';\n sub_filter '(?&gt;$host)/css' '/netalertx/css';\n sub_filter '(?&gt;$host)/js' '/netalertx/js';\n sub_filter '/img' '/netalertx/img';\n sub_filter '/lib' '/netalertx/lib';\n sub_filter '/php' '/netalertx/php'; \n }\n }\n</code></pre> <ol> <li> <p>Check your config with <code>nginx -t</code>. If there are any issues, it will tell you.</p> </li> <li> <p>Activate the new website by running the following command:</p> </li> </ol> <p><code>nginx -s reload</code> or <code>systemctl restart nginx</code></p> <ol> <li>Once NGINX restarts, you should be able to access the proxy website at http://netalertx/netalertx/</li> </ol> <p></p> <p>NGINX HTTPS Configuration (Direct Path)</p> <ol> <li> <p>On your NGINX server, create a new file called /etc/nginx/sites-available/netalertx</p> </li> <li> <p>In this file, paste the following code:</p> </li> </ol> <pre><code> server { \n listen 443; \n server_name netalertx; \n SSLEngine On;\n SSLCertificateFile /etc/ssl/certs/netalertx.pem;\n SSLCertificateKeyFile /etc/ssl/private/netalertx.key;\n proxy_preserve_host on; \n proxy_pass http://localhost:20211/; \n proxy_pass_reverse http://localhost:20211/; \n }\n</code></pre> <ol> <li> <p>Check your config with <code>nginx -t</code>. If there are any issues, it will tell you.</p> </li> <li> <p>Activate the new website by running the following command:</p> </li> </ol> <p><code>nginx -s reload</code> or <code>systemctl restart nginx</code></p> <ol> <li>Once NGINX restarts, you should be able to access the proxy website at https://netalertx/</li> </ol> <p></p> <p>NGINX HTTPS Configuration (Sub Path)</p> <ol> <li> <p>On your NGINX server, create a new file called /etc/nginx/sites-available/netalertx</p> </li> <li> <p>In this file, paste the following code:</p> </li> </ol> <pre><code> server { \n listen 443; \n server_name netalertx; \n SSLEngine On;\n SSLCertificateFile /etc/ssl/certs/netalertx.pem;\n SSLCertificateKeyFile /etc/ssl/private/netalertx.key;\n location ^~ /netalertx/ {\n proxy_pass http://localhost:20211/;\n proxy_pass_reverse http://localhost:20211/; \n proxy_redirect ~^/(.*)$ /netalertx/$1;\n rewrite ^/netalertx/?(.*)$ /$1 break; \n }\n }\n</code></pre> <ol> <li> <p>Check your config with <code>nginx -t</code>. If there are any issues, it will tell you.</p> </li> <li> <p>Activate the new website by running the following command:</p> </li> </ol> <p><code>nginx -s reload</code> or <code>systemctl restart nginx</code></p> <ol> <li>Once NGINX restarts, you should be able to access the proxy website at https://netalertx/netalertx/</li> </ol> <p></p>"},{"location":"REVERSE_PROXY/#nginx-https-configuration-sub-path-with-module-ngx_http_sub_module","title":"NGINX HTTPS Configuration (Sub Path) with module ngx_http_sub_module","text":"<ol> <li> <p>On your NGINX server, create a new file called /etc/nginx/sites-available/netalertx</p> </li> <li> <p>In this file, paste the following code:</p> </li> </ol> <pre><code> server { \n listen 443; \n server_name netalertx; \n SSLEngine On;\n SSLCertificateFile /etc/ssl/certs/netalertx.pem;\n SSLCertificateKeyFile /etc/ssl/private/netalertx.key;\n location ^~ /netalertx/ {\n proxy_pass http://localhost:20211/;\n proxy_pass_reverse http://localhost:20211/; \n proxy_redirect ~^/(.*)$ /netalertx/$1;\n rewrite ^/netalertx/?(.*)$ /$1 break;\n sub_filter_once off;\n sub_filter_types *;\n sub_filter 'href=\"/' 'href=\"/netalertx/';\n sub_filter '(?&gt;$host)/css' '/netalertx/css';\n sub_filter '(?&gt;$host)/js' '/netalertx/js';\n sub_filter '/img' '/netalertx/img';\n sub_filter '/lib' '/netalertx/lib';\n sub_filter '/php' '/netalertx/php'; \n }\n }\n</code></pre> <ol> <li> <p>Check your config with <code>nginx -t</code>. If there are any issues, it will tell you.</p> </li> <li> <p>Activate the new website by running the following command:</p> </li> </ol> <p><code>nginx -s reload</code> or <code>systemctl restart nginx</code></p> <ol> <li>Once NGINX restarts, you should be able to access the proxy website at https://netalertx/netalertx/</li> </ol> <p></p>"},{"location":"REVERSE_PROXY/#apache-http-configuration-direct-path","title":"Apache HTTP Configuration (Direct Path)","text":"<ol> <li> <p>On your Apache server, create a new file called /etc/apache2/sites-available/netalertx.conf.</p> </li> <li> <p>In this file, paste the following code:</p> </li> </ol> <pre><code> &lt;VirtualHost *:80&gt;\n ServerName netalertx\n ProxyPreserveHost On\n ProxyPass / http://localhost:20211/\n ProxyPassReverse / http://localhost:20211/\n &lt;/VirtualHost&gt;\n</code></pre> <ol> <li> <p>Check your config with <code>httpd -t</code> (or <code>apache2ctl -t</code> on Debian/Ubuntu). If there are any issues, it will tell you.</p> </li> <li> <p>Activate the new website by running the following command:</p> </li> </ol> <p><code>a2ensite netalertx</code> or <code>service apache2 reload</code></p> <ol> <li>Once Apache restarts, you should be able to access the proxy website at http://netalertx/</li> </ol> <p></p>"},{"location":"REVERSE_PROXY/#apache-http-configuration-sub-path","title":"Apache HTTP Configuration (Sub Path)","text":"<ol> <li> <p>On your Apache server, create a new file called /etc/apache2/sites-available/netalertx.conf.</p> </li> <li> <p>In this file, paste the following code:</p> </li> </ol> <pre><code> &lt;VirtualHost *:80&gt;\n ServerName netalertx\n location ^~ /netalertx/ {\n ProxyPreserveHost On\n ProxyPass / http://localhost:20211/\n ProxyPassReverse / http://localhost:20211/\n }\n &lt;/VirtualHost&gt;\n</code></pre> <ol> <li> <p>Check your config with <code>httpd -t</code> (or <code>apache2ctl -t</code> on Debian/Ubuntu). If there are any issues, it will tell you.</p> </li> <li> <p>Activate the new website by running the following command:</p> </li> </ol> <p><code>a2ensite netalertx</code> or <code>service apache2 reload</code></p> <ol> <li>Once Apache restarts, you should be able to access the proxy website at http://netalertx/</li> </ol> <p></p>"},{"location":"REVERSE_PROXY/#apache-https-configuration-direct-path","title":"Apache HTTPS Configuration (Direct Path)","text":"<ol> <li> <p>On your Apache server, create a new file called /etc/apache2/sites-available/netalertx.conf.</p> </li> <li> <p>In this file, paste the following code:</p> </li> </ol> <pre><code> &lt;VirtualHost *:443&gt;\n ServerName netalertx\n SSLEngine On\n SSLCertificateFile /etc/ssl/certs/netalertx.pem\n SSLCertificateKeyFile /etc/ssl/private/netalertx.key\n ProxyPreserveHost On\n ProxyPass / http://localhost:20211/\n ProxyPassReverse / http://localhost:20211/\n &lt;/VirtualHost&gt;\n</code></pre> <ol> <li> <p>Check your config with <code>httpd -t</code> (or <code>apache2ctl -t</code> on Debian/Ubuntu). If there are any issues, it will tell you.</p> </li> <li> <p>Activate the new website by running the following command:</p> <p><code>a2ensite netalertx</code> or <code>service apache2 reload</code></p> </li> <li> <p>Once Apache restarts, you should be able to access the proxy website at https://netalertx/</p> </li> </ol> <p></p>"},{"location":"REVERSE_PROXY/#apache-https-configuration-sub-path","title":"Apache HTTPS Configuration (Sub Path)","text":"<ol> <li> <p>On your Apache server, create a new file called /etc/apache2/sites-available/netalertx.conf.</p> </li> <li> <p>In this file, paste the following code:</p> </li> </ol> <pre><code> &lt;VirtualHost *:443&gt; \n ServerName netalertx\n SSLEngine On \n SSLCertificateFile /etc/ssl/certs/netalertx.pem\n SSLCertificateKeyFile /etc/ssl/private/netalertx.key\n location ^~ /netalertx/ {\n ProxyPreserveHost On\n ProxyPass / http://localhost:20211/\n ProxyPassReverse / http://localhost:20211/\n }\n &lt;/VirtualHost&gt;\n</code></pre> <ol> <li> <p>Check your config with <code>httpd -t</code> (or <code>apache2ctl -t</code> on Debian/Ubuntu). If there are any issues, it will tell you.</p> </li> <li> <p>Activate the new website by running the following command:</p> </li> </ol> <p><code>a2ensite netalertx</code> or <code>service apache2 reload</code></p> <ol> <li>Once Apache restarts, you should be able to access the proxy website at https://netalertx/netalertx/</li> </ol> <p></p>"},{"location":"REVERSE_PROXY/#reverse-proxy-example-by-using-linuxservers-swag-container","title":"Reverse proxy example by using LinuxServer's SWAG container.","text":"<p>Submitted by s33d1ing. \ud83d\ude4f</p>"},{"location":"REVERSE_PROXY/#linuxserverswag","title":"linuxserver/swag","text":"<p>In the SWAG container create <code>/config/nginx/proxy-confs/netalertx.subfolder.conf</code> with the following contents:</p> <pre><code>## Version 2023/02/05\n# make sure that your netalertx container is named netalertx\n# netalertx does not require a base url setting\n\n# Since NetAlertX uses a Host network, you may need to use the IP address of the system running NetAlertX for $upstream_app.\n\nlocation /netalertx {\n return 301 $scheme://$host/netalertx/;\n}\n\nlocation ^~ /netalertx/ {\n # enable the next two lines for http auth\n #auth_basic \"Restricted\";\n #auth_basic_user_file /config/nginx/.htpasswd;\n\n # enable for ldap auth (requires ldap-server.conf in the server block)\n #include /config/nginx/ldap-location.conf;\n\n # enable for Authelia (requires authelia-server.conf in the server block)\n #include /config/nginx/authelia-location.conf;\n\n # enable for Authentik (requires authentik-server.conf in the server block)\n #include /config/nginx/authentik-location.conf;\n\n include /config/nginx/proxy.conf;\n include /config/nginx/resolver.conf;\n\n set $upstream_app netalertx;\n set $upstream_port 20211;\n set $upstream_proto http;\n\n proxy_pass $upstream_proto://$upstream_app:$upstream_port;\n proxy_set_header Accept-Encoding \"\";\n\n proxy_redirect ~^/(.*)$ /netalertx/$1;\n rewrite ^/netalertx/?(.*)$ /$1 break;\n\n sub_filter_once off;\n sub_filter_types *;\n\n sub_filter 'href=\"/' 'href=\"/netalertx/';\n\n sub_filter '(?&gt;$host)/css' '/netalertx/css';\n sub_filter '(?&gt;$host)/js' '/netalertx/js';\n\n sub_filter '/img' '/netalertx/img';\n sub_filter '/lib' '/netalertx/lib';\n sub_filter '/php' '/netalertx/php';\n}\n</code></pre> <p></p>"},{"location":"REVERSE_PROXY/#traefik","title":"Traefik","text":"<p>Submitted by Isegrimm \ud83d\ude4f (based on this discussion)</p> <p>Assuming the user already has a working Traefik setup, this is what's needed to make NetAlertX work at a URL like www.domain.com/netalertx/. </p> <p>Note: Everything in these configs assumes 'www.domain.com' as your domainname and 'section31' as an arbitrary name for your certificate setup. You will have to substitute these with your own.</p> <p>Also, I use the prefix 'netalertx'. If you want to use another prefix, change it in these files: dynamic.toml and default.</p> <p>Content of my yaml-file (this is the generic Traefik config, which defines which ports to listen on, redirect http to https and sets up the certificate process). It also contains Authelia, which I use for authentication. This part contains nothing specific to NetAlertX.</p> <pre><code>version: '3.8'\n\nservices:\n traefik:\n image: traefik\n container_name: traefik\n command:\n - \"--api=true\"\n - \"--api.insecure=true\"\n - \"--api.dashboard=true\"\n - \"--entrypoints.web.address=:80\"\n - \"--entrypoints.web.http.redirections.entryPoint.to=websecure\"\n - \"--entrypoints.web.http.redirections.entryPoint.scheme=https\"\n - \"--entrypoints.websecure.address=:443\"\n - \"--providers.file.filename=/traefik-config/dynamic.toml\"\n - \"--providers.file.watch=true\"\n - \"--log.level=ERROR\"\n - \"--certificatesresolvers.section31.acme.email=postmaster@domain.com\"\n - \"--certificatesresolvers.section31.acme.storage=/traefik-config/acme.json\"\n - \"--certificatesresolvers.section31.acme.httpchallenge=true\"\n - \"--certificatesresolvers.section31.acme.httpchallenge.entrypoint=web\"\n ports:\n - \"80:80\"\n - \"443:443\"\n - \"8080:8080\"\n volumes:\n - \"/var/run/docker.sock:/var/run/docker.sock:ro\"\n - /appl/docker/traefik/config:/traefik-config\n depends_on:\n - authelia\n restart: unless-stopped\n authelia:\n container_name: authelia\n image: authelia/authelia:latest\n ports:\n - \"9091:9091\"\n volumes:\n - /appl/docker/authelia:/config\n restart: u\n nless-stopped\n</code></pre> <p>Snippet of the dynamic.toml file (referenced in the yml-file above) that defines the config for NetAlertX: The following are self-defined keywords, everything else is traefik keywords: - netalertx-router - netalertx-service - auth - netalertx-stripprefix</p> <pre><code>[http.routers]\n [http.routers.netalertx-router]\n entryPoints = [\"websecure\"]\n rule = \"Host(`www.domain.com`) &amp;&amp; PathPrefix(`/netalertx`)\"\n service = \"netalertx-service\"\n middlewares = \"auth,netalertx-stripprefix\"\n [http.routers.netalertx-router.tls]\n certResolver = \"section31\"\n [[http.routers.netalertx-router.tls.domains]]\n main = \"www.domain.com\"\n\n[http.services]\n [http.services.netalertx-service]\n [[http.services.netalertx-service.loadBalancer.servers]]\n url = \"http://internal-ip-address:20211/\"\n\n[http.middlewares]\n [http.middlewares.auth.forwardAuth]\n address = \"http://authelia:9091/api/verify?rd=https://www.domain.com/authelia/\"\n trustForwardHeader = true\n authResponseHeaders = [\"Remote-User\", \"Remote-Groups\", \"Remote-Name\", \"Remote-Email\"]\n [http.middlewares.netalertx-stripprefix.stripprefix]\n prefixes = \"/netalertx\"\n forceSlash = false\n\n</code></pre> <p>To make NetAlertX work with this setup I modified the default file at <code>/etc/nginx/sites-available/default</code> in the docker container by copying it to my local filesystem, adding the changes as specified by cvc90 and mounting the new file into the docker container, overwriting the original one. By mapping the file instead of changing the file in-place, the changes persist if an updated dockerimage is pulled. This is also a downside when the default file is updated, so I only use this as a temporary solution, until the dockerimage is updated with this change.</p> <p>Default-file:</p> <pre><code>server {\n listen 80 default_server;\n root /var/www/html;\n index index.php;\n #rewrite /netalertx/(.*) / permanent;\n add_header X-Forwarded-Prefix \"/netalertx\" always;\n proxy_set_header X-Forwarded-Prefix \"/netalertx\";\n\n location ~* \\.php$ {\n fastcgi_pass unix:/run/php/php8.2-fpm.sock;\n include fastcgi_params;\n fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;\n fastcgi_param SCRIPT_NAME $fastcgi_script_name;\n fastcgi_connect_timeout 75;\n fastcgi_send_timeout 600;\n fastcgi_read_timeout 600;\n }\n}\n</code></pre> <p>Mapping the updated file (on the local filesystem at <code>/appl/docker/netalertx/default</code>) into the docker container:</p> <pre><code>docker run -d --rm --network=host \\\n --name=netalertx \\\n -v /appl/docker/netalertx/config:/app/config \\\n -v /appl/docker/netalertx/db:/app/db \\\n -v /appl/docker/netalertx/default:/etc/nginx/sites-available/default \\\n -e TZ=Europe/Amsterdam \\\n -e PORT=20211 \\\n ghcr.io/jokob-sk/netalertx:latest\n\n</code></pre>"},{"location":"SECURITY/","title":"Security","text":""},{"location":"SECURITY/#responsibility-disclaimer","title":"\ud83e\udded Responsibility Disclaimer","text":"<p>NetAlertX provides powerful tools for network scanning, presence detection, and automation. However, it is up to you\u2014the deployer\u2014to ensure that your instance is properly secured.</p> <p>This includes (but is not limited to): - Controlling who has access to the UI and API - Following network and container security best practices - Running NetAlertX only on networks where you have legal authorization - Keeping your deployment up to date with the latest patches</p> <p>NetAlertX is not responsible for misuse, misconfiguration, or unsecure deployments. Always test and secure your setup before exposing it to the outside world.</p>"},{"location":"SECURITY/#securing-your-netalertx-instance","title":"\ud83d\udd10 Securing Your NetAlertX Instance","text":"<p>NetAlertX is a powerful network scanning and automation framework. With that power comes responsibility. It is your responsibility to secure your deployment, especially if you're running it outside a trusted local environment.</p>"},{"location":"SECURITY/#tldr-key-security-recommendations","title":"\u26a0\ufe0f TL;DR \u2013 Key Security Recommendations","text":"<ul> <li>\u2705 NEVER expose NetAlertX directly to the internet without protection</li> <li>\u2705 Use a VPN or Tailscale to access remotely</li> <li>\u2705 Enable password protection for the web UI</li> <li>\u2705 Harden your container environment (e.g., no unnecessary privileges)</li> <li>\u2705 Use firewalls and IP whitelisting</li> <li>\u2705 Keep the software updated</li> <li>\u2705 Limit the scope of plugins and API keys</li> </ul>"},{"location":"SECURITY/#access-control-with-vpn-or-tailscale","title":"\ud83d\udd17 Access Control with VPN (or Tailscale)","text":"<p>NetAlertX is designed to be run on private LANs, not the open internet.</p> <p>Recommended: Use a VPN to access NetAlertX from remote locations.</p>"},{"location":"SECURITY/#tailscale-easy-vpn-alternative","title":"\u2705 Tailscale (Easy VPN Alternative)","text":"<p>Tailscale sets up a private mesh network between your devices. It's fast to configure and ideal for NetAlertX. \ud83d\udc49 Get started with Tailscale</p>"},{"location":"SECURITY/#web-ui-password-protection","title":"\ud83d\udd11 Web UI Password Protection","text":"<p>By default, NetAlertX does not require login. Before exposing the UI in any way:</p> <ol> <li> <p>Enable password protection: <code>ini SETPWD_enable_password=true SETPWD_password=your_secure_password</code></p> </li> <li> <p>Passwords are stored as SHA256 hashes</p> </li> <li> <p>Default password (if not changed): 123456 \u2014 change it ASAP!</p> </li> </ol> <p>To disable authenticated login, set <code>SETPWD_enable_password=false</code> in <code>app.conf</code></p>"},{"location":"SECURITY/#additional-security-measures","title":"\ud83d\udd25 Additional Security Measures","text":"<ul> <li> <p>Firewall / Network Rules Restrict UI/API access to trusted IPs only.</p> </li> <li> <p>Limit Docker Capabilities Avoid <code>--privileged</code>. Use <code>--cap-add=NET_RAW</code> and others only if required by your scan method.</p> </li> <li> <p>Keep NetAlertX Updated Regular updates contain bug fixes and security patches.</p> </li> <li> <p>Plugin Permissions Disable unused plugins. Only install from trusted sources.</p> </li> <li> <p>Use Read-Only API Keys When integrating NetAlertX with other tools, scope keys tightly.</p> </li> </ul>"},{"location":"SECURITY/#docker-hardening-tips","title":"\ud83e\uddf1 Docker Hardening Tips","text":"<ul> <li>Use <code>read-only</code> mount options where possible (<code>:ro</code>)</li> <li>Avoid running as <code>root</code> unless absolutely necessary</li> <li>Consider using <code>docker scan</code> or other container image vulnerability scanners</li> <li>Run with <code>--network host</code> only on trusted networks and only if needed for ARP-based scans</li> </ul>"},{"location":"SECURITY/#responsible-disclosure","title":"\ud83d\udce3 Responsible Disclosure","text":"<p>If you discover a vulnerability or security concern, please report it privately to:</p> <p>\ud83d\udce7 jokob@duck.com</p> <p>We take security seriously and will work to patch confirmed issues promptly. Your help in responsible disclosure is appreciated!</p> <p>By following these recommendations, you can ensure your NetAlertX deployment is both powerful and secure.</p>"},{"location":"SESSION_INFO/","title":"Sessions Section in Device View","text":"<p>The Sessions Section provides details about a device's connection history. This data is automatically detected and cannot be edited by the user.</p> <p></p>"},{"location":"SESSION_INFO/#key-fields","title":"Key Fields","text":"<ol> <li>Date and Time of First Connection</li> <li>Description: Displays the first detected connection time for the device.</li> <li>Editability: Uneditable (auto-detected).</li> <li> <p>Source: Automatically captured when the device is first added to the system.</p> </li> <li> <p>Date and Time of Last Connection</p> </li> <li>Description: Shows the most recent time the device was online.</li> <li>Editability: Uneditable (auto-detected).</li> <li> <p>Source: Updated with every new connection event.</p> </li> <li> <p>Offline Devices with Missing or Conflicting Data</p> </li> <li>Description: Handles cases where a device is offline but has incomplete or conflicting session data (e.g., missing start times).</li> <li>Handling: The system flags these cases for review and attempts to infer missing details.</li> </ol>"},{"location":"SESSION_INFO/#how-sessions-are-discovered-and-calculated","title":"How Sessions are Discovered and Calculated","text":""},{"location":"SESSION_INFO/#1-detecting-new-devices","title":"1. Detecting New Devices","text":"<p>When a device is first detected in the network, the system logs it in the events table:</p> <p><code>INSERT INTO Events (eve_MAC, eve_IP, eve_DateTime, eve_EventType, eve_AdditionalInfo, eve_PendingAlertEmail) SELECT cur_MAC, cur_IP, '{startTime}', 'New Device', cur_Vendor, 1 FROM CurrentScan WHERE NOT EXISTS (SELECT 1 FROM Devices WHERE devMac = cur_MAC)</code></p> <ul> <li>Devices scanned in the current cycle (CurrentScan) are checked against the Devices table.</li> <li>If a device is new:</li> <li>A New Device event is logged.</li> <li>The device\u2019s MAC, IP, vendor, and detection time are recorded.</li> </ul>"},{"location":"SESSION_INFO/#2-logging-connection-sessions","title":"2. Logging Connection Sessions","text":"<p>When a new connection is detected, the system creates a session record:</p> <p><code>INSERT INTO Sessions (ses_MAC, ses_IP, ses_EventTypeConnection, ses_DateTimeConnection, ses_EventTypeDisconnection, ses_DateTimeDisconnection, ses_StillConnected, ses_AdditionalInfo) SELECT cur_MAC, cur_IP, 'Connected', '{startTime}', NULL, NULL, 1, cur_Vendor FROM CurrentScan WHERE NOT EXISTS (SELECT 1 FROM Sessions WHERE ses_MAC = cur_MAC)</code></p> <ul> <li>A new session is logged in the Sessions table if no prior session exists.</li> <li>Fields like <code>MAC</code>, <code>IP</code>, <code>Connection Type</code>, and <code>Connection Time</code> are populated.</li> <li>The <code>Still Connected</code> flag is set to <code>1</code> (active connection).</li> </ul>"},{"location":"SESSION_INFO/#3-handling-missing-or-conflicting-data","title":"3. Handling Missing or Conflicting Data","text":"<ul> <li>Devices with incomplete or conflicting session data (e.g., missing start times) are detected.</li> <li>The system flags these records and attempts corrections by inferring details from available data.</li> </ul>"},{"location":"SESSION_INFO/#4-updating-sessions","title":"4. Updating Sessions","text":"<ul> <li>When a device reconnects, its session is updated with a new connection timestamp.</li> <li>When a device disconnects:</li> <li>The Disconnection Time is recorded.</li> <li>The <code>Still Connected</code> flag is set to <code>0</code>.</li> </ul> <p>The session information is then used to display the device presence under Monitoring -&gt; Presence.</p> <p></p>"},{"location":"SETTINGS_SYSTEM/","title":"Settings","text":""},{"location":"SETTINGS_SYSTEM/#setting-system","title":"\u2699 Setting system","text":"<p>This is an explanation how settings are handled intended for anyone thinking about writing their own plugin or contributing to the project. </p> <p>If you are a user of the app, settings have a detailed description in the Settings section of the app. Open an issue if you'd like to clarify any of the settings. </p>"},{"location":"SETTINGS_SYSTEM/#data-storage","title":"\ud83d\udee2 Data storage","text":"<p>The source of truth for user-defined values is the <code>app.conf</code> file. Editing the file makes the App overwrite values in the <code>Settings</code> database table and in the <code>table_settings.json</code> file. </p>"},{"location":"SETTINGS_SYSTEM/#settings-database-table","title":"Settings database table","text":"<p>The <code>Settings</code> database table contains settings for App run purposes. The table is recreated every time the App restarts. The settings are loaded from the source-of-truth, that is the <code>app.conf</code> file. A high-level overview on the database structure can be found in the database documentation. </p>"},{"location":"SETTINGS_SYSTEM/#table_settingsjson","title":"table_settings.json","text":"<p>This is the API endpoint that reflects the state of the <code>Settings</code> database table. Settings can be accessed with the:</p> <ul> <li><code>getSetting(key)</code> JavaScript method</li> </ul> <p>The json file is also cached on the client-side local storage of the browser.</p>"},{"location":"SETTINGS_SYSTEM/#appconf","title":"app.conf","text":"<p>Note</p> <p>This is the source of truth for settings. User-defined values in this files always override default values specified in the Plugin definition.</p> <p>The App generates two <code>app.conf</code> entries for every setting (Since version 23.8+). One entry is the setting value, the second is the <code>__metadata</code> associated with the setting. This <code>__metadata</code> entry contains the full setting definition in JSON format. Currently unused, but intended to be used in future to extend the Settings system.</p>"},{"location":"SETTINGS_SYSTEM/#plugin-settings","title":"Plugin settings","text":"<p>Note</p> <p>This is the preferred way adding settings going forward. I'll be likely migrating all app settings into plugin-based settings.</p> <p>Plugin settings are loaded dynamically from the <code>config.json</code> of individual plugins. If a setting isn't defined in the <code>app.conf</code> file, it is initialized via the <code>default_value</code> property of a setting from the <code>config.json</code> file. Check the Plugins documentation, section <code>\u2699 Setting object structure</code> for details on the structure of the setting.</p> <p></p>"},{"location":"SETTINGS_SYSTEM/#settings-process-flow","title":"Settings Process flow","text":"<p>The process flow is mostly managed by the initialise.py file. </p> <p>The script is responsible for reading user-defined values from a configuration file (<code>app.conf</code>), initializing settings, and importing them into a database. It also handles plugins and their configurations.</p> <p>Here's a high-level description of the code:</p> <ol> <li>Function Definitions:</li> <li> <p><code>ccd</code>: This function is used to handle user-defined settings and configurations. It takes several parameters related to the setting's name, default value, input type, options, group, and more. It saves the settings and their metadata in different lists (<code>conf.mySettingsSQLsafe</code> and <code>conf.mySettings</code>).</p> </li> <li> <p><code>importConfigs</code>: This function is the main entry point of the script. It imports user settings from a configuration file, processes them, and saves them to the database.</p> </li> <li> <p><code>read_config_file</code>: This function reads the configuration file (<code>app.conf</code>) and returns a dictionary containing the key-value pairs from the file.</p> </li> <li> <p>Importing Configuration and Initializing Settings:</p> </li> <li> <p>The <code>importConfigs</code> function starts by checking the modification time of the configuration file to determine if it needs to be re-imported. If the file has not been modified since the last import, the function skips the import process.</p> </li> <li> <p>The function reads the configuration file using the <code>read_config_file</code> function, which returns a dictionary of settings.</p> </li> <li> <p>The script then initializes various user-defined settings using the <code>ccd</code> function, based on the values read from the configuration file. These settings are categorized into groups such as \"General,\" \"Email,\" \"Webhooks,\" \"Apprise,\" and more.</p> </li> <li> <p>Plugin Handling:</p> </li> <li>The script loads and handles plugins dynamically. It retrieves plugin configurations and iterates through each plugin.</li> <li>For each plugin, it extracts the prefix and settings related to that plugin and processes them similarly to other user-defined settings.</li> <li> <p>It also handles scheduling for plugins with specific <code>RUN_SCHD</code> settings.</p> </li> <li> <p>Saving Settings to the Database:</p> </li> <li> <p>The script clears the existing settings in the database and inserts the updated settings into the database using SQL queries.</p> </li> <li> <p>Updating the API and Performing Cleanup:</p> </li> <li>After importing the configurations, the script updates the API to reflect the changes in the settings.</li> <li>It saves the current timestamp to determine the next import time.</li> <li>Finally, it logs the successful import of the new configuration.</li> </ol>"},{"location":"SMTP/","title":"\ud83d\udce7 SMTP server guides","text":"<p>The SMTP plugin supports any SMTP server. Here are some commonly used services to help speed up your configuration.</p> <p>Note</p> <p>If you are using a self hosted SMTP server ssh into the container and verify (e.g. via ping) that your server is reachable from within the NetAlertX container. See also how to ssh into the container if you are running it as a Home Assistant addon.</p>"},{"location":"SMTP/#gmail","title":"Gmail","text":"<ol> <li> <p>Create an app password by following the instructions from Google, you need to Enable 2FA for this to work. https://support.google.com/accounts/answer/185833</p> </li> <li> <p>Specify the following settings:</p> </li> </ol> <pre><code> SMTP_RUN='on_notification'\n SMTP_SKIP_TLS=True\n SMTP_FORCE_SSL=True \n SMTP_PORT=465\n SMTP_SERVER='smtp.gmail.com'\n SMTP_PASS='16-digit passcode from google'\n SMTP_REPORT_TO='some_target_email@gmail.com'\n</code></pre>"},{"location":"SMTP/#brevo","title":"Brevo","text":"<p>Brevo allows for 300 free emails per day as of time of writing. </p> <ol> <li>Create an account on Brevo: https://www.brevo.com/free-smtp-server/</li> <li>Click your name -&gt; SMTP &amp; API</li> <li>Click Generate a new SMTP key</li> <li>Save the details and fill in the NetAlertX settings as below.</li> </ol> <pre><code>SMTP_SERVER='smtp-relay.brevo.com'\nSMTP_PORT=587\nSMTP_SKIP_LOGIN=False\nSMTP_USER='user@email.com'\nSMTP_PASS='xsmtpsib-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-xxxxxxxxxxx'\nSMTP_SKIP_TLS=False\nSMTP_FORCE_SSL=False\nSMTP_REPORT_TO='some_target_email@gmail.com'\nSMTP_REPORT_FROM='NetAlertX &lt;user@email.com&gt;'\n</code></pre>"},{"location":"SMTP/#gmx","title":"GMX","text":"<ol> <li>Go to your GMX account https://account.gmx.com</li> <li>Under Security Options enable 2FA (Two-factor authentication)</li> <li>Under Security Options generate an Application-specific password</li> <li>Home -&gt; Email Settings -&gt; POP3 &amp; IMAP -&gt; Enable access to this account via POP3 and IMAP</li> <li>In NetAlertX specify these settings:</li> </ol> <pre><code> SMTP_RUN='on_notification'\n SMTP_SERVER='mail.gmx.com'\n SMTP_PORT=465\n SMTP_USER='gmx_email@gmx.com'\n SMTP_PASS='&lt;your Application-specific password&gt;'\n SMTP_SKIP_TLS=True\n SMTP_FORCE_SSL=True\n SMTP_SKIP_LOGIN=False\n SMTP_REPORT_FROM='gmx_email@gmx.com' # this has to be the same email as in SMTP_USER\n SMTP_REPORT_TO='some_target_email@gmail.com'\n</code></pre>"},{"location":"SUBNETS/","title":"Subnets Configuration","text":"<p>You need to specify the network interface and the network mask. You can also configure multiple subnets and specify VLANs (see VLAN exceptions below).</p> <p><code>ARPSCAN</code> can scan multiple networks if the network allows it. To scan networks directly, the subnets must be accessible from the network where NetAlertX is running. This means NetAlertX needs to have access to the interface attached to that subnet. </p> <p>Warning</p> <p>If you don't see all expected devices run the following command in the NetAlertX container (replace the interface and ip mask): <code>sudo arp-scan --interface=eth0 192.168.1.0/24</code></p> <p>If this command returns no results, the network is not accessible due to your network or firewall restrictions (Wi-Fi Extenders, VPNs and inaccessible networks). If direct scans are not possible, check the remote networks documentation for workarounds.</p>"},{"location":"SUBNETS/#example-values","title":"Example Values","text":"<p>Note</p> <p>Please use the UI to configure settings as it ensures the config file is in the correct format. Edit <code>app.conf</code> directly only when really necessary. </p> <ul> <li>Examples for one and two subnets:</li> <li>One subnet: <code>SCAN_SUBNETS = ['192.168.1.0/24 --interface=eth0']</code></li> <li>Two subnets: <code>SCAN_SUBNETS = ['192.168.1.0/24 --interface=eth0','192.168.1.0/24 --interface=eth1 --vlan=107']</code></li> </ul> <p>Tip</p> <p>When adding more subnets, you may need to increase both the scan interval (<code>ARPSCAN_RUN_SCHD</code>) and the timeout (<code>ARPSCAN_RUN_TIMEOUT</code>)\u2014as well as similar settings for related plugins. </p> <p>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. </p> <p>To fix this: - Reduce the subnet size (e.g., change <code>/16</code> to <code>/24</code>). - Increase the timeout (e.g., set <code>ARPSCAN_RUN_TIMEOUT</code> to <code>300</code> for a 5-minute timeout). - Extend the scan interval (e.g., set <code>ARPSCAN_RUN_SCHD</code> to <code>*/10 * * * *</code> to scan every 10 minutes). </p> <p>For more troubleshooting tips, see Debugging Plugins.</p>"},{"location":"SUBNETS/#explanation","title":"Explanation","text":""},{"location":"SUBNETS/#network-mask","title":"Network Mask","text":"<p>Example value: <code>192.168.1.0/24</code></p> <p>The <code>arp-scan</code> time itself depends on the number of IP addresses to check.</p> <p>The number of IPs to check depends on the network mask you set in the <code>SCAN_SUBNETS</code> setting. For example, a <code>/24</code> mask results in 256 IPs to check, whereas a <code>/16</code> mask checks around 65,536 IPs. Each IP takes a couple of seconds, so an incorrect configuration could make <code>arp-scan</code> take hours instead of seconds.</p> <p>Specify the network filter, which significantly speeds up the scan process. For example, the filter <code>192.168.1.0/24</code> covers IP ranges from <code>192.168.1.0</code> to <code>192.168.1.255</code>.</p>"},{"location":"SUBNETS/#network-interface-adapter","title":"Network Interface (Adapter)","text":"<p>Example value: <code>--interface=eth0</code></p> <p>The adapter will probably be <code>eth0</code> or <code>eth1</code>. (Check <code>System Info</code> &gt; <code>Network Hardware</code>, or run <code>iwconfig</code> in the container to find your interface name(s)).</p> <p></p> <p>Tip</p> <p>As an alternative to <code>iwconfig</code>, run <code>ip -o link show | awk -F': ' '!/lo|vir|docker/ {print $2}'</code> in your container to find your interface name(s) (e.g.: <code>eth0</code>, <code>eth1</code>): <code>bash Synology-NAS:/# ip -o link show | awk -F': ' '!/lo|vir|docker/ {print $2}' sit0@NONE eth1 eth0</code></p>"},{"location":"SUBNETS/#vlans","title":"VLANs","text":"<p>Example value: <code>--vlan=107</code></p> <ul> <li>Append <code>--vlan=107</code> to the <code>SCAN_SUBNETS</code> field (e.g.: <code>192.168.1.0/24 --interface=vmbr0 --vlan=107</code>) for multiple VLANs.</li> </ul>"},{"location":"SUBNETS/#vlans-on-a-hyper-v-setup","title":"VLANs on a Hyper-V Setup","text":"<p>Community-sourced content by mscreations from this discussion.</p> <p>Tested Setup: Bare Metal \u2192 Hyper-V on Win Server 2019 \u2192 Ubuntu 22.04 VM \u2192 Docker \u2192 NetAlertX.</p> <p>Approach 1 (may cause issues): Configure multiple network adapters in Hyper-V with distinct VLANs connected to each one using Hyper-V's network setup. However, this action can potentially lead to the Docker host's inability to handle network traffic correctly. This might interfere with other applications such as Authentik.</p> <p>Approach 2 (working example):</p> <p>Network connections to switches are configured as trunk and allow all VLANs access to the server.</p> <p>By default, Hyper-V only allows untagged packets through to the VM interface, blocking VLAN-tagged packets. To fix this, follow these steps:</p> <ol> <li>Run the following command in PowerShell on the Hyper-V machine:</li> </ol> <p><code>powershell Set-VMNetworkAdapterVlan -VMName &lt;Docker VM Name&gt; -Trunk -NativeVlanId 0 -AllowedVlanIdList \"&lt;comma separated list of vlans&gt;\"</code></p> <ol> <li>Within the VM, set up sub-interfaces for each VLAN to enable scanning. On Ubuntu 22.04, Netplan can be used. In /etc/netplan/00-installer-config.yaml, add VLAN definitions:</li> </ol> <p><code>yaml network: ethernets: eth0: dhcp4: yes vlans: eth0.2: id: 2 link: eth0 addresses: [ \"192.168.2.2/24\" ] routes: - to: 192.168.2.0/24 via: 192.168.1.1</code></p> <ol> <li>Run <code>sudo netplan apply</code> to activate the interfaces for scanning in NetAlertX.</li> </ol> <p>In this case, use <code>192.168.2.0/24 --interface=eth0.2</code> in NetAlertX.</p>"},{"location":"SUBNETS/#vlan-support-exceptions","title":"VLAN Support &amp; Exceptions","text":"<p>Please note the accessibility of macvlans when configured on the same computer. This is a general networking behavior, but feel free to clarify via a PR/issue.</p> <ul> <li>NetAlertX does not detect the macvlan container when it is running on the same computer.</li> <li>NetAlertX recognizes the macvlan container when it is running on a different computer.</li> </ul>"},{"location":"SYNOLOGY_GUIDE/","title":"Installation on a Synology NAS","text":"<p>There are different ways to install NetAlertX on a Synology, including SSH-ing into the machine and using the command line. For this guide, we will use the Project option in Container manager. </p>"},{"location":"SYNOLOGY_GUIDE/#create-the-folder-structure","title":"Create the folder structure","text":"<p>The folders you are creating below will contain the configuration and the database. Back them up regularly. </p> <ol> <li>Create a parent folder named <code>netalertx</code></li> <li>Create a <code>db</code> sub-folder</li> </ol> <p> </p> <ol> <li>Create a <code>config</code> sub-folder</li> </ol> <p></p> <ol> <li>Note down the folders Locations:</li> </ol> <p> </p> <ol> <li>Open Container manager -&gt; Project and click Create.</li> <li> <p>Fill in the details:</p> </li> <li> <p>Project name: <code>netalertx</code></p> </li> <li>Path: <code>/app_storage/netalertx</code> (will differ from yours)</li> <li>Paste in the following template:</li> </ol> <pre><code>version: \"3\"\nservices:\n netalertx:\n container_name: netalertx\n # use the below line if you want to test the latest dev image\n # image: \"ghcr.io/jokob-sk/netalertx-dev:latest\" \n image: \"ghcr.io/jokob-sk/netalertx:latest\" \n network_mode: \"host\" \n restart: unless-stopped\n volumes:\n - local/path/config:/app/config\n - local/path/db:/app/db \n # (optional) useful for debugging if you have issues setting up the container\n - local/path/logs:/app/log\n environment:\n - TZ=Europe/Berlin \n - PORT=20211\n</code></pre> <p></p> <ol> <li> <p>Replace the paths to your volume and comment out unnecessary line(s):</p> </li> <li> <p>This is only an example, your paths will differ.</p> </li> </ol> <pre><code> volumes:\n - /volume1/app_storage/netalertx/config:/app/config\n - /volume1/app_storage/netalertx/db:/app/db \n # (optional) useful for debugging if you have issues setting up the container\n # - local/path/logs:/app/log &lt;- commented out with # \u26a0\n</code></pre> <p></p> <ol> <li>(optional) Change the port number from <code>20211</code> to an unused port if this port is already used.</li> <li>Build the project:</li> </ol> <p></p> <ol> <li>Navigate to <code>&lt;Synology URL&gt;:20211</code> (or your custom port).</li> <li>Read the Subnets and Plugins docs to complete your setup. </li> </ol>"},{"location":"UPDATES/","title":"Docker Update Strategies to upgrade NetAlertX","text":"<p>Warning</p> <p>For versions prior to <code>v25.6.7</code> upgrade to version <code>v25.5.24</code> first (<code>docker pull ghcr.io/jokob-sk/netalertx:25.5.24</code>) as later versions don't support a full upgrade. Alternatively, devices and settings can be migrated manually, e.g. via CSV import.</p> <p>This guide outlines approaches for updating Docker containers, usually when upgrading to a newer version of NetAlertX. Each method offers different benefits depending on the situation. Here are the methods:</p> <ul> <li>Manual: Direct commands to stop, remove, and rebuild containers.</li> <li>Dockcheck: Semi-automated with more control, suited for bulk updates.</li> <li>Watchtower: Fully automated, runs continuously to check and update containers.</li> <li>Portainer: Manual with UI.</li> </ul> <p>You can choose any approach that fits your workflow.</p> <p>In the examples I assume that the container name is <code>netalertx</code> and the image name is <code>netalertx</code> as well.</p> <p>Note</p> <p>See also Backup strategies to be on the safe side.</p>"},{"location":"UPDATES/#1-manual-updates","title":"1. Manual Updates","text":"<p>Use this method when you need precise control over a single container or when dealing with a broken container that needs immediate attention. Example Commands</p> <p>To manually update the <code>netalertx</code> container, stop it, delete it, remove the old image, and start a fresh one with <code>docker-compose</code>.</p> <pre><code># Stop the container\nsudo docker container stop netalertx\n\n# Remove the container\nsudo docker container rm netalertx\n\n# Remove the old image\nsudo docker image rm netalertx\n\n# Pull and start a new container\nsudo docker-compose up -d\n</code></pre>"},{"location":"UPDATES/#alternative-force-pull-with-docker-compose","title":"Alternative: Force Pull with Docker Compose","text":"<p>You can also use <code>--pull always</code> to ensure Docker pulls the latest image before starting the container:</p> <pre><code>sudo docker-compose up --pull always -d\n</code></pre>"},{"location":"UPDATES/#2-dockcheck-for-bulk-container-updates","title":"2. Dockcheck for Bulk Container Updates","text":"<p>Always check the Dockcheck docs if encountering issues with the guide below. </p> <p>Dockcheck is a useful tool if you have multiple containers to update and some flexibility for handling potential issues that might arise during mass updates. Dockcheck allows you to inspect each container and decide when to update.</p>"},{"location":"UPDATES/#example-workflow-with-dockcheck","title":"Example Workflow with Dockcheck","text":"<p>You might use Dockcheck to:</p> <ul> <li>Inspect container versions.</li> <li>Pull the latest images in bulk.</li> <li>Apply updates selectively.</li> </ul> <p>Dockcheck can help streamline bulk updates, especially if you\u2019re managing multiple containers.</p> <p>Below is a script I use to run an update of the Dockcheck script and start a check for new containers:</p> <pre><code>cd /path/to/Docker &amp;&amp;\nrm dockcheck.sh &amp;&amp;\nwget https://raw.githubusercontent.com/mag37/dockcheck/main/dockcheck.sh &amp;&amp;\nsudo chmod +x dockcheck.sh &amp;&amp;\nsudo ./dockcheck.sh\n</code></pre>"},{"location":"UPDATES/#3-automated-updates-with-watchtower","title":"3. Automated Updates with Watchtower","text":"<p>Always check the watchtower docs if encountering issues with the guide below. </p> <p>Watchtower monitors your Docker containers and automatically updates them when new images are available. This is ideal for ongoing updates without manual intervention.</p>"},{"location":"UPDATES/#setting-up-watchtower","title":"Setting Up Watchtower","text":""},{"location":"UPDATES/#1-pull-the-watchtower-image","title":"1. Pull the Watchtower Image:","text":"<pre><code>docker pull containrrr/watchtower\n</code></pre>"},{"location":"UPDATES/#2-run-watchtower-to-update-all-images","title":"2. Run Watchtower to update all images:","text":"<pre><code>docker run -d \\\n --name watchtower \\\n -v /var/run/docker.sock:/var/run/docker.sock \\\n containrrr/watchtower \\\n --interval 300 # Check for updates every 5 minutes\n</code></pre>"},{"location":"UPDATES/#3-run-watchtower-to-update-only-netalertx","title":"3. Run Watchtower to update only NetAlertX:","text":"<p>You can specify which containers to monitor by listing them. For example, to monitor netalertx only:</p> <pre><code>docker run -d \\\n --name watchtower \\\n -v /var/run/docker.sock:/var/run/docker.sock \\\n containrrr/watchtower netalertx\n\n</code></pre>"},{"location":"UPDATES/#4-portainer-controlled-image","title":"4. Portainer controlled image","text":"<p>This assumes you're using Portainer to manage Docker (or Docker Swarm) and want to pull the latest version of an image and redeploy the container.</p> <p>Note</p> <ul> <li>Portainer does not auto-update containers. For automation, use Watchtower or similar tools.</li> <li>Make sure you have the persistent volumes mounted or backups ready before recreating.</li> </ul>"},{"location":"UPDATES/#41-steps-to-update-an-image-in-portainer-standalone-docker","title":"4.1 Steps to Update an Image in Portainer (Standalone Docker)","text":"<ol> <li>Login to Portainer.</li> <li>Go to \"Containers\" in the left sidebar.</li> <li>Find the container you want to update, click its name.</li> <li>Click \"Recreate\" (top right).</li> <li>Tick: Pull latest image (this ensures Portainer fetches the newest version from Docker Hub or your registry).</li> <li>Click \"Recreate\" again.</li> <li>Wait for the container to be stopped, removed, and recreated with the updated image.</li> </ol>"},{"location":"UPDATES/#42-for-docker-swarm-services","title":"4.2 For Docker Swarm Services","text":"<p>If you're using Docker Swarm (under \"Stacks\" or \"Services\"):</p> <ol> <li>Go to \"Stacks\".</li> <li>Select the stack managing the container.</li> <li>Click \"Editor\" (or \"Update the Stack\").</li> <li>Add a version tag or use <code>:latest</code> if your image tag is <code>latest</code> (not recommended for production).</li> <li>Click \"Update the Stack\". \u26a0 Portainer will not pull the new image unless the tag changes OR the stack is forced to recreate.</li> <li>If image tag hasn't changed, go to \"Services\", find the service, and click \"Force Update\".</li> </ol>"},{"location":"UPDATES/#summary","title":"Summary","text":"Method Type Pros Cons Manual CLI Full control, no dependencies Tedious for many containers Dockcheck CLI Script Great for batch updates Needs setup, semi-automated Watchtower Daemonized Fully automated updates Less control, version drift Portainer UI Easy via web interface No auto-updates <p>These approaches allow you to maintain flexibility in how you update Docker containers, depending on the urgency and scale of the update.</p>"},{"location":"VERSIONS/","title":"Versions","text":""},{"location":"VERSIONS/#am-i-running-the-latest-released-version","title":"Am I running the latest released version?","text":"<p>Since version 23.01.14 NetAlertX uses a simple timestamp-based version check to verify if a new version is available. You can check the current and past releases here, or have a look at what I'm currently working on. </p> <p>If you are not on the latest version, the app will notify you, that a new released version is avialable the following way:</p>"},{"location":"VERSIONS/#via-email-on-a-notification-event","title":"\ud83d\udce7 Via email on a notification event","text":"<p>If any notification occurs and an email is sent, the email will contain a note that a new version is available. See the sample email below:</p> <p></p>"},{"location":"VERSIONS/#in-the-ui","title":"\ud83c\udd95 In the UI","text":"<p>In the UI via a notification Icon and via a custom message in the Maintenance section.</p> <p></p> <p>For a comparison, this is how the UI looks like if you are on the latest stable image:</p> <p></p>"},{"location":"VERSIONS/#implementation-details","title":"Implementation details","text":"<p>During build a /app/front/buildtimestamp.txt file is created. The app then periodically checks if a new release is available with a newer timestamp in GitHub's rest-based JSON endpoint (check the <code>def isNewVersion:</code> method for details). </p>"},{"location":"WEBHOOK_N8N/","title":"Webhooks (n8n)","text":""},{"location":"WEBHOOK_N8N/#create-a-simple-n8n-workflow","title":"Create a simple n8n workflow","text":"<p>Note</p> <p>You need to enable the <code>WEBHOOK</code> plugin first in order to follow this guide. See the Plugins guide for details.</p> <p>N8N can be used for more advanced conditional notification use cases. For example, you want only to get notified if two out of a specified list of devices is down. Or you can use other plugins to process the notifiations further. The below is a simple example of sending an email on a webhook. </p> <p></p>"},{"location":"WEBHOOK_N8N/#specify-your-email-template","title":"Specify your email template","text":"<p>See sample JSON if you want to see the JSON paths used in the email template below </p> <pre><code>Events count: {{ $json[\"body\"][\"attachments\"][0][\"text\"][\"events\"].length }}\nNew devices count: {{ $json[\"body\"][\"attachments\"][0][\"text\"][\"new_devices\"].length }}\n</code></pre>"},{"location":"WEBHOOK_N8N/#get-your-webhook-in-n8n","title":"Get your webhook in n8n","text":""},{"location":"WEBHOOK_N8N/#configure-netalertx-to-point-to-the-above-url","title":"Configure NetAlertX to point to the above URL","text":""},{"location":"WEBHOOK_SECRET/","title":"Webhook Secrets","text":"<p>Note</p> <p>You need to enable the <code>WEBHOOK</code> plugin first in order to follow this guide. See the Plugins guide for details.</p>"},{"location":"WEBHOOK_SECRET/#how-does-the-signing-work","title":"How does the signing work?","text":"<p>NetAlertX will use the configured secret to create a hash signature of the request body. This SHA256-HMAC signature will appear in the <code>X-Webhook-Signature</code> header of each request to the webhook target URL. You can use the value of this header to validate the request was sent by NetAlertX.</p>"},{"location":"WEBHOOK_SECRET/#activating-webhook-signatures","title":"Activating webhook signatures","text":"<p>All you need to do in order to add a signature to the request headers is to set the <code>WEBHOOK_SECRET</code> config value to a non-empty string.</p>"},{"location":"WEBHOOK_SECRET/#validating-webhook-deliveries","title":"Validating webhook deliveries","text":"<p>There are a few things to keep in mind when validating the webhook delivery:</p> <ul> <li>NetAlertX uses an HMAC hex digest to compute the hash</li> <li>The signature in the <code>X-Webhook-Signature</code> header always starts with <code>sha256=</code></li> <li>The hash signature is generated using the configured <code>WEBHOOK_SECRET</code> and the request body.</li> <li>Never use a plain <code>==</code> operator. Instead, consider using a method like <code>secure_compare</code> or <code>crypto.timingSafeEqual</code>, which performs a \"constant time\" string comparison to help mitigate certain timing attacks against regular equality operators, or regular loops in JIT-optimized languages.</li> </ul>"},{"location":"WEBHOOK_SECRET/#testing-the-webhook-payload-validation","title":"Testing the webhook payload validation","text":"<p>You can use the following secret and payload to verify that your implementation is working correctly.</p> <p><code>secret</code>: 'this is my secret'</p> <p><code>payload</code>: '{\"test\":\"this is a test body\"}'</p> <p>If your implementation is correct, the signature you generated should match the following:</p> <p><code>signature</code>: bed21fcc34f98e94fd71c7edb75e51a544b4a3b38b069ebaaeb19bf4be8147e9</p> <p><code>X-Webhook-Signature</code>: sha256=bed21fcc34f98e94fd71c7edb75e51a544b4a3b38b069ebaaeb19bf4be8147e9</p>"},{"location":"WEBHOOK_SECRET/#more-information","title":"More information","text":"<p>If you want to learn more about webhook security, take a look at GitHub's webhook documentation.</p> <p>You can find examples for validating a webhook delivery here.</p>"},{"location":"WEB_UI_PORT_DEBUG/","title":"Debugging inaccessible UI","text":"<p>The application uses the following default ports:</p> <ul> <li>Web UI: <code>20211</code> </li> <li>GraphQL API: <code>20212</code></li> </ul> <p>The Web UI is served by an nginx server, while the API backend runs on a Flask (Python) server.</p>"},{"location":"WEB_UI_PORT_DEBUG/#changing-ports","title":"Changing Ports","text":"<ul> <li>To change the Web UI port, update the <code>PORT</code> environment variable in the <code>docker-compose.yml</code> file.</li> <li>To change the GraphQL API port, use the <code>GRAPHQL_PORT</code> setting, either directly or via Docker: <code>yaml APP_CONF_OVERRIDE={\"GRAPHQL_PORT\":\"20212\"}</code></li> </ul> <p>For more information, check the Docker installation guide.</p>"},{"location":"WEB_UI_PORT_DEBUG/#possible-issues-and-troubleshooting","title":"Possible issues and troubleshooting","text":"<p>Follow all of the below in order to disqualify potential causes of issues and to troubleshoot these problems faster.</p>"},{"location":"WEB_UI_PORT_DEBUG/#1-port-conflicts","title":"1. Port conflicts","text":"<p>When opening an issue or debugging:</p> <ol> <li>Include a screenshot of what you see when accessing <code>HTTP://&lt;your rpi IP&gt;/20211</code> (or your custom port)</li> <li>Follow steps 1, 2, 3, 4 on this page </li> <li>Execute the following in the container to see the processes and their ports and submit a screenshot of the result:</li> <li><code>sudo apk add lsof</code></li> <li><code>sudo lsof -i</code></li> <li>Try running the <code>nginx</code> command in the container:</li> <li>if you get <code>nginx: [emerg] bind() to 0.0.0.0:20211 failed (98: Address in use)</code> try using a different port number</li> </ol> <p></p>"},{"location":"WEB_UI_PORT_DEBUG/#2-javascript-issues","title":"2. JavaScript issues","text":"<p>Check for browser console (F12 browser dev console) errors + check different browsers.</p>"},{"location":"WEB_UI_PORT_DEBUG/#3-clear-the-app-cache-and-cached-javascript-files","title":"3. Clear the app cache and cached JavaScript files","text":"<p>Refresh the browser cache (usually shoft + refresh), try a private window, or different browsers. Please also refresh the app cache by clicking the \ud83d\udd03 (reload) button in the header of the application. </p>"},{"location":"WEB_UI_PORT_DEBUG/#4-disable-proxies","title":"4. Disable proxies","text":"<p>If you have any reverse proxy or similar, try disabling it. </p>"},{"location":"WEB_UI_PORT_DEBUG/#5-disable-your-firewall","title":"5. Disable your firewall","text":"<p>If you are using a firewall, try to temporarily disabling it. </p>"},{"location":"WEB_UI_PORT_DEBUG/#6-post-your-docker-start-details","title":"6. Post your docker start details","text":"<p>If you haven't, post your docker compose/run command.</p>"},{"location":"WEB_UI_PORT_DEBUG/#7-check-for-errors-in-your-phpnginx-error-logs","title":"7. Check for errors in your PHP/NGINX error logs","text":"<p>In the container execute and investigate:</p> <p><code>cat /var/log/nginx/error.log</code></p> <p><code>cat /app/log/app.php_errors.log</code></p>"},{"location":"WEB_UI_PORT_DEBUG/#8-make-sure-permissions-are-correct","title":"8. Make sure permissions are correct","text":"<p>Tip</p> <p>You can try to start the container without mapping the <code>/app/config</code> and <code>/app/db</code> dirs and if the UI shows up then the issue is most likely related to your file system permissions or file ownership.</p> <p>Please read the Permissions troubleshooting guide and provide a screesnhot of the permissions and ownership in the <code>/app/db</code> and <code>app/config</code> directories. </p>"},{"location":"WORKFLOWS/","title":"Workflows Overview","text":"<p>The workflows module in allows to automate repetitive tasks, making network management more efficient. Whether you need to assign newly discovered devices to a specific Network Node, auto-group devices from a given vendor, unarchive a device if detected online, or automatically delete devices, this module provides the flexibility to tailor the automations to your needs.</p> <p></p> <p>Below are a few examples that demonstrate how this module can be used to simplify network management tasks.</p>"},{"location":"WORKFLOWS/#updating-workflows","title":"Updating Workflows","text":"<p>Note</p> <p>In order to apply a workflow change, you must first Save the changes and then reload the application by clicking Restart server.</p>"},{"location":"WORKFLOWS/#workflow-components","title":"Workflow components","text":""},{"location":"WORKFLOWS/#triggers","title":"Triggers","text":"<p>Triggers define the event that activates a workflow. They monitor changes to objects within the system, such as updates to devices or the insertion of new entries. When the specified event occurs, the workflow is executed.</p> <p>Tip</p> <p>Workflows not running? Check the Workflows debugging guide how to troubleshoot triggers and conditions.</p>"},{"location":"WORKFLOWS/#example-trigger","title":"Example Trigger:","text":"<ul> <li>Object Type: <code>Devices</code></li> <li>Event Type: <code>update</code></li> </ul> <p>This trigger will activate when a <code>Device</code> object is updated.</p>"},{"location":"WORKFLOWS/#conditions","title":"Conditions","text":"<p>Conditions determine whether a workflow should proceed based on certain criteria. These criteria can be set for specific fields, such as whether a device is from a certain vendor, or whether it is new or archived. You can combine conditions using logical operators (<code>AND</code>, <code>OR</code>).</p> <p>Tip</p> <p>To better understand how to use specific Device fields, please read through the Database overview guide.</p>"},{"location":"WORKFLOWS/#example-condition","title":"Example Condition:","text":"<ul> <li>Logic: <code>AND</code></li> <li>Field: <code>devVendor</code></li> <li>Operator: <code>contains</code> (case in-sensitive)</li> <li>Value: <code>Google</code></li> </ul> <p>This condition checks if the device's vendor is <code>Google</code>. The workflow will only proceed if the condition is true.</p>"},{"location":"WORKFLOWS/#actions","title":"Actions","text":"<p>Actions define the tasks that the workflow will perform once the conditions are met. Actions can include updating fields or deleting devices.</p> <p>You can include multiple actions that should execute once the conditions are met.</p>"},{"location":"WORKFLOWS/#example-action","title":"Example Action:","text":"<ul> <li>Action Type: <code>update_field</code></li> <li>Field: <code>devIsNew</code></li> <li>Value: <code>0</code></li> </ul> <p>This action updates the <code>devIsNew</code> field to <code>0</code>, marking the device as no longer new.</p>"},{"location":"WORKFLOWS/#examples","title":"Examples","text":"<p>You can find a couple of configuration examples in Workflow Examples.</p> <p>Tip</p> <p>Share your workflows in Discord or GitHub Discussions.</p>"},{"location":"WORKFLOWS_DEBUGGING/","title":"Workflows debugging and troubleshooting","text":"<p>Tip</p> <p>Before troubleshooting, please ensure you have Debugging enabled.</p> <p>Workflows are triggered by various events. These events are captured and listed in the Integrations -&gt; App Events section of the application. </p>"},{"location":"WORKFLOWS_DEBUGGING/#troubleshooting-triggers","title":"Troubleshooting triggers","text":"<p>Note</p> <p>Workflow events are processed once every 5 seconds. However, if a scan or other background tasks are running, this can cause a delay up to a few minutes.</p> <p>If an event doesn't trigger a workflow as expected, check the App Events section for the event. You can filter these by the ID of the device (<code>devMAC</code> or <code>devGUID</code>). </p> <p></p> <p>Once you find the Event Guid and Object GUID, use them to find relevant debug entries. </p> <p>Navigate to Mainetenace -&gt; Logs where you can filter the logs based on the Event or Object GUID. </p> <p></p> <p>Below you can find some example <code>app.log</code> entries that will help you understand why a Workflow was or was not triggered.</p> <pre><code>16:27:03 [WF] Checking if '13f0ce26-1835-4c48-ae03-cdaf38f328fe' triggers the workflow 'Sample Device Update Workflow'\n16:27:03 [WF] self.triggered 'False' for event '[[155], ['13f0ce26-1835-4c48-ae03-cdaf38f328fe'], [0], ['2025-04-02 05:26:56'], ['Devices'], ['050b6980-7af6-4409-950d-08e9786b7b33'], ['DEVICES'], ['00:11:32:ef:a5:6c'], ['192.168.1.82'], ['050b6980-7af6-4409-950d-08e9786b7b33'], [None], [0], [0], ['devPresentLastScan'], ['online'], ['update'], [None], [None], [None], [None]] and trigger {\"object_type\": \"Devices\", \"event_type\": \"insert\"}' \n16:27:03 [WF] Checking if '13f0ce26-1835-4c48-ae03-cdaf38f328fe' triggers the workflow 'Location Change'\n16:27:03 [WF] self.triggered 'True' for event '[[155], ['13f0ce26-1835-4c48-ae03-cdaf38f328fe'], [0], ['2025-04-02 05:26:56'], ['Devices'], ['050b6980-7af6-4409-950d-08e9786b7b33'], ['DEVICES'], ['00:11:32:ef:a5:6c'], ['192.168.1.82'], ['050b6980-7af6-4409-950d-08e9786b7b33'], [None], [0], [0], ['devPresentLastScan'], ['online'], ['update'], [None], [None], [None], [None]] and trigger {\"object_type\": \"Devices\", \"event_type\": \"update\"}' \n16:27:03 [WF] Event with GUID '13f0ce26-1835-4c48-ae03-cdaf38f328fe' triggered the workflow 'Location Change'\n</code></pre> <p>Note how one trigger executed, but the other didn't based on different <code>\"event_type\"</code> values. One is <code>\"event_type\": \"insert\"</code>, the other <code>\"event_type\": \"update\"</code>.</p> <p>Given the Event is a update event (note <code>...['online'], ['update'], [None]...</code> in the event structure), the <code>\"event_type\": \"insert\"</code> trigger didn't execute.</p>"},{"location":"WORKFLOW_EXAMPLES/","title":"Workflow examples","text":"<p>Workflows in NetAlertX automate actions based on real-time events and conditions. Below are practical examples that demonstrate how to build automation using triggers, conditions, and actions.</p>"},{"location":"WORKFLOW_EXAMPLES/#example-1-un-archive-devices-if-detected-online","title":"Example 1: Un-archive devices if detected online","text":"<p>This workflow automatically unarchives a device if it was previously archived but has now been detected as online.</p>"},{"location":"WORKFLOW_EXAMPLES/#use-case","title":"\ud83d\udccb Use Case","text":"<p>Sometimes devices are manually archived (e.g., no longer expected on the network), but they reappear unexpectedly. This workflow reverses the archive status when such devices are detected during a scan.</p>"},{"location":"WORKFLOW_EXAMPLES/#workflow-configuration","title":"\u2699\ufe0f Workflow Configuration","text":"<pre><code>{\n \"name\": \"Un-archive devices if detected online\",\n \"trigger\": {\n \"object_type\": \"Devices\",\n \"event_type\": \"update\"\n },\n \"conditions\": [\n {\n \"logic\": \"AND\",\n \"conditions\": [\n {\n \"field\": \"devIsArchived\",\n \"operator\": \"equals\",\n \"value\": \"1\"\n },\n {\n \"field\": \"devPresentLastScan\",\n \"operator\": \"equals\",\n \"value\": \"1\"\n }\n ]\n }\n ],\n \"actions\": [\n {\n \"type\": \"update_field\",\n \"field\": \"devIsArchived\",\n \"value\": \"0\"\n }\n ],\n \"enabled\": \"Yes\"\n}\n</code></pre>"},{"location":"WORKFLOW_EXAMPLES/#explanation","title":"\ud83d\udd0d Explanation","text":"<pre><code>- Trigger: Listens for updates to device records.\n- Conditions:\n - `devIsArchived` is `1` (archived).\n - `devPresentLastScan` is `1` (device was detected in the latest scan).\n- Action: Updates the device to set `devIsArchived` to `0` (unarchived).\n</code></pre>"},{"location":"WORKFLOW_EXAMPLES/#result","title":"\u2705 Result","text":"<p>Whenever a previously archived device shows up during a network scan, it will be automatically unarchived \u2014 allowing it to reappear in your device lists and dashboards.</p> <p>Here is your updated version of Example 2 and Example 3, fully aligned with the format and structure of Example 1 for consistency and professionalism:</p>"},{"location":"WORKFLOW_EXAMPLES/#example-2-assign-device-to-network-node-based-on-ip","title":"Example 2: Assign Device to Network Node Based on IP","text":"<p>This workflow assigns newly added devices with IP addresses in the <code>192.168.1.*</code> range to a specific network node with MAC address <code>6c:6d:6d:6c:6c:6c</code>.</p>"},{"location":"WORKFLOW_EXAMPLES/#use-case_1","title":"\ud83d\udccb Use Case","text":"<p>When new devices join your network, assigning them to the correct network node is important for accurate topology and grouping. This workflow ensures devices in a specific subnet are automatically linked to the intended node.</p>"},{"location":"WORKFLOW_EXAMPLES/#workflow-configuration_1","title":"\u2699\ufe0f Workflow Configuration","text":"<pre><code>{\n \"name\": \"Assign Device to Network Node Based on IP\",\n \"trigger\": {\n \"object_type\": \"Devices\",\n \"event_type\": \"insert\"\n },\n \"conditions\": [\n {\n \"logic\": \"AND\",\n \"conditions\": [\n {\n \"field\": \"devLastIP\",\n \"operator\": \"contains\",\n \"value\": \"192.168.1.\"\n }\n ]\n }\n ],\n \"actions\": [\n {\n \"type\": \"update_field\",\n \"field\": \"devNetworkNode\",\n \"value\": \"6c:6d:6d:6c:6c:6c\"\n }\n ],\n \"enabled\": \"Yes\"\n}\n</code></pre>"},{"location":"WORKFLOW_EXAMPLES/#explanation_1","title":"\ud83d\udd0d Explanation","text":"<ul> <li>Trigger: Activates when a new device is added.</li> <li> <p>Condition:</p> </li> <li> <p><code>devLastIP</code> contains <code>192.168.1.</code> (matches subnet).</p> </li> <li> <p>Action:</p> </li> <li> <p>Sets <code>devNetworkNode</code> to the specified MAC address.</p> </li> </ul>"},{"location":"WORKFLOW_EXAMPLES/#result_1","title":"\u2705 Result","text":"<p>New devices with IPs in the <code>192.168.1.*</code> subnet are automatically assigned to the correct network node, streamlining device organization and reducing manual work.</p>"},{"location":"WORKFLOW_EXAMPLES/#example-3-mark-device-as-not-new-and-delete-if-from-google-vendor","title":"Example 3: Mark Device as Not New and Delete If from Google Vendor","text":"<p>This workflow automatically marks newly detected Google devices as not new and deletes them immediately.</p>"},{"location":"WORKFLOW_EXAMPLES/#use-case_2","title":"\ud83d\udccb Use Case","text":"<p>You may want to automatically clear out newly detected Google devices (such as Chromecast or Google Home) if they\u2019re not needed in your device database. This workflow handles that clean-up automatically.</p>"},{"location":"WORKFLOW_EXAMPLES/#workflow-configuration_2","title":"\u2699\ufe0f Workflow Configuration","text":"<pre><code>{\n \"name\": \"Mark Device as Not New and Delete If from Google Vendor\",\n \"trigger\": {\n \"object_type\": \"Devices\",\n \"event_type\": \"update\"\n },\n \"conditions\": [\n {\n \"logic\": \"AND\",\n \"conditions\": [\n {\n \"field\": \"devVendor\",\n \"operator\": \"contains\",\n \"value\": \"Google\"\n },\n {\n \"field\": \"devIsNew\",\n \"operator\": \"equals\",\n \"value\": \"1\"\n }\n ]\n }\n ],\n \"actions\": [\n {\n \"type\": \"update_field\",\n \"field\": \"devIsNew\",\n \"value\": \"0\"\n },\n {\n \"type\": \"delete_device\"\n }\n ],\n \"enabled\": \"Yes\"\n}\n</code></pre>"},{"location":"WORKFLOW_EXAMPLES/#explanation_2","title":"\ud83d\udd0d Explanation","text":"<ul> <li>Trigger: Runs on device updates.</li> <li> <p>Conditions:</p> </li> <li> <p>Vendor contains <code>Google</code>.</p> </li> <li>Device is marked as new (<code>devIsNew</code> is <code>1</code>).</li> <li> <p>Actions:</p> </li> <li> <p>Set <code>devIsNew</code> to <code>0</code> (mark as not new).</p> </li> <li>Delete the device.</li> </ul>"},{"location":"WORKFLOW_EXAMPLES/#result_2","title":"\u2705 Result","text":"<p>Any newly detected Google devices are cleaned up instantly \u2014 first marked as not new, then deleted \u2014 helping you avoid clutter in your device records.</p>"}]}