Compare commits

..

26 Commits

Author SHA1 Message Date
Jokob @NetAlertX
4f2fa86a49 feat(docs): Update coding standards to clarify database storage guidelines
Some checks are pending
🐳 ⚠ docker-unsafe from next_release branch / docker_dev_unsafe (push) Waiting to run
2026-04-03 01:56:42 +00:00
Jokob @NetAlertX
3f80d2e57f feat(plugins): Implement /plugins/stats endpoint for per-plugin row counts with optional foreignKey filtering
Some checks failed
🐳 ⚠ docker-unsafe from next_release branch / docker_dev_unsafe (push) Has been cancelled
2026-03-27 21:35:41 +00:00
Jokob @NetAlertX
b18cf98266 feat(plugins): Enhance plugin counts handling with fail-open support and improved comments 2026-03-27 10:59:22 +00:00
Jokob @NetAlertX
77369c3ce8 feat(plugins): Optimize plugin badge fetching and rendering to prevent flicker and enhance visibility 2026-03-27 10:41:18 +00:00
Jokob @NetAlertX
cd0a3f6de0 feat(plugins): Refactor auto-hide functionality to leverage Bootstrap's tab management for improved visibility handling 2026-03-27 10:12:12 +00:00
Jokob @NetAlertX
13e91731be feat(plugins): Improve auto-hide functionality for empty plugin tabs by ensuring proper visibility handling and Bootstrap integration 2026-03-27 09:49:21 +00:00
Jokob @NetAlertX
7ef19b1c12 feat(plugins): Implement auto-hide functionality for empty plugin tabs 2026-03-27 09:26:25 +00:00
Jokob @NetAlertX
4daead1f8f feat(plugins): Enhance badge fetching with conditional JSON and GraphQL support 2026-03-27 08:08:44 +00:00
Jokob @NetAlertX
48454f6f2f feat(plugins): Optimize badge fetching by using lightweight JSON instead of GraphQL 2026-03-27 07:30:13 +00:00
Jokob @NetAlertX
7305fd78e3 fix(pagination): Ensure page number is always at least 1 in apply_common_pagination 2026-03-27 06:51:17 +00:00
Jokob @NetAlertX
ec3e4c8988 feat(api): Enhance session events API with pagination, sorting, and filtering
- Added support for pagination (page and limit) in the session events endpoint.
- Implemented sorting functionality based on specified columns and directions.
- Introduced free-text search capability for session events.
- Updated SQL queries to retrieve all events and added a new SQL constant for events.
- Refactored GraphQL types and helpers to support new plugin and event queries.
- Created new GraphQL resolvers for plugins and events with pagination and filtering.
- Added comprehensive tests for new GraphQL endpoints and session events functionality.
2026-03-26 20:57:10 +00:00
jokob-sk
250e533655 DOCS: pin mkdocs version
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2026-03-25 06:26:57 +11:00
jokob-sk
37730301f4 BE: lazy SQL execution caused devIsSleeping to be missing and tiles not show #1569 #1250
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2026-03-23 09:55:45 +11:00
Jokob @NetAlertX
7278ee8cfa Refactor getTotals method to clarify API contract and ensure stable response structure #1569 #1561 2026-03-21 21:28:42 +00:00
Jokob @NetAlertX
fa22523a0b Refactor device tiles SQL logic to use get_sql_devices_tiles function for improved maintainability Feature Request - Flapping and Sleeping nuances
Fixes #1567
2026-03-21 21:10:37 +00:00
Jokob @NetAlertX
7569923481 Refactor column name replacements to include variations for ObjectPrimaryID and ObjectSecondaryID 2026-03-21 20:55:24 +00:00
Jokob @NetAlertX
d7c7bd2cd2 Enhance SQL templates to prevent duplicate notifications for 'Down Reconnected' devices in event section 2026-03-18 09:57:20 +00:00
Jokob @NetAlertX
b311113575 Fix Spanish translations and improve HTML attributes in config files and report 2026-03-17 11:58:53 +00:00
Jokob @NetAlertX
43984132c4 Fix Spanish translations in config.json files for internet_speedtest, nmap_scan, and snmp_discovery plugins 2026-03-17 09:46:27 +00:00
Jokob @NetAlertX
0a7ecb5b7c Update config.json files to add 'ordeable' option and refactor cacheStrings function for consistency 2026-03-17 09:22:25 +00:00
Jokob @NetAlertX
c7399215ec Refactor event and session column names to camelCase
- Updated test cases to reflect new column names (eve_MAC -> eveMac, eve_DateTime -> eveDateTime, etc.) across various test files.
- Modified SQL table definitions in the database cleanup and migration tests to use camelCase naming conventions.
- Implemented migration tests to ensure legacy column names are correctly renamed to camelCase equivalents.
- Ensured that existing data is preserved during the migration process and that views referencing old column names are dropped before renaming.
- Verified that the migration function is idempotent, allowing for safe re-execution without data loss.
2026-03-16 10:11:22 +00:00
Jokob @NetAlertX
0bb6db155b Merge branch 'next_release' of https://github.com/netalertx/NetAlertX into next_release 2026-03-15 01:42:23 +00:00
Jokob @NetAlertX
7221b4ba96 Keep all local changes while resolving conflicts 2026-03-15 01:19:34 +00:00
Jokob @NetAlertX
c4904739b2 Merge pull request #1559 from netalertx/main
sync
2026-03-15 12:15:19 +11:00
Jokob @NetAlertX
67cab9d606 Merge branch 'main' of https://github.com/netalertx/NetAlertX 2026-03-14 23:27:45 +00:00
Jokob @NetAlertX
f75c53fc5d Implement notification text templates and update related settings for customizable notifications 2026-03-14 23:27:29 +00:00
123 changed files with 4758 additions and 2471 deletions

View File

@@ -12,6 +12,8 @@ description: NetAlertX coding standards and conventions. Use this when writing c
- code has to be maintainable, no duplicate code
- follow DRY principle - maintainability of code is more important than speed of implementation
- code files should be less than 500 LOC for better maintainability
- DB columns must not contain underscores, use camelCase instead (e.g., deviceInstanceId, not device_instance_id)
- treat DB as temporary storage for stats, long term configuration should be stored in the /config folder, the /config folder should allow you to restore most of your functionality (excluding historical data)
## File Length
@@ -64,7 +66,7 @@ Use timeNowUTC(as_string=False) for datetime operations (scheduling, comparisons
## String Sanitization
Use sanitizers from `server/helper.py` before storing user input.
Use sanitizers from `server/helper.py` before storing user input. MAC addresses are always lowercased and normalized. IP addresses should be validated.
## Devcontainer Constraints

View File

@@ -22,8 +22,10 @@ jobs:
- name: Install MkDocs
run: |
pip install mkdocs mkdocs-material
pip install mkdocs-github-admonitions-plugin
pip install \
mkdocs==1.6.0 \
mkdocs-material==9.5.21 \
mkdocs-github-admonitions-plugin==0.0.4
- name: Build MkDocs
run: mkdocs build

View File

@@ -15,6 +15,21 @@ Before opening a new issue:
- [Check Common Issues & Debug Tips](https://docs.netalertx.com/DEBUG_TIPS#common-issues)
- [Search Closed Issues](https://github.com/netalertx/NetAlertX/issues?q=is%3Aissue+is%3Aclosed)
---
## Use of AI
Use of AI-assisted tools is permitted, provided all generated code is reviewed, understood, and verified before submission.
- All AI-generated code must meet the project's **quality, security, and performance standards**.
- Contributors are responsible for **fully understanding** any code they submit, regardless of how it was produced.
- Prefer **clarity and maintainability over cleverness or brevity**. Readable code is always favored over dense or obfuscated implementations.
- Follow the **DRY (Don't Repeat Yourself) principle** where appropriate, without sacrificing readability.
- Do not submit code that you cannot confidently explain or debug.
All changes must pass the **full test suite** before opening a PR.
---
## Submitting Pull Requests (PRs)
@@ -28,11 +43,19 @@ Please:
- Provide a clear title and description for your PR
- If relevant, add or update tests and documentation
- For plugins, refer to the [Plugin Dev Guide](https://docs.netalertx.com/PLUGINS_DEV)
- Switch the PR to DRAFT mode if still being worked on
- Keep PRs **focused and minimal** — avoid unrelated changes in a single PR
- PRs that do not meet these guidelines may be closed without review
## Commit Messages
## Code quality
- Use clear, descriptive commit messages
- Explain *why* a change was made, not just *what* changed
- Reference related issues where applicable
- read and follow the [code-standards](/.github/skills/code-standards/SKILL.md)
## Code Quality
- Read and follow the [code standards](/.github/skills/code-standards/SKILL.md)
---

View File

@@ -58,12 +58,12 @@ The Events API provides access to **device event logs**, allowing creation, retr
"success": true,
"events": [
{
"eve_MAC": "00:11:22:33:44:55",
"eve_IP": "192.168.1.10",
"eve_DateTime": "2025-08-24T12:00:00Z",
"eve_EventType": "Device Down",
"eve_AdditionalInfo": "",
"eve_PendingAlertEmail": 1
"eveMac": "00:11:22:33:44:55",
"eveIp": "192.168.1.10",
"eveDateTime": "2025-08-24T12:00:00Z",
"eveEventType": "Device Down",
"eveAdditionalInfo": "",
"evePendingAlertEmail": 1
}
]
}
@@ -102,11 +102,11 @@ The Events API provides access to **device event logs**, allowing creation, retr
"count": 5,
"events": [
{
"eve_DateTime": "2025-12-07 12:00:00",
"eve_EventType": "New Device",
"eve_MAC": "AA:BB:CC:DD:EE:FF",
"eve_IP": "192.168.1.100",
"eve_AdditionalInfo": "Device detected"
"eveDateTime": "2025-12-07 12:00:00",
"eveEventType": "New Device",
"eveMac": "AA:BB:CC:DD:EE:FF",
"eveIp": "192.168.1.100",
"eveAdditionalInfo": "Device detected"
}
]
}
@@ -127,9 +127,9 @@ The Events API provides access to **device event logs**, allowing creation, retr
"count": 10,
"events": [
{
"eve_DateTime": "2025-12-07 12:00:00",
"eve_EventType": "Device Down",
"eve_MAC": "AA:BB:CC:DD:EE:FF"
"eveDateTime": "2025-12-07 12:00:00",
"eveEventType": "Device Down",
"eveMac": "AA:BB:CC:DD:EE:FF"
}
]
}
@@ -159,9 +159,9 @@ The Events API provides access to **device event logs**, allowing creation, retr
1. Total events in the period
2. Total sessions
3. Missing sessions
4. Voided events (`eve_EventType LIKE 'VOIDED%'`)
5. New device events (`eve_EventType LIKE 'New Device'`)
6. Device down events (`eve_EventType LIKE 'Device Down'`)
4. Voided events (`eveEventType LIKE 'VOIDED%'`)
5. New device events (`eveEventType LIKE 'New Device'`)
6. Device down events (`eveEventType LIKE 'Device Down'`)
---
@@ -187,7 +187,7 @@ Event endpoints are available as **MCP Tools** for AI assistant integration:
```
* Events are stored in the **Events table** with the following fields:
`eve_MAC`, `eve_IP`, `eve_DateTime`, `eve_EventType`, `eve_AdditionalInfo`, `eve_PendingAlertEmail`.
`eveMac`, `eveIp`, `eveDateTime`, `eveEventType`, `eveAdditionalInfo`, `evePendingAlertEmail`.
* Event creation automatically logs activity for debugging.

View File

@@ -4,6 +4,10 @@ GraphQL queries are **read-optimized for speed**. Data may be slightly out of da
* Devices
* Settings
* Events
* PluginsObjects
* PluginsHistory
* PluginsEvents
* Language Strings (LangStrings)
## Endpoints
@@ -254,11 +258,160 @@ curl 'http://host:GRAPHQL_PORT/graphql' \
---
## Plugin Tables (Objects, Events, History)
Three queries expose the plugin database tables with server-side pagination, filtering, and search:
* `pluginsObjects` — current plugin object state
* `pluginsEvents` — unprocessed plugin events
* `pluginsHistory` — historical plugin event log
All three share the same `PluginQueryOptionsInput` and return the same `PluginEntry` shape.
### Sample Query
```graphql
query GetPluginObjects($options: PluginQueryOptionsInput) {
pluginsObjects(options: $options) {
dbCount
count
entries {
index plugin objectPrimaryId objectSecondaryId
dateTimeCreated dateTimeChanged
watchedValue1 watchedValue2 watchedValue3 watchedValue4
status extra userData foreignKey
syncHubNodeName helpVal1 helpVal2 helpVal3 helpVal4 objectGuid
}
}
}
```
### Query Parameters (`PluginQueryOptionsInput`)
| Parameter | Type | Description |
| ------------ | ----------------- | ------------------------------------------------------ |
| `page` | Int | Page number (1-based). |
| `limit` | Int | Rows per page (max 1000). |
| `sort` | [SortOptionsInput] | Sorting options (`field`, `order`). |
| `search` | String | Free-text search across key columns. |
| `filters` | [FilterOptionsInput] | Column-value exact-match filters. |
| `plugin` | String | Plugin prefix to scope results (e.g. `"ARPSCAN"`). |
| `foreignKey` | String | Foreign key filter (e.g. device MAC). |
| `dateFrom` | String | Start of date range filter on `dateTimeCreated`. |
| `dateTo` | String | End of date range filter on `dateTimeCreated`. |
### Response Fields
| Field | Type | Description |
| --------- | ------------- | ------------------------------------------------------------- |
| `dbCount` | Int | Total rows for the requested plugin (before search/filters). |
| `count` | Int | Total rows after all filters (before pagination). |
| `entries` | [PluginEntry] | Paginated list of plugin entries. |
### `curl` Example
```sh
curl 'http://host:GRAPHQL_PORT/graphql' \
-X POST \
-H 'Authorization: Bearer API_TOKEN' \
-H 'Content-Type: application/json' \
--data '{
"query": "query GetPluginObjects($options: PluginQueryOptionsInput) { pluginsObjects(options: $options) { dbCount count entries { index plugin objectPrimaryId status foreignKey } } }",
"variables": {
"options": {
"plugin": "ARPSCAN",
"page": 1,
"limit": 25
}
}
}'
```
### Badge Prefetch (Batched Counts)
Use GraphQL aliases to fetch counts for all plugins in a single request:
```graphql
query BadgeCounts {
ARPSCAN: pluginsObjects(options: {plugin: "ARPSCAN", page: 1, limit: 1}) { dbCount }
INTRNT: pluginsObjects(options: {plugin: "INTRNT", page: 1, limit: 1}) { dbCount }
}
```
---
## Events Query
Access the Events table with server-side pagination, filtering, and search.
### Sample Query
```graphql
query GetEvents($options: EventQueryOptionsInput) {
events(options: $options) {
dbCount
count
entries {
eveMac
eveIp
eveDateTime
eveEventType
eveAdditionalInfo
evePendingAlertEmail
}
}
}
```
### Query Parameters (`EventQueryOptionsInput`)
| Parameter | Type | Description |
| ----------- | ------------------ | ------------------------------------------------ |
| `page` | Int | Page number (1-based). |
| `limit` | Int | Rows per page (max 1000). |
| `sort` | [SortOptionsInput] | Sorting options (`field`, `order`). |
| `search` | String | Free-text search across key columns. |
| `filters` | [FilterOptionsInput] | Column-value exact-match filters. |
| `eveMac` | String | Filter by device MAC address. |
| `eventType` | String | Filter by event type (e.g. `"New Device"`). |
| `dateFrom` | String | Start of date range filter on `eveDateTime`. |
| `dateTo` | String | End of date range filter on `eveDateTime`. |
### Response Fields
| Field | Type | Description |
| --------- | ------------ | ------------------------------------------------------------ |
| `dbCount` | Int | Total rows in the Events table (before any filters). |
| `count` | Int | Total rows after all filters (before pagination). |
| `entries` | [EventEntry] | Paginated list of event entries. |
### `curl` Example
```sh
curl 'http://host:GRAPHQL_PORT/graphql' \
-X POST \
-H 'Authorization: Bearer API_TOKEN' \
-H 'Content-Type: application/json' \
--data '{
"query": "query GetEvents($options: EventQueryOptionsInput) { events(options: $options) { dbCount count entries { eveMac eveIp eveDateTime eveEventType } } }",
"variables": {
"options": {
"eveMac": "00:11:22:33:44:55",
"page": 1,
"limit": 50
}
}
}'
```
---
## Notes
* Device, settings, and LangStrings queries can be combined in **one request** since GraphQL supports batching.
* Device, settings, LangStrings, plugin, and event queries can be combined in **one request** since GraphQL supports batching.
* The `fallback_to_en` feature ensures UI always has a value even if a translation is missing.
* Data is **cached in memory** per JSON file; changes to language or plugin files will only refresh after the cache detects a file modification.
* The `setOverriddenByEnv` flag helps identify setting values that are locked at container runtime.
* The schema is **read-only** — updates must be performed through other APIs or configuration management. See the other [API](API.md) endpoints for details.
* Plugin queries scope `dbCount` to the requested `plugin`/`foreignKey` so badge counts reflect per-plugin totals.
* The schema is **read-only** — updates must be performed through other APIs or configuration management. See the other [API](API.md) endpoints for details.

View File

@@ -106,12 +106,12 @@ curl -X DELETE "http://<server_ip>:<GRAPHQL_PORT>/sessions/delete" \
"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": ""
"sesMac": "AA:BB:CC:DD:EE:FF",
"sesDateTimeConnection": "2025-08-01 10:00",
"sesDateTimeDisconnection": "2025-08-01 12:00",
"sesDuration": "2h 0m",
"sesIp": "192.168.1.10",
"sesAdditionalInfo": ""
}
]
}
@@ -194,12 +194,12 @@ curl -X GET "http://<server_ip>:<GRAPHQL_PORT>/sessions/calendar?start=2025-08-0
"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": ""
"sesMac": "AA:BB:CC:DD:EE:FF",
"sesDateTimeConnection": "2025-08-01 10:00",
"sesDateTimeDisconnection": "2025-08-01 12:00",
"sesDuration": "2h 0m",
"sesIp": "192.168.1.10",
"sesAdditionalInfo": ""
}
]
}
@@ -224,15 +224,33 @@ curl -X GET "http://<server_ip>:<GRAPHQL_PORT>/sessions/AA:BB:CC:DD:EE:FF?period
* `type` → Event type (`all`, `sessions`, `missing`, `voided`, `new`, `down`)
Default: `all`
* `period` → Period to retrieve events (`7 days`, `1 month`, etc.)
* `page` → Page number, 1-based (default: `1`)
* `limit` → Rows per page, max 1000 (default: `100`)
* `search` → Free-text search filter across all columns
* `sortCol` → Column index to sort by, 0-based (default: `0`)
* `sortDir` → Sort direction: `asc` or `desc` (default: `desc`)
**Example:**
```
/sessions/session-events?type=all&period=7 days
/sessions/session-events?type=all&period=7 days&page=1&limit=25&sortCol=3&sortDir=desc
```
**Response:**
Returns a list of events or sessions with formatted connection, disconnection, duration, and IP information.
```json
{
"data": [...],
"total": 150,
"recordsFiltered": 150
}
```
| Field | Type | Description |
| ----------------- | ---- | ------------------------------------------------- |
| `data` | list | Paginated rows (each row is a list of values). |
| `total` | int | Total rows before search filter. |
| `recordsFiltered` | int | Total rows after search filter (before paging). |
#### `curl` Example

View File

@@ -43,7 +43,7 @@ Input data from the plugin might cause mapping issues in specific edge cases. Lo
17:31:05 [Scheduler] run for PIHOLE: YES
17:31:05 [Plugin utils] ---------------------------------------------
17:31:05 [Plugin utils] display_name: PiHole (Device sync)
17: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
17:31:05 [Plugins] CMD: SELECT n.hwaddr AS objectPrimaryId, {s-quote}null{s-quote} AS objectSecondaryId, datetime() AS DateTime, na.ip AS watchedValue1, n.lastQuery AS watchedValue2, na.name AS watchedValue3, n.macVendor AS watchedValue4, {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
17:31:05 [Plugins] setTyp: subnets
17:31:05 [Plugin utils] Flattening the below array
17:31:05 ['192.168.1.0/24 --interface=eth1']
@@ -52,7 +52,7 @@ Input data from the plugin might cause mapping issues in specific edge cases. Lo
17:31:05 [Plugins] Convert to Base64: True
17:31:05 [Plugins] base64 value: b'MTkyLjE2OC4xLjAvMjQgLS1pbnRlcmZhY2U9ZXRoMQ=='
17:31:05 [Plugins] Timeout: 10
17: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
17:31:05 [Plugins] Executing: SELECT n.hwaddr AS objectPrimaryId, 'null' AS objectSecondaryId, datetime() AS DateTime, na.ip AS watchedValue1, n.lastQuery AS watchedValue2, na.name AS watchedValue3, n.macVendor AS watchedValue4, '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
🔻
17:31:05 [Plugins] SUCCESS, received 2 entries
17: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')]

View File

@@ -1,5 +1,8 @@
# Notifications 📧
> [!TIP]
> Want to customize how devices appear in text notifications? See [Notification Text Templates](NOTIFICATION_TEMPLATES.md).
There are 4 ways how to influence notifications:
1. On the device itself
@@ -33,7 +36,7 @@ The following device properties influence notifications. You can:
On almost all plugins there are 2 core settings, `<plugin>_WATCH` and `<plugin>_REPORT_ON`.
1. `<plugin>_WATCH` 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 `<plugin>_REPORT_ON` setting.
2. `<plugin>_REPORT_ON` let's you specify on which events the app should notify you. This is related to the `<plugin>_WATCH` setting. So if you select `watched-changed` and in `<plugin>_WATCH` you only select `Watched_Value1`, then a notification is triggered if `Watched_Value1` is changed from the previous value, but no notification is send if `Watched_Value2` changes.
2. `<plugin>_REPORT_ON` let's you specify on which events the app should notify you. This is related to the `<plugin>_WATCH` setting. So if you select `watched-changed` and in `<plugin>_WATCH` you only select `watchedValue1`, then a notification is triggered if `watchedValue1` is changed from the previous value, but no notification is send if `watchedValue2` changes.
Click the **Read more in the docs.** Link at the top of each plugin to get more details on how the given plugin works.

View File

@@ -0,0 +1,100 @@
# Notification Text Templates
> Customize how devices and events appear in **text** notifications (email previews, push notifications, Apprise messages).
By default, NetAlertX formats each device as a vertical list of `Header: Value` pairs. Text templates let you define a **single-line format per device** using `{FieldName}` placeholders — ideal for mobile notification previews and high-volume alerts.
HTML email tables are **not affected** by these templates.
## Quick Start
1. Go to **Settings → Notification Processing**.
2. Set a template string for the section you want to customize, e.g.:
- **Text Template: New Devices** → `{devName} ({eveMac}) - {eveIp}`
3. Save. The next notification will use your format.
**Before (default):**
```
🆕 New devices
---------
devName: MyPhone
eveMac: aa:bb:cc:dd:ee:ff
devVendor: Apple
eveIp: 192.168.1.42
eveDateTime: 2025-01-15 10:30:00
eveEventType: New Device
devComments:
```
**After (with template `{devName} ({eveMac}) - {eveIp}`):**
```
🆕 New devices
---------
MyPhone (aa:bb:cc:dd:ee:ff) - 192.168.1.42
```
## Settings Reference
| Setting | Type | Default | Description |
|---------|------|---------|-------------|
| `NTFPRCS_TEXT_SECTION_HEADERS` | Boolean | `true` | Show/hide section titles (e.g. `🆕 New devices \n---------`). |
| `NTFPRCS_TEXT_TEMPLATE_new_devices` | String | *(empty)* | Template for new device rows. |
| `NTFPRCS_TEXT_TEMPLATE_down_devices` | String | *(empty)* | Template for down device rows. |
| `NTFPRCS_TEXT_TEMPLATE_down_reconnected` | String | *(empty)* | Template for reconnected device rows. |
| `NTFPRCS_TEXT_TEMPLATE_events` | String | *(empty)* | Template for event rows. |
| `NTFPRCS_TEXT_TEMPLATE_plugins` | String | *(empty)* | Template for plugin event rows. |
When a template is **empty**, the section uses the original vertical `Header: Value` format (full backward compatibility).
## Template Syntax
Use `{FieldName}` to insert a value from the notification data. Field names are **case-sensitive** and must match the column names exactly.
```
{devName} ({eveMac}) connected at {eveDateTime}
```
- No loops, conditionals, or nesting — just simple string replacement.
- If a `{FieldName}` does not exist in the data, it is left as-is in the output (safe failure). For example, `{NonExistent}` renders literally as `{NonExistent}`.
## Variable Availability by Section
All four device sections (`new_devices`, `down_devices`, `down_reconnected`, `events`) share the same unified field names.
### `new_devices`, `down_devices`, `down_reconnected`, and `events`
| Variable | Description |
|----------|-------------|
| `{devName}` | Device display name |
| `{eveMac}` | Device MAC address |
| `{devVendor}` | Device vendor/manufacturer |
| `{eveIp}` | Device IP address |
| `{eveDateTime}` | Event timestamp |
| `{eveEventType}` | Type of event (e.g. `New Device`, `Connected`, `Device Down`) |
| `{devComments}` | Device comments |
**Example (new_devices/events):** `{devName} ({eveMac}) - {eveIp} [{eveEventType}]`
**Example (down_devices):** `{devName} ({eveMac}) {devVendor} - went down at {eveDateTime}`
**Example (down_reconnected):** `{devName} ({eveMac}) reconnected at {eveDateTime}`
### `plugins`
| Variable | Description |
|----------|-------------|
| `{plugin}` | Plugin code name |
| `{objectPrimaryId}` | Primary identifier of the object |
| `{objectSecondaryId}` | Secondary identifier |
| `{dateTimeChanged}` | Timestamp of change |
| `{watchedValue1}` | First watched value |
| `{watchedValue2}` | Second watched value |
| `{watchedValue3}` | Third watched value |
| `{watchedValue4}` | Fourth watched value |
| `{status}` | Plugin event status |
**Example:** `{plugin}: {objectPrimaryId} - {status}`
## Section Headers Toggle
Set **Text Section Headers** (`NTFPRCS_TEXT_SECTION_HEADERS`) to `false` to remove the section title separators from text notifications. This is useful when you want compact output without the `🆕 New devices \n---------` banners.

View File

@@ -179,13 +179,13 @@ Quick reference:
| Column | Name | Required | Example |
|--------|------|----------|---------|
| 0 | Object_PrimaryID | **YES** | `"device_name"` or `"192.168.1.1"` |
| 1 | Object_SecondaryID | no | `"secondary_id"` or `null` |
| 0 | objectPrimaryId | **YES** | `"device_name"` or `"192.168.1.1"` |
| 1 | objectSecondaryId | no | `"secondary_id"` or `null` |
| 2 | DateTime | **YES** | `"2023-01-02 15:56:30"` |
| 3 | Watched_Value1 | **YES** | `"online"` or `"200"` |
| 4 | Watched_Value2 | no | `"ip_address"` or `null` |
| 5 | Watched_Value3 | no | `null` |
| 6 | Watched_Value4 | no | `null` |
| 3 | watchedValue1 | **YES** | `"online"` or `"200"` |
| 4 | watchedValue2 | no | `"ip_address"` or `null` |
| 5 | watchedValue3 | no | `null` |
| 6 | watchedValue4 | no | `null` |
| 7 | Extra | no | `"additional data"` or `null` |
| 8 | ForeignKey | no | `"aa:bb:cc:dd:ee:ff"` or `null` |
@@ -243,7 +243,7 @@ Control which rows display in the UI:
{
"data_filters": [
{
"compare_column": "Object_PrimaryID",
"compare_column": "objectPrimaryId",
"compare_operator": "==",
"compare_field_id": "txtMacFilter",
"compare_js_template": "'{value}'.toString()",
@@ -267,7 +267,7 @@ To import plugin data into NetAlertX tables for device discovery or notification
"mapped_to_table": "CurrentScan",
"database_column_definitions": [
{
"column": "Object_PrimaryID",
"column": "objectPrimaryId",
"mapped_to_column": "scanMac",
"show": true,
"type": "device_mac",
@@ -345,7 +345,7 @@ See [PLUGINS_DEV_SETTINGS.md](PLUGINS_DEV_SETTINGS.md) for complete settings doc
### Plugin Output Format
```
Object_PrimaryID|Object_SecondaryID|DateTime|Watched_Value1|Watched_Value2|Watched_Value3|Watched_Value4|Extra|ForeignKey
objectPrimaryId|objectSecondaryId|DateTime|watchedValue1|watchedValue2|watchedValue3|watchedValue4|Extra|ForeignKey
```
9 required columns, 4 optional helpers = 13 max

View File

@@ -77,7 +77,7 @@ It also describes plugin output expectations and the main plugin categories.
* `database_column_definitions`
* `mapped_to_table`
**Example:** `Object_PrimaryID → devMAC`
**Example:** `objectPrimaryId → devMAC`
---
@@ -88,9 +88,9 @@ Output values are pipe-delimited in a fixed order.
#### Identifiers
* `Object_PrimaryID` and `Object_SecondaryID` uniquely identify records (for example, `MAC|IP`).
* `objectPrimaryId` and `objectSecondaryId` uniquely identify records (for example, `MAC|IP`).
#### Watched Values (`Watched_Value14`)
#### Watched Values (`watchedValue14`)
* Used by the core to detect changes between runs.
* Changes in these fields can trigger notifications.
@@ -114,7 +114,7 @@ Output values are pipe-delimited in a fixed order.
### 7. Persistence
* Parsed data is **upserted** into the database.
* Conflicts are resolved using the combined key: `Object_PrimaryID + Object_SecondaryID`.
* Conflicts are resolved using the combined key: `objectPrimaryId + objectSecondaryId`.
---

View File

@@ -107,7 +107,7 @@ Query the NetAlertX SQLite database and display results.
{
"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, dv.devMac as ForeignKey FROM (SELECT * FROM Nmap_Scan) ns LEFT JOIN (SELECT devName, devMac, devLastIP FROM Devices) dv ON ns.MAC = dv.devMac",
"default_value": "SELECT dv.devName as objectPrimaryId, cast(dv.devLastIP as VARCHAR(100)) || ':' || cast(SUBSTR(ns.Port, 0, INSTR(ns.Port, '/')) as VARCHAR(100)) as objectSecondaryId, datetime() as DateTime, ns.Service as watchedValue1, ns.State as watchedValue2, null as watchedValue3, null as watchedValue4, 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",
"localized": ["name"],
"name": [{"language_code": "en_us", "string": "SQL to run"}],
"description": [{"language_code": "en_us", "string": "This SQL query populates the plugin table"}]
@@ -118,13 +118,13 @@ Query the NetAlertX SQLite database and display results.
```sql
SELECT
e.EventValue as Object_PrimaryID,
d.devName as Object_SecondaryID,
e.EventValue as objectPrimaryId,
d.devName as objectSecondaryId,
e.EventDateTime as DateTime,
e.EventType as Watched_Value1,
d.devLastIP as Watched_Value2,
null as Watched_Value3,
null as Watched_Value4,
e.EventType as watchedValue1,
d.devLastIP as watchedValue2,
null as watchedValue3,
null as watchedValue4,
e.EventDetails as Extra,
d.devMac as ForeignKey
FROM
@@ -181,7 +181,7 @@ Then set data source and query:
```json
{
"function": "CMD",
"default_value": "SELECT hwaddr as Object_PrimaryID, cast('http://' || (SELECT ip FROM EXTERNAL_PIHOLE.network_addresses WHERE network_id = id ORDER BY lastseen DESC LIMIT 1) as VARCHAR(100)) || ':' || cast(SUBSTR((SELECT name FROM EXTERNAL_PIHOLE.network_addresses WHERE network_id = id ORDER BY lastseen DESC LIMIT 1), 0, INSTR((SELECT name FROM EXTERNAL_PIHOLE.network_addresses WHERE network_id = id ORDER BY lastseen DESC 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 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 <> '00:00:00:00:00:00'",
"default_value": "SELECT hwaddr as objectPrimaryId, cast('http://' || (SELECT ip FROM EXTERNAL_PIHOLE.network_addresses WHERE network_id = id ORDER BY lastseen DESC LIMIT 1) as VARCHAR(100)) || ':' || cast(SUBSTR((SELECT name FROM EXTERNAL_PIHOLE.network_addresses WHERE network_id = id ORDER BY lastseen DESC LIMIT 1), 0, INSTR((SELECT name FROM EXTERNAL_PIHOLE.network_addresses WHERE network_id = id ORDER BY lastseen DESC LIMIT 1), '/')) as VARCHAR(100)) as objectSecondaryId, datetime() as DateTime, macVendor as watchedValue1, lastQuery as watchedValue2, (SELECT name FROM EXTERNAL_PIHOLE.network_addresses WHERE network_id = id ORDER BY lastseen DESC LIMIT 1) as watchedValue3, null as watchedValue4, '' as Extra, hwaddr as ForeignKey FROM EXTERNAL_PIHOLE.network WHERE hwaddr NOT LIKE 'ip-%' AND hwaddr <> '00:00:00:00:00:00'",
"localized": ["name"],
"name": [{"language_code": "en_us", "string": "SQL to run"}]
}

View File

@@ -18,19 +18,19 @@ Plugins communicate with NetAlertX by writing results to a **pipe-delimited log
## Column Specification
> [!NOTE]
> The order of columns is **FIXED** and cannot be changed. All 9 mandatory columns must be provided. If you use any optional column (`HelpVal1`), you must supply all optional columns (`HelpVal1` through `HelpVal4`).
> The order of columns is **FIXED** and cannot be changed. All 9 mandatory columns must be provided. If you use any optional column (`helpVal1`), you must supply all optional columns (`helpVal1` through `helpVal4`).
### Mandatory Columns (08)
| Order | Column Name | Type | Required | Description |
|-------|-------------|------|----------|-------------|
| 0 | `Object_PrimaryID` | string | **YES** | The primary identifier for grouping. Examples: device MAC, hostname, service name, or any unique ID |
| 1 | `Object_SecondaryID` | string | no | Secondary identifier for relationships (e.g., IP address, port, sub-ID). Use `null` if not needed |
| 0 | `objectPrimaryId` | string | **YES** | The primary identifier for grouping. Examples: device MAC, hostname, service name, or any unique ID |
| 1 | `objectSecondaryId` | string | no | Secondary identifier for relationships (e.g., IP address, port, sub-ID). Use `null` if not needed |
| 2 | `DateTime` | string | **YES** | Timestamp when the event/data was collected. Format: `YYYY-MM-DD HH:MM:SS` |
| 3 | `Watched_Value1` | string | **YES** | Primary watched value. Changes trigger notifications. Examples: IP address, status, version |
| 4 | `Watched_Value2` | string | no | Secondary watched value. Use `null` if not needed |
| 5 | `Watched_Value3` | string | no | Tertiary watched value. Use `null` if not needed |
| 6 | `Watched_Value4` | string | no | Quaternary watched value. Use `null` if not needed |
| 3 | `watchedValue1` | string | **YES** | Primary watched value. Changes trigger notifications. Examples: IP address, status, version |
| 4 | `watchedValue2` | string | no | Secondary watched value. Use `null` if not needed |
| 5 | `watchedValue3` | string | no | Tertiary watched value. Use `null` if not needed |
| 6 | `watchedValue4` | string | no | Quaternary watched value. Use `null` if not needed |
| 7 | `Extra` | string | no | Any additional metadata to display in UI and notifications. Use `null` if not needed |
| 8 | `ForeignKey` | string | no | Foreign key linking to parent object (usually MAC address for device relationship). Use `null` if not needed |
@@ -38,10 +38,10 @@ Plugins communicate with NetAlertX by writing results to a **pipe-delimited log
| Order | Column Name | Type | Required | Description |
|-------|-------------|------|----------|-------------|
| 9 | `HelpVal1` | string | *conditional* | Helper value 1. If used, all help values must be supplied |
| 10 | `HelpVal2` | string | *conditional* | Helper value 2. If used, all help values must be supplied |
| 11 | `HelpVal3` | string | *conditional* | Helper value 3. If used, all help values must be supplied |
| 12 | `HelpVal4` | string | *conditional* | Helper value 4. If used, all help values must be supplied |
| 9 | `helpVal1` | string | *conditional* | Helper value 1. If used, all help values must be supplied |
| 10 | `helpVal2` | string | *conditional* | Helper value 2. If used, all help values must be supplied |
| 11 | `helpVal3` | string | *conditional* | Helper value 3. If used, all help values must be supplied |
| 12 | `helpVal4` | string | *conditional* | Helper value 4. If used, all help values must be supplied |
## Usage Guide
@@ -58,15 +58,15 @@ Watched values are fields that the NetAlertX core monitors for **changes between
**How to use them:**
- `Watched_Value1`: Always required; primary indicator of status/state
- `Watched_Value24`: Optional; use for secondary/tertiary state information
- `watchedValue1`: Always required; primary indicator of status/state
- `watchedValue24`: Optional; use for secondary/tertiary state information
- Leave unused ones as `null`
**Example:**
- Device scanner: `Watched_Value1 = "online"` or `"offline"`
- Port scanner: `Watched_Value1 = "80"` (port number), `Watched_Value2 = "open"` (state)
- Service monitor: `Watched_Value1 = "200"` (HTTP status), `Watched_Value2 = "0.45"` (response time)
- Device scanner: `watchedValue1 = "online"` or `"offline"`
- Port scanner: `watchedValue1 = "80"` (port number), `watchedValue2 = "open"` (state)
- Service monitor: `watchedValue1 = "200"` (HTTP status), `watchedValue2 = "0.45"` (response time)
### Foreign Key
@@ -110,14 +110,14 @@ https://google.com|null|2023-01-02 15:56:30|200|0.7898||null|null
Missing pipe
```
**Missing mandatory Watched_Value1** (column 3):
**Missing mandatory watchedValue1** (column 3):
```csv
https://duckduckgo.com|192.168.1.1|2023-01-02 15:56:30|null|0.9898|null|null|Best|null
Must not be null
```
**Incomplete optional columns** (has HelpVal1 but missing HelpVal24):
**Incomplete optional columns** (has helpVal1 but missing helpVal24):
```csv
device|null|2023-01-02 15:56:30|status|null|null|null|null|null|helper1
@@ -146,19 +146,19 @@ plugin_objects = Plugin_Objects("YOURPREFIX")
# Add objects
plugin_objects.add_object(
Object_PrimaryID="device_id",
Object_SecondaryID="192.168.1.1",
objectPrimaryId="device_id",
objectSecondaryId="192.168.1.1",
DateTime="2023-01-02 15:56:30",
Watched_Value1="online",
Watched_Value2=None,
Watched_Value3=None,
Watched_Value4=None,
watchedValue1="online",
watchedValue2=None,
watchedValue3=None,
watchedValue4=None,
Extra="Additional data",
ForeignKey="aa:bb:cc:dd:ee:ff",
HelpVal1=None,
HelpVal2=None,
HelpVal3=None,
HelpVal4=None
helpVal1=None,
helpVal2=None,
helpVal3=None,
helpVal4=None
)
# Write results (handles formatting, sanitization, and file creation)
@@ -177,7 +177,7 @@ The library automatically:
The core runs **de-duplication once per hour** on the `Plugins_Objects` table:
- **Duplicate Detection Key:** Combination of `Object_PrimaryID`, `Object_SecondaryID`, `Plugin` (auto-filled from `unique_prefix`), and `UserData`
- **Duplicate Detection Key:** Combination of `objectPrimaryId`, `objectSecondaryId`, `Plugin` (auto-filled from `unique_prefix`), and `UserData`
- **Resolution:** Oldest duplicate entries are removed, newest are kept
- **Use Case:** Prevents duplicate notifications when the same object is detected multiple times
@@ -213,9 +213,9 @@ Before writing your plugin's `script.py`, ensure:
- [ ] **9 or 13 columns** in each output line (8 or 12 pipe separators)
- [ ] **Mandatory columns filled:**
- Column 0: `Object_PrimaryID` (not null)
- Column 0: `objectPrimaryId` (not null)
- Column 2: `DateTime` in `YYYY-MM-DD HH:MM:SS` format
- Column 3: `Watched_Value1` (not null)
- Column 3: `watchedValue1` (not null)
- [ ] **Null values as literal string** `null` (not empty string or special chars)
- [ ] **No extra pipes or misaligned columns**
- [ ] **If using optional helpers** (columns 912), all 4 must be present

View File

@@ -68,13 +68,13 @@ try:
# Add an object to results
plugin_objects.add_object(
Object_PrimaryID="example_id",
Object_SecondaryID=None,
objectPrimaryId="example_id",
objectSecondaryId=None,
DateTime="2023-01-02 15:56:30",
Watched_Value1="value1",
Watched_Value2=None,
Watched_Value3=None,
Watched_Value4=None,
watchedValue1="value1",
watchedValue2=None,
watchedValue3=None,
watchedValue4=None,
Extra="additional_data",
ForeignKey=None
)

View File

@@ -16,7 +16,7 @@ Each column definition specifies:
```json
{
"column": "Object_PrimaryID",
"column": "objectPrimaryId",
"mapped_to_column": "devMac",
"mapped_to_column_data": null,
"css_classes": "col-sm-2",
@@ -39,7 +39,7 @@ Each column definition specifies:
| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `column` | string | **YES** | Source column name from data contract (e.g., `Object_PrimaryID`, `Watched_Value1`) |
| `column` | string | **YES** | Source column name from data contract (e.g., `objectPrimaryId`, `watchedValue1`) |
| `mapped_to_column` | string | no | Target database column if mapping to a table like `CurrentScan` |
| `mapped_to_column_data` | object | no | Static value to map instead of using column data |
| `css_classes` | string | no | Bootstrap CSS classes for width/spacing (e.g., `"col-sm-2"`, `"col-sm-6"`) |
@@ -64,7 +64,7 @@ Plain text display (read-only).
```json
{
"column": "Watched_Value1",
"column": "watchedValue1",
"show": true,
"type": "label",
"localized": ["name"],
@@ -99,7 +99,7 @@ Resolves an IP address to a MAC address and creates a device link.
```json
{
"column": "Object_SecondaryID",
"column": "objectSecondaryId",
"show": true,
"type": "device_ip",
"localized": ["name"],
@@ -117,7 +117,7 @@ Creates a device link with the target device's name as the link label.
```json
{
"column": "Object_PrimaryID",
"column": "objectPrimaryId",
"show": true,
"type": "device_name_mac",
"localized": ["name"],
@@ -135,7 +135,7 @@ Renders as a clickable HTTP/HTTPS link.
```json
{
"column": "Watched_Value1",
"column": "watchedValue1",
"show": true,
"type": "url",
"localized": ["name"],
@@ -153,7 +153,7 @@ Creates two links (HTTP and HTTPS) as lock icons for the given IP/hostname.
```json
{
"column": "Object_SecondaryID",
"column": "objectSecondaryId",
"show": true,
"type": "url_http_https",
"localized": ["name"],
@@ -207,7 +207,7 @@ Color-codes values based on ranges. Useful for status codes, latency, capacity p
```json
{
"column": "Watched_Value1",
"column": "watchedValue1",
"show": true,
"type": "threshold",
"options": [
@@ -252,7 +252,7 @@ Replaces specific values with display strings or HTML.
```json
{
"column": "Watched_Value2",
"column": "watchedValue2",
"show": true,
"type": "replace",
"options": [
@@ -286,7 +286,7 @@ Applies a regular expression to extract/transform values.
```json
{
"column": "Watched_Value1",
"column": "watchedValue1",
"show": true,
"type": "regex",
"options": [
@@ -310,7 +310,7 @@ Evaluates JavaScript code with access to the column value (use `${value}` or `{v
```json
{
"column": "Watched_Value1",
"column": "watchedValue1",
"show": true,
"type": "eval",
"default_value": "",
@@ -322,7 +322,7 @@ Evaluates JavaScript code with access to the column value (use `${value}` or `{v
**Example with custom formatting:**
```json
{
"column": "Watched_Value1",
"column": "watchedValue1",
"show": true,
"type": "eval",
"options": [
@@ -347,7 +347,7 @@ You can chain multiple transformations with dot notation:
```json
{
"column": "Watched_Value3",
"column": "watchedValue3",
"show": true,
"type": "regex.url_http_https",
"options": [
@@ -376,7 +376,7 @@ Use SQL query results to populate dropdown options:
```json
{
"column": "Watched_Value2",
"column": "watchedValue2",
"show": true,
"type": "select",
"options": ["{value}"],
@@ -405,7 +405,7 @@ Use plugin settings to populate options:
```json
{
"column": "Watched_Value1",
"column": "watchedValue1",
"show": true,
"type": "select",
"options": ["{value}"],
@@ -439,7 +439,7 @@ To import plugin data into the device scan pipeline (for notifications, heuristi
"mapped_to_table": "CurrentScan",
"database_column_definitions": [
{
"column": "Object_PrimaryID",
"column": "objectPrimaryId",
"mapped_to_column": "scanMac",
"show": true,
"type": "device_mac",
@@ -447,7 +447,7 @@ To import plugin data into the device scan pipeline (for notifications, heuristi
"name": [{"language_code": "en_us", "string": "MAC Address"}]
},
{
"column": "Object_SecondaryID",
"column": "objectSecondaryId",
"mapped_to_column": "scanLastIP",
"show": true,
"type": "device_ip",
@@ -501,7 +501,7 @@ Control which rows are displayed based on filter conditions. Filters are applied
{
"data_filters": [
{
"compare_column": "Object_PrimaryID",
"compare_column": "objectPrimaryId",
"compare_operator": "==",
"compare_field_id": "txtMacFilter",
"compare_js_template": "'{value}'.toString()",
@@ -545,7 +545,7 @@ When viewing a device detail page, the `txtMacFilter` field is populated with th
{
"database_column_definitions": [
{
"column": "Object_PrimaryID",
"column": "objectPrimaryId",
"mapped_to_column": "scanMac",
"css_classes": "col-sm-2",
"show": true,
@@ -555,7 +555,7 @@ When viewing a device detail page, the `txtMacFilter` field is populated with th
"name": [{"language_code": "en_us", "string": "MAC Address"}]
},
{
"column": "Object_SecondaryID",
"column": "objectSecondaryId",
"mapped_to_column": "scanLastIP",
"css_classes": "col-sm-2",
"show": true,
@@ -574,7 +574,7 @@ When viewing a device detail page, the `txtMacFilter` field is populated with th
"name": [{"language_code": "en_us", "string": "Last Seen"}]
},
{
"column": "Watched_Value1",
"column": "watchedValue1",
"css_classes": "col-sm-2",
"show": true,
"type": "threshold",
@@ -589,7 +589,7 @@ When viewing a device detail page, the `txtMacFilter` field is populated with th
"name": [{"language_code": "en_us", "string": "HTTP Status"}]
},
{
"column": "Watched_Value2",
"column": "watchedValue2",
"css_classes": "col-sm-1",
"show": true,
"type": "label",

View File

@@ -61,16 +61,16 @@ $(document).ready(function () {
appEvents(options: $options) {
count
appEvents {
DateTimeCreated
AppEventProcessed
AppEventType
ObjectType
ObjectPrimaryID
ObjectSecondaryID
ObjectStatus
ObjectPlugin
ObjectGUID
GUID
dateTimeCreated
appEventProcessed
appEventType
objectType
objectPrimaryId
objectSecondaryId
objectStatus
objectPlugin
objectGuid
guid
}
}
}
@@ -128,16 +128,16 @@ $(document).ready(function () {
},
columns: [
{ data: 'DateTimeCreated', title: getString('AppEvents_DateTimeCreated') },
{ data: 'AppEventProcessed', title: getString('AppEvents_AppEventProcessed') },
{ data: 'AppEventType', title: getString('AppEvents_Type') },
{ data: 'ObjectType', title: getString('AppEvents_ObjectType') },
{ data: 'ObjectPrimaryID', title: getString('AppEvents_ObjectPrimaryID') },
{ data: 'ObjectSecondaryID', title: getString('AppEvents_ObjectSecondaryID') },
{ data: 'ObjectStatus', title: getString('AppEvents_ObjectStatus') },
{ data: 'ObjectPlugin', title: getString('AppEvents_Plugin') },
{ data: 'ObjectGUID', title: 'Object GUID' },
{ data: 'GUID', title: 'Event GUID' }
{ data: 'dateTimeCreated', title: getString('AppEvents_DateTimeCreated') },
{ data: 'appEventProcessed', title: getString('AppEvents_AppEventProcessed') },
{ data: 'appEventType', title: getString('AppEvents_Type') },
{ data: 'objectType', title: getString('AppEvents_ObjectType') },
{ data: 'objectPrimaryId', title: getString('AppEvents_ObjectPrimaryID') },
{ data: 'objectSecondaryId', title: getString('AppEvents_ObjectSecondaryID') },
{ data: 'objectStatus', title: getString('AppEvents_ObjectStatus') },
{ data: 'objectPlugin', title: getString('AppEvents_Plugin') },
{ data: 'objectGuid', title: 'Object GUID' },
{ data: 'guid', title: 'Event GUID' }
],
columnDefs: [

View File

@@ -32,51 +32,64 @@
function loadEventsData() {
const hideConnections = $('#chkHideConnectionEvents')[0].checked;
const hideConnectionsStr = hideConnections ? 'true' : 'false';
let period = $("#period").val();
let { start, end } = getPeriodStartEnd(period);
const rawSql = `
SELECT eve_DateTime, eve_EventType, eve_IP, eve_AdditionalInfo
FROM Events
WHERE eve_MAC = "${mac}"
AND eve_DateTime BETWEEN "${start}" AND "${end}"
AND (
(eve_EventType NOT IN ("Connected", "Disconnected", "VOIDED - Connected", "VOIDED - Disconnected"))
OR "${hideConnectionsStr}" = "false"
)
const apiToken = getSetting("API_TOKEN");
const apiBase = getApiBase();
const graphqlUrl = `${apiBase}/graphql`;
const query = `
query Events($options: EventQueryOptionsInput) {
events(options: $options) {
count
entries {
eveDateTime
eveEventType
eveIp
eveAdditionalInfo
}
}
}
`;
const apiToken = getSetting("API_TOKEN");
const apiBaseUrl = getApiBase();
const url = `${apiBaseUrl}/dbquery/read`;
$.ajax({
url: url,
url: graphqlUrl,
method: "POST",
contentType: "application/json",
headers: {
"Authorization": `Bearer ${apiToken}`
},
data: JSON.stringify({
rawSql: btoa(rawSql)
query,
variables: {
options: {
eveMac: mac,
dateFrom: start,
dateTo: end,
limit: 500,
sort: [{ field: "eveDateTime", order: "desc" }]
}
}
}),
success: function (data) {
// assuming read_query returns rows directly
const rows = data["results"].map(row => {
const rawDate = row.eve_DateTime;
const formattedDate = rawDate ? localizeTimestamp(rawDate) : '-';
const CONNECTION_TYPES = ["Connected", "Disconnected", "VOIDED - Connected", "VOIDED - Disconnected"];
return [
formattedDate,
row.eve_DateTime,
row.eve_EventType,
row.eve_IP,
row.eve_AdditionalInfo
];
});
const rows = data.data.events.entries
.filter(row => !hideConnections || !CONNECTION_TYPES.includes(row.eveEventType))
.map(row => {
const rawDate = row.eveDateTime;
const formattedDate = rawDate ? localizeTimestamp(rawDate) : '-';
return [
formattedDate,
row.eveDateTime,
row.eveEventType,
row.eveIp,
row.eveAdditionalInfo
];
});
const table = $('#tableEvents').DataTable();
table.clear();

View File

@@ -121,12 +121,12 @@ function loadSessionsData() {
if (data.success && data.sessions.length) {
data.sessions.forEach(session => {
table.row.add([
session.ses_DateTimeOrder,
session.ses_Connection,
session.ses_Disconnection,
session.ses_Duration,
session.ses_IP,
session.ses_Info
session.sesDateTimeOrder,
session.sesConnection,
session.sesDisconnection,
session.sesDuration,
session.sesIp,
session.sesInfo
]);
});
}

View File

@@ -105,21 +105,64 @@ function main() {
$('#period').val(period);
initializeDatatable();
getEventsTotals();
getEvents(eventsType);
getEvents(eventsType); // triggers first serverSide draw
}
/* ---------------- Initialize DataTable ---------------- */
function initializeDatatable() {
const table = $('#tableEvents').DataTable({
paging: true,
const apiBase = getApiBase();
const apiToken = getSetting("API_TOKEN");
$('#tableEvents').DataTable({
processing: true,
serverSide: true,
paging: true,
lengthChange: true,
lengthMenu: getLengthMenu(getSetting("UI_DEFAULT_PAGE_SIZE")),
searching: true,
ordering: true,
info: true,
autoWidth: false,
order: [[0, "desc"], [3, "desc"], [5, "desc"]],
pageLength: tableRows,
lengthMenu: getLengthMenu(getSetting("UI_DEFAULT_PAGE_SIZE")),
searching: true,
ordering: true,
info: true,
autoWidth: false,
order: [[0, "desc"]],
pageLength: tableRows,
ajax: function (dtRequest, callback) {
const page = Math.floor(dtRequest.start / dtRequest.length) + 1;
const limit = dtRequest.length;
const search = dtRequest.search?.value || '';
const sortCol = dtRequest.order?.length ? dtRequest.order[0].column : 0;
const sortDir = dtRequest.order?.length ? dtRequest.order[0].dir : 'desc';
const url = `${apiBase}/sessions/session-events`
+ `?type=${encodeURIComponent(eventsType)}`
+ `&period=${encodeURIComponent(period)}`
+ `&page=${page}`
+ `&limit=${limit}`
+ `&sortCol=${sortCol}`
+ `&sortDir=${sortDir}`
+ (search ? `&search=${encodeURIComponent(search)}` : '');
$.ajax({
url,
method: "GET",
dataType: "json",
headers: { "Authorization": `Bearer ${apiToken}` },
success: function (response) {
callback({
data: response.data || [],
recordsTotal: response.total || 0,
recordsFiltered: response.recordsFiltered || 0
});
hideSpinner();
},
error: function (xhr, status, error) {
console.error("Error fetching session events:", status, error, xhr.responseText);
callback({ data: [], recordsTotal: 0, recordsFiltered: 0 });
hideSpinner();
}
});
},
columnDefs: [
{ targets: [0,5,6,7,8,10,11,12,13], visible: false },
{ targets: [7], orderData: [8] },
@@ -131,14 +174,14 @@ function initializeDatatable() {
{ targets: [3], createdCell: (td, cellData) => $(td).html(localizeTimestamp(cellData)) },
{ targets: [4,5,6,7], createdCell: (td, cellData) => $(td).html(translateHTMLcodes(cellData)) }
],
processing: true, // Shows "processing" overlay
language: {
processing: '<table><td width="130px" align="middle"><?= lang("Events_Loading"); ?></td><td><i class="fa-solid fa-spinner fa-spin-pulse"></i></td></table>',
emptyTable: 'No data',
lengthMenu: "<?= lang('Events_Tablelenght'); ?>",
search: "<?= lang('Events_Searchbox'); ?>: ",
paginate: { next: "<?= lang('Events_Table_nav_next'); ?>", previous: "<?= lang('Events_Table_nav_prev'); ?>" },
info: "<?= lang('Events_Table_info'); ?>"
search: "<?= lang('Events_Searchbox'); ?>: ",
paginate: { next: "<?= lang('Events_Table_nav_next'); ?>", previous: "<?= lang('Events_Table_nav_prev'); ?>" },
info: "<?= lang('Events_Table_info'); ?>"
}
});
@@ -179,53 +222,33 @@ function getEventsTotals() {
});
}
/* ---------------- Fetch events and reload DataTable ---------------- */
/* ---------------- Switch event type and reload DataTable ---------------- */
function getEvents(type) {
eventsType = type;
const table = $('#tableEvents').DataTable();
// Event type config: title, color, session columns visibility
const config = {
all: {title: 'Events_Shortcut_AllEvents', color: 'aqua', sesionCols: false},
sessions: {title: 'Events_Shortcut_Sessions', color: 'green', sesionCols: true},
missing: {title: 'Events_Shortcut_MissSessions', color: 'yellow', sesionCols: true},
voided: {title: 'Events_Shortcut_VoidSessions', color: 'yellow', sesionCols: false},
new: {title: 'Events_Shortcut_NewDevices', color: 'yellow', sesionCols: false},
down: {title: 'Events_Shortcut_DownAlerts', color: 'red', sesionCols: false}
all: {title: 'Events_Shortcut_AllEvents', color: 'aqua', sesionCols: false},
sessions: {title: 'Events_Shortcut_Sessions', color: 'green', sesionCols: true},
missing: {title: 'Events_Shortcut_MissSessions', color: 'yellow', sesionCols: true},
voided: {title: 'Events_Shortcut_VoidSessions', color: 'yellow', sesionCols: false},
new: {title: 'Events_Shortcut_NewDevices', color: 'yellow', sesionCols: false},
down: {title: 'Events_Shortcut_DownAlerts', color: 'red', sesionCols: false}
}[type] || {title: 'Events_Shortcut_Events', color: '', sesionCols: false};
// Update title and color
$('#tableEventsTitle').attr('class', 'box-title text-' + config.color).html(getString(config.title));
$('#tableEventsBox').attr('class', 'box box-' + config.color);
// Toggle columns visibility
// Toggle column visibility
table.column(3).visible(!config.sesionCols);
table.column(4).visible(!config.sesionCols);
table.column(5).visible(config.sesionCols);
table.column(6).visible(config.sesionCols);
table.column(7).visible(config.sesionCols);
// Build API URL
const apiBase = getApiBase();
const apiToken = getSetting("API_TOKEN");
const url = `${apiBase}/sessions/session-events?type=${encodeURIComponent(type)}&period=${encodeURIComponent(period)}`;
table.clear().draw(); // Clear old rows
showSpinner()
$.ajax({
url,
method: "GET",
dataType: "json",
headers: { "Authorization": `Bearer ${apiToken}` },
beforeSend: showSpinner, // Show spinner during fetch
complete: hideSpinner, // Hide spinner after fetch
success: response => {
const data = Array.isArray(response) ? response : response.data || [];
table.rows.add(data).draw();
},
error: (xhr, status, error) => console.error("Error fetching session events:", status, error, xhr.responseText)
});
showSpinner();
table.ajax.reload(null, true); // reset to page 1
}
</script>

View File

@@ -16,7 +16,7 @@
// -----------------------------------------------------------------------------
var completedCalls = []
var completedCalls_final = ['cacheApiConfig', 'cacheSettings', 'cacheStrings', 'cacheDevices'];
var completedCalls_final = ['cacheApiConfig', 'cacheSettings', 'cacheStrings_v2', 'cacheDevices'];
var lang_completedCalls = 0;

View File

@@ -290,7 +290,7 @@ function getSetting (key) {
// -----------------------------------------------------------------------------
function cacheStrings() {
return new Promise((resolve, reject) => {
if(getCache(CACHE_KEYS.initFlag('cacheStrings')) === "true")
if(getCache(CACHE_KEYS.initFlag('cacheStrings_v2')) === "true")
{
// Core strings are cached, but plugin strings may have failed silently on
// the first load (non-fatal fetch). Always re-fetch them so that plugin
@@ -304,7 +304,7 @@ function cacheStrings() {
.then((data) => {
if (!Array.isArray(data)) { data = []; }
data.forEach((langString) => {
setCache(CACHE_KEYS.langString(langString.String_Key, langString.Language_Code), langString.String_Value);
setCache(CACHE_KEYS.langString(langString.stringKey, langString.languageCode), langString.stringValue);
});
resolve();
});
@@ -347,11 +347,11 @@ function cacheStrings() {
if (!Array.isArray(data)) { data = []; }
// Store plugin translations
data.forEach((langString) => {
setCache(CACHE_KEYS.langString(langString.String_Key, langString.Language_Code), langString.String_Value);
setCache(CACHE_KEYS.langString(langString.stringKey, langString.languageCode), langString.stringValue);
});
// Handle successful completion of language processing
handleSuccess('cacheStrings');
handleSuccess('cacheStrings_v2');
resolveLang();
});
})
@@ -370,7 +370,7 @@ function cacheStrings() {
})
.catch((error) => {
// Handle failure in any of the language processing
handleFailure('cacheStrings');
handleFailure('cacheStrings_v2');
reject(error);
});

View File

@@ -2,6 +2,7 @@
//------------------------------------------------------------------------------
// check if authenticated
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/templates/security.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/server/db.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/templates/language/lang.php';
?>

View File

@@ -28,13 +28,13 @@ function initOnlineHistoryGraph() {
res.data.forEach(function(entry) {
var formattedTime = localizeTimestamp(entry.Scan_Date).slice(11, 17);
var formattedTime = localizeTimestamp(entry.scanDate).slice(11, 17);
timeStamps.push(formattedTime);
onlineCounts.push(entry.Online_Devices);
downCounts.push(entry.Down_Devices);
offlineCounts.push(entry.Offline_Devices);
archivedCounts.push(entry.Archived_Devices);
onlineCounts.push(entry.onlineDevices);
downCounts.push(entry.downDevices);
offlineCounts.push(entry.offlineDevices);
archivedCounts.push(entry.archivedDevices);
});
// Call your presenceOverTime function after data is ready

View File

@@ -24,7 +24,7 @@ $pia_lang_selected = isset($_langMatch[1]) ? strtolower($_langMatch[1]) : $defau
$result = $db->query("SELECT * FROM Plugins_Language_Strings");
$strings = array();
while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
$strings[$row['String_Key']] = $row['String_Value'];
$strings[$row['stringKey']] = $row['stringValue'];
}

View File

@@ -8,7 +8,7 @@
"mapped_to_table": "CurrentScan",
"data_filters": [
{
"compare_column": "Object_PrimaryID",
"compare_column": "objectPrimaryId",
"compare_operator": "==",
"compare_field_id": "txtMacFilter",
"compare_js_template": "'{value}'.toString()",
@@ -403,7 +403,7 @@
],
"database_column_definitions": [
{
"column": "Index",
"column": "index",
"css_classes": "col-sm-2",
"show": true,
"type": "none",
@@ -418,7 +418,7 @@
]
},
{
"column": "Object_PrimaryID",
"column": "objectPrimaryId",
"mapped_to_column": "scanMac",
"css_classes": "col-sm-3",
"show": true,
@@ -434,7 +434,7 @@
]
},
{
"column": "Object_SecondaryID",
"column": "objectSecondaryId",
"mapped_to_column": "scanLastIP",
"css_classes": "col-sm-2",
"show": true,
@@ -450,7 +450,7 @@
]
},
{
"column": "Watched_Value1",
"column": "watchedValue1",
"mapped_to_column": "scanName",
"css_classes": "col-sm-2",
"show": true,
@@ -466,7 +466,7 @@
]
},
{
"column": "Watched_Value2",
"column": "watchedValue2",
"mapped_to_column": "scanVendor",
"css_classes": "col-sm-2",
"show": true,
@@ -482,7 +482,7 @@
]
},
{
"column": "Watched_Value3",
"column": "watchedValue3",
"mapped_to_column": "scanType",
"css_classes": "col-sm-2",
"show": true,
@@ -498,7 +498,7 @@
]
},
{
"column": "Watched_Value4",
"column": "watchedValue4",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -532,7 +532,7 @@
]
},
{
"column": "DateTimeCreated",
"column": "dateTimeCreated",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -547,7 +547,7 @@
]
},
{
"column": "DateTimeChanged",
"column": "dateTimeChanged",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -562,7 +562,7 @@
]
},
{
"column": "Status",
"column": "status",
"css_classes": "col-sm-1",
"show": true,
"type": "replace",

View File

@@ -50,7 +50,7 @@ def main():
# make sure the below mapping is mapped in config.json, for example:
# "database_column_definitions": [
# {
# "column": "Object_PrimaryID", <--------- the value I save into primaryId
# "column": "objectPrimaryId", <--------- the value I save into primaryId
# "mapped_to_column": "scanMac", <--------- gets inserted into the CurrentScan DB
# table column scanMac
#

View File

@@ -31,7 +31,7 @@
"params": [],
"database_column_definitions": [
{
"column": "Index",
"column": "index",
"css_classes": "col-sm-2",
"show": true,
"type": "none",
@@ -46,7 +46,7 @@
]
},
{
"column": "Plugin",
"column": "plugin",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -65,7 +65,7 @@
]
},
{
"column": "Object_PrimaryID",
"column": "objectPrimaryId",
"css_classes": "col-sm-2",
"show": false,
"type": "url",
@@ -80,7 +80,7 @@
]
},
{
"column": "Object_SecondaryID",
"column": "objectSecondaryId",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -99,7 +99,7 @@
]
},
{
"column": "DateTimeCreated",
"column": "dateTimeCreated",
"css_classes": "col-sm-3",
"show": true,
"type": "label",
@@ -114,7 +114,7 @@
]
},
{
"column": "DateTimeChanged",
"column": "dateTimeChanged",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -133,7 +133,7 @@
]
},
{
"column": "Watched_Value1",
"column": "watchedValue1",
"css_classes": "col-sm-1",
"show": true,
"type": "eval",
@@ -153,7 +153,7 @@
]
},
{
"column": "Watched_Value2",
"column": "watchedValue2",
"css_classes": "col-sm-8",
"show": true,
"type": "textarea_readonly",
@@ -168,7 +168,7 @@
]
},
{
"column": "Watched_Value3",
"column": "watchedValue3",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -187,7 +187,7 @@
]
},
{
"column": "Watched_Value4",
"column": "watchedValue4",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -206,7 +206,7 @@
]
},
{
"column": "UserData",
"column": "userData",
"css_classes": "col-sm-2",
"show": false,
"type": "textbox_save",
@@ -225,7 +225,7 @@
]
},
{
"column": "Status",
"column": "status",
"css_classes": "col-sm-1",
"show": false,
"type": "replace",
@@ -261,7 +261,7 @@
]
},
{
"column": "Extra",
"column": "extra",
"css_classes": "col-sm-3",
"show": false,
"type": "label",

View File

@@ -31,7 +31,7 @@
"params": [],
"database_column_definitions": [
{
"column": "Index",
"column": "index",
"css_classes": "col-sm-2",
"show": true,
"type": "none",
@@ -46,7 +46,7 @@
]
},
{
"column": "Plugin",
"column": "plugin",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -65,7 +65,7 @@
]
},
{
"column": "Object_PrimaryID",
"column": "objectPrimaryId",
"css_classes": "col-sm-2",
"show": false,
"type": "url",
@@ -80,7 +80,7 @@
]
},
{
"column": "Object_SecondaryID",
"column": "objectSecondaryId",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -99,7 +99,7 @@
]
},
{
"column": "DateTimeCreated",
"column": "dateTimeCreated",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -114,7 +114,7 @@
]
},
{
"column": "DateTimeChanged",
"column": "dateTimeChanged",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -133,7 +133,7 @@
]
},
{
"column": "Watched_Value1",
"column": "watchedValue1",
"css_classes": "col-sm-1",
"show": true,
"type": "eval",
@@ -153,7 +153,7 @@
]
},
{
"column": "Watched_Value2",
"column": "watchedValue2",
"css_classes": "col-sm-8",
"show": true,
"type": "textarea_readonly",
@@ -168,7 +168,7 @@
]
},
{
"column": "Watched_Value3",
"column": "watchedValue3",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -187,7 +187,7 @@
]
},
{
"column": "Watched_Value4",
"column": "watchedValue4",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -206,7 +206,7 @@
]
},
{
"column": "UserData",
"column": "userData",
"css_classes": "col-sm-2",
"show": false,
"type": "textbox_save",
@@ -225,7 +225,7 @@
]
},
{
"column": "Status",
"column": "status",
"css_classes": "col-sm-1",
"show": false,
"type": "replace",
@@ -261,7 +261,7 @@
]
},
{
"column": "Extra",
"column": "extra",
"css_classes": "col-sm-3",
"show": false,
"type": "label",

View File

@@ -7,7 +7,7 @@
"show_ui": true,
"data_filters": [
{
"compare_column": "Watched_Value4",
"compare_column": "watchedValue4",
"compare_operator": "==",
"compare_field_id": "txtMacFilter",
"compare_js_template": "'{value}'.toString()",
@@ -47,7 +47,7 @@
],
"database_column_definitions": [
{
"column": "Index",
"column": "index",
"css_classes": "col-sm-2",
"show": true,
"type": "none",
@@ -62,7 +62,7 @@
]
},
{
"column": "Plugin",
"column": "plugin",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -81,7 +81,7 @@
]
},
{
"column": "Object_PrimaryID",
"column": "objectPrimaryId",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -96,7 +96,7 @@
]
},
{
"column": "Object_SecondaryID",
"column": "objectSecondaryId",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -111,7 +111,7 @@
]
},
{
"column": "DateTimeCreated",
"column": "dateTimeCreated",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -126,7 +126,7 @@
]
},
{
"column": "DateTimeChanged",
"column": "dateTimeChanged",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -145,7 +145,7 @@
]
},
{
"column": "Watched_Value1",
"column": "watchedValue1",
"css_classes": "col-sm-3",
"show": false,
"type": "label",
@@ -160,7 +160,7 @@
]
},
{
"column": "Watched_Value2",
"column": "watchedValue2",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -175,7 +175,7 @@
]
},
{
"column": "Watched_Value3",
"column": "watchedValue3",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -190,7 +190,7 @@
]
},
{
"column": "Watched_Value4",
"column": "watchedValue4",
"css_classes": "col-sm-2",
"show": true,
"type": "device_name_mac",
@@ -205,7 +205,7 @@
]
},
{
"column": "UserData",
"column": "userData",
"css_classes": "col-sm-2",
"show": false,
"type": "textbox_save",
@@ -224,7 +224,7 @@
]
},
{
"column": "Status",
"column": "status",
"css_classes": "col-sm-1",
"show": true,
"type": "replace",
@@ -260,7 +260,7 @@
]
},
{
"column": "Extra",
"column": "extra",
"css_classes": "col-sm-3",
"show": false,
"type": "label",

View File

@@ -212,14 +212,14 @@ class sensor_config:
already known. If not, it marks the sensor as new and logs relevant information.
"""
# Retrieve the plugin object based on the sensor's hash
plugObj = getPluginObject({"Plugin": "MQTT", "Watched_Value3": self.hash})
plugObj = getPluginObject({"plugin": "MQTT", "watchedValue3": self.hash})
# Check if the plugin object is new
if not plugObj:
self.isNew = True
mylog('verbose', [f"[{pluginName}] New sensor entry (name|mac|hash) : ({self.deviceName}|{self.mac}|{self.hash}"])
else:
device_name = plugObj.get("Watched_Value1", "Unknown")
device_name = plugObj.get("watchedValue1", "Unknown")
mylog('verbose', [f"[{pluginName}] Existing, skip Device Name: {device_name}"])
self.isNew = False

View File

@@ -31,7 +31,7 @@
"params": [],
"database_column_definitions": [
{
"column": "Index",
"column": "index",
"css_classes": "col-sm-2",
"show": true,
"type": "none",
@@ -46,7 +46,7 @@
]
},
{
"column": "Plugin",
"column": "plugin",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -65,7 +65,7 @@
]
},
{
"column": "Object_PrimaryID",
"column": "objectPrimaryId",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -80,7 +80,7 @@
]
},
{
"column": "Object_SecondaryID",
"column": "objectSecondaryId",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -95,7 +95,7 @@
]
},
{
"column": "Watched_Value1",
"column": "watchedValue1",
"css_classes": "col-sm-1",
"show": true,
"type": "eval",
@@ -115,7 +115,7 @@
]
},
{
"column": "Watched_Value2",
"column": "watchedValue2",
"css_classes": "col-sm-2",
"show": true,
"type": "textarea_readonly",
@@ -130,7 +130,7 @@
]
},
{
"column": "Watched_Value3",
"column": "watchedValue3",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -145,7 +145,7 @@
]
},
{
"column": "Watched_Value4",
"column": "watchedValue4",
"css_classes": "col-sm-2",
"show": false,
"type": "device_mac",
@@ -160,7 +160,7 @@
]
},
{
"column": "UserData",
"column": "userData",
"css_classes": "col-sm-2",
"show": false,
"type": "textbox_save",
@@ -179,7 +179,7 @@
]
},
{
"column": "Status",
"column": "status",
"css_classes": "col-sm-1",
"show": false,
"type": "replace",
@@ -215,7 +215,7 @@
]
},
{
"column": "Extra",
"column": "extra",
"css_classes": "col-sm-3",
"show": false,
"type": "label",

View File

@@ -31,7 +31,7 @@
"params": [],
"database_column_definitions": [
{
"column": "Index",
"column": "index",
"css_classes": "col-sm-2",
"show": true,
"type": "none",
@@ -46,7 +46,7 @@
]
},
{
"column": "Plugin",
"column": "plugin",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -65,7 +65,7 @@
]
},
{
"column": "Object_PrimaryID",
"column": "objectPrimaryId",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -80,7 +80,7 @@
]
},
{
"column": "Object_SecondaryID",
"column": "objectSecondaryId",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -95,7 +95,7 @@
]
},
{
"column": "Watched_Value1",
"column": "watchedValue1",
"css_classes": "col-sm-1",
"show": true,
"type": "eval",
@@ -115,7 +115,7 @@
]
},
{
"column": "Watched_Value2",
"column": "watchedValue2",
"css_classes": "col-sm-2",
"show": true,
"type": "textarea_readonly",
@@ -130,7 +130,7 @@
]
},
{
"column": "Watched_Value3",
"column": "watchedValue3",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -145,7 +145,7 @@
]
},
{
"column": "Watched_Value4",
"column": "watchedValue4",
"css_classes": "col-sm-2",
"show": false,
"type": "device_mac",
@@ -160,7 +160,7 @@
]
},
{
"column": "UserData",
"column": "userData",
"css_classes": "col-sm-2",
"show": false,
"type": "textbox_save",
@@ -179,7 +179,7 @@
]
},
{
"column": "Status",
"column": "status",
"css_classes": "col-sm-1",
"show": false,
"type": "replace",
@@ -215,7 +215,7 @@
]
},
{
"column": "Extra",
"column": "extra",
"css_classes": "col-sm-3",
"show": false,
"type": "label",

View File

@@ -31,7 +31,7 @@
"params": [],
"database_column_definitions": [
{
"column": "Index",
"column": "index",
"css_classes": "col-sm-2",
"show": true,
"type": "none",
@@ -46,7 +46,7 @@
]
},
{
"column": "Plugin",
"column": "plugin",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -65,7 +65,7 @@
]
},
{
"column": "Object_PrimaryID",
"column": "objectPrimaryId",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -80,7 +80,7 @@
]
},
{
"column": "Object_SecondaryID",
"column": "objectSecondaryId",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -95,7 +95,7 @@
]
},
{
"column": "Watched_Value1",
"column": "watchedValue1",
"css_classes": "col-sm-1",
"show": true,
"type": "eval",
@@ -115,7 +115,7 @@
]
},
{
"column": "Watched_Value2",
"column": "watchedValue2",
"css_classes": "col-sm-2",
"show": true,
"type": "textarea_readonly",
@@ -130,7 +130,7 @@
]
},
{
"column": "Watched_Value3",
"column": "watchedValue3",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -145,7 +145,7 @@
]
},
{
"column": "Watched_Value4",
"column": "watchedValue4",
"css_classes": "col-sm-2",
"show": false,
"type": "device_mac",
@@ -160,7 +160,7 @@
]
},
{
"column": "UserData",
"column": "userData",
"css_classes": "col-sm-2",
"show": false,
"type": "textbox_save",
@@ -179,7 +179,7 @@
]
},
{
"column": "Status",
"column": "status",
"css_classes": "col-sm-1",
"show": false,
"type": "replace",
@@ -215,7 +215,7 @@
]
},
{
"column": "Extra",
"column": "extra",
"css_classes": "col-sm-3",
"show": false,
"type": "label",

View File

@@ -27,7 +27,7 @@
"params": [],
"database_column_definitions": [
{
"column": "Index",
"column": "index",
"css_classes": "col-sm-2",
"show": true,
"type": "none",
@@ -42,7 +42,7 @@
]
},
{
"column": "Plugin",
"column": "plugin",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -61,7 +61,7 @@
]
},
{
"column": "Object_PrimaryID",
"column": "objectPrimaryId",
"css_classes": "col-sm-2",
"show": false,
"type": "url",
@@ -76,7 +76,7 @@
]
},
{
"column": "Object_SecondaryID",
"column": "objectSecondaryId",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -95,7 +95,7 @@
]
},
{
"column": "DateTimeCreated",
"column": "dateTimeCreated",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -110,7 +110,7 @@
]
},
{
"column": "DateTimeChanged",
"column": "dateTimeChanged",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -129,7 +129,7 @@
]
},
{
"column": "Watched_Value1",
"column": "watchedValue1",
"css_classes": "col-sm-1",
"show": true,
"type": "eval",
@@ -149,7 +149,7 @@
]
},
{
"column": "Watched_Value2",
"column": "watchedValue2",
"css_classes": "col-sm-8",
"show": true,
"type": "textarea_readonly",
@@ -164,7 +164,7 @@
]
},
{
"column": "Watched_Value3",
"column": "watchedValue3",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -183,7 +183,7 @@
]
},
{
"column": "Watched_Value4",
"column": "watchedValue4",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -202,7 +202,7 @@
]
},
{
"column": "UserData",
"column": "userData",
"css_classes": "col-sm-2",
"show": false,
"type": "textbox_save",
@@ -221,7 +221,7 @@
]
},
{
"column": "Status",
"column": "status",
"css_classes": "col-sm-1",
"show": false,
"type": "replace",
@@ -257,7 +257,7 @@
]
},
{
"column": "Extra",
"column": "extra",
"css_classes": "col-sm-3",
"show": false,
"type": "label",

View File

@@ -31,7 +31,7 @@
"params": [],
"database_column_definitions": [
{
"column": "Index",
"column": "index",
"css_classes": "col-sm-2",
"show": true,
"type": "none",
@@ -46,7 +46,7 @@
]
},
{
"column": "Plugin",
"column": "plugin",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -65,7 +65,7 @@
]
},
{
"column": "Object_PrimaryID",
"column": "objectPrimaryId",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -80,7 +80,7 @@
]
},
{
"column": "Object_SecondaryID",
"column": "objectSecondaryId",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -95,7 +95,7 @@
]
},
{
"column": "Watched_Value1",
"column": "watchedValue1",
"css_classes": "col-sm-1",
"show": true,
"type": "eval",
@@ -115,7 +115,7 @@
]
},
{
"column": "Watched_Value2",
"column": "watchedValue2",
"css_classes": "col-sm-3",
"show": true,
"type": "textarea_readonly",
@@ -130,7 +130,7 @@
]
},
{
"column": "Watched_Value3",
"column": "watchedValue3",
"css_classes": "col-sm-3",
"show": true,
"type": "textarea_readonly",
@@ -145,7 +145,7 @@
]
},
{
"column": "Watched_Value4",
"column": "watchedValue4",
"css_classes": "col-sm-2",
"show": false,
"type": "device_mac",
@@ -160,7 +160,7 @@
]
},
{
"column": "UserData",
"column": "userData",
"css_classes": "col-sm-2",
"show": false,
"type": "textbox_save",
@@ -179,7 +179,7 @@
]
},
{
"column": "Status",
"column": "status",
"css_classes": "col-sm-1",
"show": false,
"type": "replace",
@@ -215,7 +215,7 @@
]
},
{
"column": "Extra",
"column": "extra",
"css_classes": "col-sm-3",
"show": false,
"type": "label",

View File

@@ -8,7 +8,7 @@
"mapped_to_table": "CurrentScan",
"data_filters": [
{
"compare_column": "Object_PrimaryID",
"compare_column": "objectPrimaryId",
"compare_operator": "==",
"compare_field_id": "txtMacFilter",
"compare_js_template": "'{value}'.toString()",
@@ -378,7 +378,7 @@
],
"database_column_definitions": [
{
"column": "Index",
"column": "index",
"css_classes": "col-sm-2",
"show": true,
"type": "none",
@@ -393,7 +393,7 @@
]
},
{
"column": "Object_PrimaryID",
"column": "objectPrimaryId",
"mapped_to_column": "scanMac",
"css_classes": "col-sm-3",
"show": true,
@@ -409,7 +409,7 @@
]
},
{
"column": "Object_SecondaryID",
"column": "objectSecondaryId",
"mapped_to_column": "scanLastIP",
"css_classes": "col-sm-2",
"show": true,
@@ -425,7 +425,7 @@
]
},
{
"column": "Watched_Value1",
"column": "watchedValue1",
"mapped_to_column": "scanName",
"css_classes": "col-sm-2",
"show": true,
@@ -441,7 +441,7 @@
]
},
{
"column": "Watched_Value2",
"column": "watchedValue2",
"mapped_to_column": "scanType",
"css_classes": "col-sm-2",
"show": true,
@@ -457,7 +457,7 @@
]
},
{
"column": "Watched_Value3",
"column": "watchedValue3",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -472,7 +472,7 @@
]
},
{
"column": "Watched_Value4",
"column": "watchedValue4",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -506,7 +506,7 @@
]
},
{
"column": "DateTimeCreated",
"column": "dateTimeCreated",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -521,7 +521,7 @@
]
},
{
"column": "DateTimeChanged",
"column": "dateTimeChanged",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -536,7 +536,7 @@
]
},
{
"column": "Status",
"column": "status",
"css_classes": "col-sm-1",
"show": true,
"type": "replace",

View File

@@ -8,7 +8,7 @@
"mapped_to_table": "CurrentScan",
"data_filters": [
{
"compare_column": "Object_PrimaryID",
"compare_column": "objectPrimaryId",
"compare_operator": "==",
"compare_field_id": "txtMacFilter",
"compare_js_template": "'{value}'.toString()",
@@ -338,17 +338,17 @@
"elements": [
{
"elementType": "select",
"elementOptions": [{ "multiple": "true" }],
"elementOptions": [{ "multiple": "true", "ordeable": "true"}],
"transformers": []
}
]
},
"default_value": ["Watched_Value1", "Watched_Value2"],
"default_value": ["watchedValue1", "watchedValue2"],
"options": [
"Watched_Value1",
"Watched_Value2",
"Watched_Value3",
"Watched_Value4"
"watchedValue1",
"watchedValue2",
"watchedValue3",
"watchedValue4"
],
"localized": ["name", "description"],
"name": [
@@ -368,15 +368,15 @@
"description": [
{
"language_code": "en_us",
"string": "Send a notification if selected values change. Use <code>CTRL + Click</code> to select/deselect. <ul> <li><code>Watched_Value1</code> is IP</li><li><code>Watched_Value2</code> is Vendor</li><li><code>Watched_Value3</code> is Interface </li><li><code>Watched_Value4</code> is N/A </li></ul>"
"string": "Send a notification if selected values change. Use <code>CTRL + Click</code> to select/deselect. <ul> <li><code>watchedValue1</code> is IP</li><li><code>watchedValue2</code> is Vendor</li><li><code>watchedValue3</code> is Interface </li><li><code>watchedValue4</code> is N/A </li></ul>"
},
{
"language_code": "es_es",
"string": "Envía una notificación si los valores seleccionados cambian. Utilice <code>CTRL + clic</code> para seleccionar/deseleccionar. <ul> <li><code>Valor_observado1</code> es IP</li><li><code>Valor_observado2</code> es Proveedor</li><li><code>Valor_observado3</code> es Interfaz </li><li><code>Valor_observado4</code> es N/A </li></ul>"
"string": "Envía una notificación si los valores seleccionados cambian. Utilice <code>CTRL + clic</code> para seleccionar/deseleccionar. <ul> <li><code>watchedValue1</code> es IP</li><li><code>watchedValue2</code> es Proveedor</li><li><code>watchedValue3</code> es Interfaz </li><li><code>watchedValue4</code> es N/A </li></ul>"
},
{
"language_code": "de_de",
"string": "Sende eine Benachrichtigung, wenn ein ausgwählter Wert sich ändert. <code>STRG + klicken</code> zum aus-/abwählen. <ul> <li><code>Watched_Value1</code> ist die IP</li><li><code>Watched_Value2</code> ist der Hersteller</li><li><code>Watched_Value3</code> ist das Interface </li><li><code>Watched_Value4</code> ist nicht in Verwendung </li></ul>"
"string": "Sende eine Benachrichtigung, wenn ein ausgwählter Wert sich ändert. <code>STRG + klicken</code> zum aus-/abwählen. <ul> <li><code>watchedValue1</code> ist die IP</li><li><code>watchedValue2</code> ist der Hersteller</li><li><code>watchedValue3</code> ist das Interface </li><li><code>watchedValue4</code> ist nicht in Verwendung </li></ul>"
}
]
},
@@ -387,7 +387,7 @@
"elements": [
{
"elementType": "select",
"elementOptions": [{ "multiple": "true" }],
"elementOptions": [{ "multiple": "true", "ordeable": "true"}],
"transformers": []
}
]
@@ -484,7 +484,7 @@
],
"database_column_definitions": [
{
"column": "Index",
"column": "index",
"css_classes": "col-sm-2",
"show": true,
"type": "none",
@@ -499,7 +499,7 @@
]
},
{
"column": "Object_PrimaryID",
"column": "objectPrimaryId",
"mapped_to_column": "scanMac",
"css_classes": "col-sm-3",
"show": true,
@@ -515,7 +515,7 @@
]
},
{
"column": "Watched_Value1",
"column": "watchedValue1",
"mapped_to_column": "scanLastIP",
"css_classes": "col-sm-2",
"show": true,
@@ -531,7 +531,7 @@
]
},
{
"column": "Watched_Value2",
"column": "watchedValue2",
"mapped_to_column": "scanVendor",
"css_classes": "col-sm-2",
"show": true,
@@ -582,7 +582,7 @@
]
},
{
"column": "DateTimeCreated",
"column": "dateTimeCreated",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -605,7 +605,7 @@
]
},
{
"column": "DateTimeChanged",
"column": "dateTimeChanged",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -628,7 +628,7 @@
]
},
{
"column": "Status",
"column": "status",
"css_classes": "col-sm-1",
"show": true,
"type": "replace",

View File

@@ -9,7 +9,7 @@
"mapped_to_table": "CurrentScan",
"data_filters": [
{
"compare_column": "Object_PrimaryID",
"compare_column": "objectPrimaryId",
"compare_operator": "==",
"compare_field_id": "txtMacFilter",
"compare_js_template": "'{value}'.toString()",
@@ -431,7 +431,7 @@
],
"database_column_definitions": [
{
"column": "Index",
"column": "index",
"css_classes": "col-sm-2",
"show": true,
"type": "none",
@@ -448,7 +448,7 @@
]
},
{
"column": "Object_PrimaryID",
"column": "objectPrimaryId",
"css_classes": "col-sm-2",
"default_value": "",
"localized": [
@@ -474,7 +474,7 @@
"type": "device_mac"
},
{
"column": "Object_SecondaryID",
"column": "objectSecondaryId",
"css_classes": "col-sm-2",
"default_value": "",
"localized": [
@@ -500,7 +500,7 @@
"type": "device_ip"
},
{
"column": "Watched_Value1",
"column": "watchedValue1",
"css_classes": "col-sm-2",
"default_value": "",
"localized": [
@@ -526,7 +526,7 @@
"type": "label"
},
{
"column": "Watched_Value2",
"column": "watchedValue2",
"mapped_to_column": "scanVendor",
"css_classes": "col-sm-2",
"default_value": "",
@@ -573,7 +573,7 @@
]
},
{
"column": "DateTimeCreated",
"column": "dateTimeCreated",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -590,7 +590,7 @@
]
},
{
"column": "DateTimeChanged",
"column": "dateTimeChanged",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -607,7 +607,7 @@
]
},
{
"column": "Status",
"column": "status",
"css_classes": "col-sm-1",
"show": true,
"type": "replace",

View File

@@ -8,7 +8,7 @@
"show_ui": true,
"data_filters": [
{
"compare_column": "Object_PrimaryID",
"compare_column": "objectPrimaryId",
"compare_operator": "==",
"compare_field_id": "txtMacFilter",
"compare_js_template": "'{value}'.toString()",
@@ -291,7 +291,7 @@
],
"database_column_definitions": [
{
"column": "Index",
"column": "index",
"css_classes": "col-sm-2",
"show": true,
"type": "none",
@@ -306,7 +306,7 @@
]
},
{
"column": "Object_PrimaryID",
"column": "objectPrimaryId",
"css_classes": "col-sm-2",
"show": true,
"type": "device_name_mac",
@@ -325,7 +325,7 @@
]
},
{
"column": "Object_SecondaryID",
"column": "objectSecondaryId",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -344,7 +344,7 @@
]
},
{
"column": "Watched_Value1",
"column": "watchedValue1",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -359,7 +359,7 @@
]
},
{
"column": "Watched_Value2",
"column": "watchedValue2",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -374,7 +374,7 @@
]
},
{
"column": "DateTimeCreated",
"column": "dateTimeCreated",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -389,7 +389,7 @@
]
},
{
"column": "DateTimeChanged",
"column": "dateTimeChanged",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -404,7 +404,7 @@
]
},
{
"column": "Status",
"column": "status",
"css_classes": "col-sm-1",
"show": true,
"type": "replace",

View File

@@ -82,16 +82,16 @@ def cleanup_database(
# Cleanup Online History
mylog("verbose", [f"[{pluginName}] Online_History: Delete all but keep latest 150 entries"])
cursor.execute(
"""DELETE from Online_History where "Index" not in (
SELECT "Index" from Online_History
order by Scan_Date desc limit 150)"""
"""DELETE from Online_History where "index" not in (
SELECT "index" from Online_History
order by scanDate desc limit 150)"""
)
mylog("verbose", [f"[{pluginName}] Online_History deleted rows: {cursor.rowcount}"])
# -----------------------------------------------------
# Cleanup Events
mylog("verbose", f"[{pluginName}] Events: Delete all older than {str(DAYS_TO_KEEP_EVENTS)} days (DAYS_TO_KEEP_EVENTS setting)")
sql = f"""DELETE FROM Events WHERE eve_DateTime <= date('now', '-{str(DAYS_TO_KEEP_EVENTS)} day')"""
sql = f"""DELETE FROM Events WHERE eveDateTime <= date('now', '-{str(DAYS_TO_KEEP_EVENTS)} day')"""
mylog("verbose", [f"[{pluginName}] SQL : {sql}"])
cursor.execute(sql)
mylog("verbose", [f"[{pluginName}] Events deleted rows: {cursor.rowcount}"])
@@ -100,7 +100,7 @@ def cleanup_database(
# Sessions (derived snapshot — trimmed to the same window as Events so the
# two tables stay in sync without introducing a separate setting)
mylog("verbose", f"[{pluginName}] Sessions: Delete all older than {str(DAYS_TO_KEEP_EVENTS)} days (reuses DAYS_TO_KEEP_EVENTS)")
sql = f"""DELETE FROM Sessions WHERE ses_DateTimeConnection <= date('now', '-{str(DAYS_TO_KEEP_EVENTS)} day')"""
sql = f"""DELETE FROM Sessions WHERE sesDateTimeConnection <= date('now', '-{str(DAYS_TO_KEEP_EVENTS)} day')"""
mylog("verbose", [f"[{pluginName}] SQL : {sql}"])
cursor.execute(sql)
mylog("verbose", [f"[{pluginName}] Sessions deleted rows: {cursor.rowcount}"])
@@ -113,7 +113,7 @@ def cleanup_database(
SELECT "Index"
FROM (
SELECT "Index",
ROW_NUMBER() OVER(PARTITION BY "Plugin" ORDER BY DateTimeChanged DESC) AS row_num
ROW_NUMBER() OVER(PARTITION BY plugin ORDER BY dateTimeChanged DESC) AS row_num
FROM Plugins_History
) AS ranked_objects
WHERE row_num <= {str(PLUGINS_KEEP_HIST)}
@@ -130,7 +130,7 @@ def cleanup_database(
SELECT "Index"
FROM (
SELECT "Index",
ROW_NUMBER() OVER(PARTITION BY "Notifications" ORDER BY DateTimeCreated DESC) AS row_num
ROW_NUMBER() OVER(PARTITION BY "index" ORDER BY dateTimeCreated DESC) AS row_num
FROM Notifications
) AS ranked_objects
WHERE row_num <= {histCount}
@@ -147,7 +147,7 @@ def cleanup_database(
SELECT "Index"
FROM (
SELECT "Index",
ROW_NUMBER() OVER(PARTITION BY "AppEvents" ORDER BY DateTimeCreated DESC) AS row_num
ROW_NUMBER() OVER(PARTITION BY "index" ORDER BY dateTimeCreated DESC) AS row_num
FROM AppEvents
) AS ranked_objects
WHERE row_num <= {histCount}
@@ -192,10 +192,10 @@ def cleanup_database(
DELETE FROM Plugins_Objects
WHERE rowid > (
SELECT MIN(rowid) FROM Plugins_Objects p2
WHERE Plugins_Objects.Plugin = p2.Plugin
AND Plugins_Objects.Object_PrimaryID = p2.Object_PrimaryID
AND Plugins_Objects.Object_SecondaryID = p2.Object_SecondaryID
AND Plugins_Objects.UserData = p2.UserData
WHERE Plugins_Objects.plugin = p2.plugin
AND Plugins_Objects.objectPrimaryId = p2.objectPrimaryId
AND Plugins_Objects.objectSecondaryId = p2.objectSecondaryId
AND Plugins_Objects.userData = p2.userData
)
"""
)

View File

@@ -5,7 +5,7 @@
"enabled": true,
"data_filters": [
{
"compare_column": "Object_PrimaryID",
"compare_column": "objectPrimaryId",
"compare_operator": "==",
"compare_field_id": "txtMacFilter",
"compare_js_template": "'{value}'.toString()",
@@ -432,17 +432,17 @@
"elements": [
{
"elementType": "select",
"elementOptions": [{ "multiple": "true" }],
"elementOptions": [{ "multiple": "true", "ordeable": "true"}],
"transformers": []
}
]
},
"default_value": ["Watched_Value1"],
"default_value": ["watchedValue1"],
"options": [
"Watched_Value1",
"Watched_Value2",
"Watched_Value3",
"Watched_Value4"
"watchedValue1",
"watchedValue2",
"watchedValue3",
"watchedValue4"
],
"localized": ["name", "description"],
"name": [
@@ -462,11 +462,11 @@
"description": [
{
"language_code": "en_us",
"string": "Send a notification if selected values change. Use <code>CTRL + Click</code> to select/deselect. <ul> <li><code>Watched_Value1</code> is Previous IP (not recommended)</li><li><code>Watched_Value2</code> unused</li><li><code>Watched_Value3</code> unused </li><li><code>Watched_Value4</code> unused </li></ul>"
"string": "Send a notification if selected values change. Use <code>CTRL + Click</code> to select/deselect. <ul> <li><code>watchedValue1</code> is Previous IP (not recommended)</li><li><code>watchedValue2</code> unused</li><li><code>watchedValue3</code> unused </li><li><code>watchedValue4</code> unused </li></ul>"
},
{
"language_code": "de_de",
"string": "Sende eine Benachrichtigung, wenn ein ausgwählter Wert sich ändert. <code>STRG + klicken</code> zum aus-/abwählen. <ul> <li><code>Watched_Value1</code> ist die Vorige IP (nicht empfohlen)</li><li><code>Watched_Value2</code> ist nicht in Verwendung</li><li><code>Watched_Value3</code> ist nicht in Verwendung </li><li><code>Watched_Value4</code> ist nicht in Verwendung </li></ul>"
"string": "Sende eine Benachrichtigung, wenn ein ausgwählter Wert sich ändert. <code>STRG + klicken</code> zum aus-/abwählen. <ul> <li><code>watchedValue1</code> ist die Vorige IP (nicht empfohlen)</li><li><code>watchedValue2</code> ist nicht in Verwendung</li><li><code>watchedValue3</code> ist nicht in Verwendung </li><li><code>watchedValue4</code> ist nicht in Verwendung </li></ul>"
}
]
},
@@ -477,7 +477,7 @@
"elements": [
{
"elementType": "select",
"elementOptions": [{ "multiple": "true" }],
"elementOptions": [{ "multiple": "true", "ordeable": "true"}],
"transformers": []
}
]
@@ -507,22 +507,22 @@
"description": [
{
"language_code": "en_us",
"string": "Send a notification only on these statuses. <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."
"string": "Send a notification only on these statuses. <code>new</code> means a new unique (unique combination of PrimaryId and SecondaryId) object was discovered. <code>watched-changed</code> means that selected <code>watchedValueN</code> columns changed."
},
{
"language_code": "es_es",
"string": "Envíe una notificación solo en estos estados. <code>new</code> significa que se descubrió un nuevo objeto único (una combinación única de PrimaryId y SecondaryId). <code>watched-changed</code> significa que las columnas <code>Watched_ValueN</code> seleccionadas cambiaron."
"string": "Envíe una notificación solo en estos estados. <code>new</code> significa que se descubrió un nuevo objeto único (una combinación única de PrimaryId y SecondaryId). <code>watched-changed</code> significa que las columnas <code>watchedValueN</code> seleccionadas cambiaron."
},
{
"language_code": "de_de",
"string": "Benachrichtige nur bei diesen Status. <code>new</code> bedeutet ein neues eindeutiges (einzigartige Kombination aus PrimaryId und SecondaryId) Objekt wurde erkennt. <code>watched-changed</code> bedeutet eine ausgewählte <code>Watched_ValueN</code>-Spalte hat sich geändert."
"string": "Benachrichtige nur bei diesen Status. <code>new</code> bedeutet ein neues eindeutiges (einzigartige Kombination aus PrimaryId und SecondaryId) Objekt wurde erkennt. <code>watched-changed</code> bedeutet eine ausgewählte <code>watchedValueN</code>-Spalte hat sich geändert."
}
]
}
],
"database_column_definitions": [
{
"column": "Index",
"column": "index",
"css_classes": "col-sm-2",
"show": true,
"type": "none",
@@ -537,7 +537,7 @@
]
},
{
"column": "Object_PrimaryID",
"column": "objectPrimaryId",
"css_classes": "col-sm-2",
"show": true,
"type": "device_name_mac",
@@ -560,7 +560,7 @@
]
},
{
"column": "Object_SecondaryID",
"column": "objectSecondaryId",
"css_classes": "col-sm-2",
"show": true,
"type": "device_ip",
@@ -583,7 +583,7 @@
]
},
{
"column": "Watched_Value1",
"column": "watchedValue1",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -628,7 +628,7 @@
]
},
{
"column": "DateTimeCreated",
"column": "dateTimeCreated",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -651,7 +651,7 @@
]
},
{
"column": "DateTimeChanged",
"column": "dateTimeChanged",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -674,7 +674,7 @@
]
},
{
"column": "Status",
"column": "status",
"css_classes": "col-sm-1",
"show": true,
"type": "replace",

View File

@@ -78,7 +78,7 @@ volumes:
10. Load the `DHCPLSS` plugin and add the search path: `/etc/dnsmasq/dnsmasq.leases`
Configure the plugin, and save everything. You can trigger a manual run.
Configure the plugin, and save everything. You can trigger a manual run.
> [!NOTE]
> DHCP leases don't allow for realtime tracking and the freshness of the data depends on the DHCP leasing time (usually set to 1 or 24h, or 3600 to 86400 seconds).
@@ -93,8 +93,8 @@ DHCPLSS_CMD: 'python3 /app/front/plugins/dhcp_leases/script.py paths={paths}'
DHCPLSS_paths_to_check: ['/etc/dnsmasq/dnsmasq.leases']
DHCPLSS_RUN_SCHD: '*/5 * * * *'
DHCPLSS_TUN_TIMEOUT: 5
DHCPLSS_WATCH: ['Watched_Value1', 'Watched_Value4']
DHCPLSS_REPORT_ON: ['new', 'watched_changed']
DHCPLSS_WATCH: ['watchedValue1', 'watchedValue4']
DHCPLSS_REPORT_ON: ['new', 'watched-changed']
```
You can check the the `dnsmasq.leases` file in the container by running `ls /etc/dnsmasq/`:

View File

@@ -7,7 +7,7 @@
"data_source": "script",
"data_filters": [
{
"compare_column": "Object_PrimaryID",
"compare_column": "objectPrimaryId",
"compare_operator": "==",
"compare_field_id": "txtMacFilter",
"compare_js_template": "'{value}'.toString()",
@@ -64,7 +64,7 @@
],
"database_column_definitions": [
{
"column": "Index",
"column": "index",
"css_classes": "col-sm-2",
"show": true,
"type": "none",
@@ -79,7 +79,7 @@
]
},
{
"column": "Plugin",
"column": "plugin",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -102,7 +102,7 @@
]
},
{
"column": "Object_PrimaryID",
"column": "objectPrimaryId",
"mapped_to_column": "scanMac",
"css_classes": "col-sm-2",
"show": true,
@@ -126,7 +126,7 @@
]
},
{
"column": "Object_SecondaryID",
"column": "objectSecondaryId",
"mapped_to_column": "scanLastIP",
"css_classes": "col-sm-2",
"show": true,
@@ -150,7 +150,7 @@
]
},
{
"column": "DateTimeCreated",
"column": "dateTimeCreated",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -173,7 +173,7 @@
]
},
{
"column": "DateTimeChanged",
"column": "dateTimeChanged",
"mapped_to_column": "scanLastConnection",
"css_classes": "col-sm-2",
"show": true,
@@ -197,7 +197,7 @@
]
},
{
"column": "Watched_Value1",
"column": "watchedValue1",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -220,7 +220,7 @@
]
},
{
"column": "Watched_Value2",
"column": "watchedValue2",
"mapped_to_column": "scanName",
"css_classes": "col-sm-2",
"show": true,
@@ -244,7 +244,7 @@
]
},
{
"column": "Watched_Value3",
"column": "watchedValue3",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -267,7 +267,7 @@
]
},
{
"column": "Watched_Value4",
"column": "watchedValue4",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -290,7 +290,7 @@
]
},
{
"column": "UserData",
"column": "userData",
"css_classes": "col-sm-2",
"show": false,
"type": "textbox_save",
@@ -313,7 +313,7 @@
]
},
{
"column": "Extra",
"column": "extra",
"css_classes": "col-sm-3",
"show": true,
"type": "label",
@@ -363,7 +363,7 @@
]
},
{
"column": "Status",
"column": "status",
"css_classes": "col-sm-1",
"show": true,
"type": "replace",
@@ -753,17 +753,17 @@
"elements": [
{
"elementType": "select",
"elementOptions": [{ "multiple": "true" }],
"elementOptions": [{ "multiple": "true", "ordeable": "true"}],
"transformers": []
}
]
},
"default_value": ["Watched_Value1", "Watched_Value4"],
"default_value": ["watchedValue1", "watchedValue4"],
"options": [
"Watched_Value1",
"Watched_Value2",
"Watched_Value3",
"Watched_Value4"
"watchedValue1",
"watchedValue2",
"watchedValue3",
"watchedValue4"
],
"localized": ["name", "description"],
"name": [
@@ -783,15 +783,15 @@
"description": [
{
"language_code": "en_us",
"string": "Send a notification if selected values change. Use <code>CTRL + Click</code> to select/deselect. <ul> <li><code>Watched_Value1</code> is Active </li><li><code>Watched_Value2</code> is Hostname </li><li><code>Watched_Value3</code> is hardware </li><li><code>Watched_Value4</code> is State </li></ul>"
"string": "Send a notification if selected values change. Use <code>CTRL + Click</code> to select/deselect. <ul> <li><code>watchedValue1</code> is Active </li><li><code>watchedValue2</code> is Hostname </li><li><code>watchedValue3</code> is hardware </li><li><code>watchedValue4</code> is State </li></ul>"
},
{
"language_code": "es_es",
"string": "Enviar una notificación si los valores seleccionados cambian. Utilice <code>CTRL + clic</code> para seleccionar/deseleccionar. <ul> <li><code>Watched_Value1</code> está activo </li><li><code>Watched_Value2</code> es el nombre de host </li><li><code>Watched_Value3</code > es hardware </li><li><code>Watched_Value4</code> es Estado </li></ul>"
"string": "Enviar una notificación si los valores seleccionados cambian. Utilice <code>CTRL + clic</code> para seleccionar/deseleccionar. <ul> <li><code>watchedValue1</code> está activo </li><li><code>watchedValue2</code> es el nombre de host </li><li><code>watchedValue3</code > es hardware </li><li><code>watchedValue4</code> es Estado </li></ul>"
},
{
"language_code": "de_de",
"string": "Sende eine Benachrichtigung, wenn ein ausgwählter Wert sich ändert. <code>STRG + klicken</code> zum aus-/abwählen. <ul> <li><code>Watched_Value1</code> ist der Aktivstatus</li><li><code>Watched_Value2</code> ist der Hostname</li><li><code>Watched_Value3</code> ist die Hardware</li><li><code>Watched_Value4</code> ist der Zustand </li></ul>"
"string": "Sende eine Benachrichtigung, wenn ein ausgwählter Wert sich ändert. <code>STRG + klicken</code> zum aus-/abwählen. <ul> <li><code>watchedValue1</code> ist der Aktivstatus</li><li><code>watchedValue2</code> ist der Hostname</li><li><code>watchedValue3</code> ist die Hardware</li><li><code>watchedValue4</code> ist der Zustand </li></ul>"
}
]
},
@@ -802,7 +802,7 @@
"elements": [
{
"elementType": "select",
"elementOptions": [{ "multiple": "true" }],
"elementOptions": [{ "multiple": "true", "ordeable": "true"}],
"transformers": []
}
]
@@ -832,15 +832,15 @@
"description": [
{
"language_code": "en_us",
"string": "Send a notification only on these statuses. <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."
"string": "Send a notification only on these statuses. <code>new</code> means a new unique (unique combination of PrimaryId and SecondaryId) object was discovered. <code>watched-changed</code> means that selected <code>watchedValueN</code> columns changed."
},
{
"language_code": "es_es",
"string": "Envíe una notificación solo en estos estados. <code>new</code> significa que se descubrió un nuevo objeto único (una combinación única de PrimaryId y SecondaryId). <code>watched-changed</code> significa que las columnas <code>Watched_ValueN</code> seleccionadas cambiaron."
"string": "Envíe una notificación solo en estos estados. <code>new</code> significa que se descubrió un nuevo objeto único (una combinación única de PrimaryId y SecondaryId). <code>watched-changed</code> significa que las columnas <code>watchedValueN</code> seleccionadas cambiaron."
},
{
"language_code": "de_de",
"string": "Benachrichtige nur bei diesen Status. <code>new</code> bedeutet ein neues eindeutiges (einzigartige Kombination aus PrimaryId und SecondaryId) Objekt wurde erkennt. <code>watched-changed</code> bedeutet eine ausgewählte <code>Watched_ValueN</code>-Spalte hat sich geändert."
"string": "Benachrichtige nur bei diesen Status. <code>new</code> bedeutet ein neues eindeutiges (einzigartige Kombination aus PrimaryId und SecondaryId) Objekt wurde erkennt. <code>watched-changed</code> bedeutet eine ausgewählte <code>watchedValueN</code>-Spalte hat sich geändert."
}
]
}

View File

@@ -40,7 +40,7 @@
"params": [],
"database_column_definitions": [
{
"column": "Index",
"column": "index",
"css_classes": "col-sm-2",
"show": true,
"type": "none",
@@ -55,7 +55,7 @@
]
},
{
"column": "Plugin",
"column": "plugin",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -74,7 +74,7 @@
]
},
{
"column": "Object_PrimaryID",
"column": "objectPrimaryId",
"css_classes": "col-sm-2",
"show": true,
"type": "device_ip",
@@ -93,7 +93,7 @@
]
},
{
"column": "Object_SecondaryID",
"column": "objectSecondaryId",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -112,7 +112,7 @@
]
},
{
"column": "DateTimeCreated",
"column": "dateTimeCreated",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -131,7 +131,7 @@
]
},
{
"column": "DateTimeChanged",
"column": "dateTimeChanged",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -150,7 +150,7 @@
]
},
{
"column": "Watched_Value1",
"column": "watchedValue1",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -169,7 +169,7 @@
]
},
{
"column": "Watched_Value2",
"column": "watchedValue2",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -188,7 +188,7 @@
]
},
{
"column": "Watched_Value3",
"column": "watchedValue3",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -207,7 +207,7 @@
]
},
{
"column": "Watched_Value4",
"column": "watchedValue4",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -226,7 +226,7 @@
]
},
{
"column": "UserData",
"column": "userData",
"css_classes": "col-sm-2",
"show": true,
"type": "textbox_save",
@@ -245,7 +245,7 @@
]
},
{
"column": "Status",
"column": "status",
"css_classes": "col-sm-1",
"show": true,
"type": "replace",
@@ -281,7 +281,7 @@
]
},
{
"column": "Extra",
"column": "extra",
"css_classes": "col-sm-3",
"show": true,
"type": "label",
@@ -478,17 +478,17 @@
"elements": [
{
"elementType": "select",
"elementOptions": [{ "multiple": "true" }],
"elementOptions": [{ "multiple": "true", "ordeable": "true"}],
"transformers": []
}
]
},
"default_value": ["Watched_Value1"],
"default_value": ["watchedValue1"],
"options": [
"Watched_Value1",
"Watched_Value2",
"Watched_Value3",
"Watched_Value4"
"watchedValue1",
"watchedValue2",
"watchedValue3",
"watchedValue4"
],
"localized": ["name", "description"],
"name": [
@@ -504,11 +504,11 @@
"description": [
{
"language_code": "en_us",
"string": "Send a notification if selected values change. Use <code>CTRL + Click</code> to select/deselect. <ul> <li><code>Watched_Value1</code> is Domain Name Server</li><li><code>Watched_Value2</code> is IP Offered</li><li><code>Watched_Value3</code> is Interface </li><li><code>Watched_Value4</code> is Router </li></ul>"
"string": "Send a notification if selected values change. Use <code>CTRL + Click</code> to select/deselect. <ul> <li><code>watchedValue1</code> is Domain Name Server</li><li><code>watchedValue2</code> is IP Offered</li><li><code>watchedValue3</code> is Interface </li><li><code>watchedValue4</code> is Router </li></ul>"
},
{
"language_code": "es_es",
"string": "Envíe una notificación si los valores seleccionados cambian. Utilice <code>CTRL + clic</code> para seleccionar/deseleccionar. <ul> <li><code>Watched_Value1</code> es servidor de nombres de dominio</li><li><code>Watched_Value2</code> es IP ofrecida</li><li><code>Watched_Value3</code> es Interfaz </li><li><code>Watched_Value4</code> es enrutador </li></ul>"
"string": "Envíe una notificación si los valores seleccionados cambian. Utilice <code>CTRL + clic</code> para seleccionar/deseleccionar. <ul> <li><code>watchedValue1</code> es servidor de nombres de dominio</li><li><code>watchedValue2</code> es IP ofrecida</li><li><code>watchedValue3</code> es Interfaz </li><li><code>watchedValue4</code> es enrutador </li></ul>"
}
]
},
@@ -519,7 +519,7 @@
"elements": [
{
"elementType": "select",
"elementOptions": [{ "multiple": "true" }],
"elementOptions": [{ "multiple": "true", "ordeable": "true"}],
"transformers": []
}
]
@@ -540,11 +540,11 @@
"description": [
{
"language_code": "en_us",
"string": "Send a notification only on these statuses. <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."
"string": "Send a notification only on these statuses. <code>new</code> means a new unique (unique combination of PrimaryId and SecondaryId) object was discovered. <code>watched-changed</code> means that selected <code>watchedValueN</code> columns changed."
},
{
"language_code": "es_es",
"string": "Envíe una notificación solo en estos estados. <code>new</code> significa que se descubrió un nuevo objeto único (una combinación única de PrimaryId y SecondaryId). <code>watched-changed</code> significa que las columnas <code>Watched_ValueN</code> seleccionadas cambiaron."
"string": "Envíe una notificación solo en estos estados. <code>new</code> significa que se descubrió un nuevo objeto único (una combinación única de PrimaryId y SecondaryId). <code>watched-changed</code> significa que las columnas <code>watchedValueN</code> seleccionadas cambiaron."
}
]
}

View File

@@ -8,7 +8,7 @@
"show_ui": true,
"data_filters": [
{
"compare_column": "Object_PrimaryID",
"compare_column": "objectPrimaryId",
"compare_operator": "==",
"compare_field_id": "txtMacFilter",
"compare_js_template": "'{value}'.toString()",
@@ -299,7 +299,7 @@
],
"database_column_definitions": [
{
"column": "Index",
"column": "index",
"css_classes": "col-sm-2",
"show": true,
"type": "none",
@@ -314,7 +314,7 @@
]
},
{
"column": "Object_PrimaryID",
"column": "objectPrimaryId",
"css_classes": "col-sm-2",
"show": true,
"type": "device_name_mac",
@@ -333,7 +333,7 @@
]
},
{
"column": "Object_SecondaryID",
"column": "objectSecondaryId",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -352,7 +352,7 @@
]
},
{
"column": "Watched_Value1",
"column": "watchedValue1",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -367,7 +367,7 @@
]
},
{
"column": "Watched_Value2",
"column": "watchedValue2",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -382,7 +382,7 @@
]
},
{
"column": "DateTimeCreated",
"column": "dateTimeCreated",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -397,7 +397,7 @@
]
},
{
"column": "DateTimeChanged",
"column": "dateTimeChanged",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -412,7 +412,7 @@
]
},
{
"column": "Status",
"column": "status",
"css_classes": "col-sm-1",
"show": true,
"type": "replace",

View File

@@ -8,7 +8,7 @@
"mapped_to_table": "CurrentScan",
"data_filters": [
{
"compare_column": "Object_PrimaryID",
"compare_column": "objectPrimaryId",
"compare_operator": "==",
"compare_field_id": "txtMacFilter",
"compare_js_template": "'{value}'.toString()",
@@ -376,7 +376,7 @@
],
"database_column_definitions": [
{
"column": "Index",
"column": "index",
"css_classes": "col-sm-2",
"show": true,
"type": "none",
@@ -393,7 +393,7 @@
]
},
{
"column": "Object_PrimaryID",
"column": "objectPrimaryId",
"mapped_to_column": "scanMac",
"css_classes": "col-sm-3",
"show": true,
@@ -411,7 +411,7 @@
]
},
{
"column": "Object_SecondaryID",
"column": "objectSecondaryId",
"mapped_to_column": "scanLastIP",
"css_classes": "col-sm-2",
"show": true,
@@ -429,7 +429,7 @@
]
},
{
"column": "Watched_Value1",
"column": "watchedValue1",
"mapped_to_column": "scanName",
"css_classes": "col-sm-2",
"show": true,
@@ -447,7 +447,7 @@
]
},
{
"column": "Watched_Value2",
"column": "watchedValue2",
"mapped_to_column": "scanVendor",
"css_classes": "col-sm-2",
"show": true,
@@ -465,7 +465,7 @@
]
},
{
"column": "Watched_Value3",
"column": "watchedValue3",
"mapped_to_column": "scanType",
"css_classes": "col-sm-2",
"show": true,
@@ -483,7 +483,7 @@
]
},
{
"column": "Watched_Value4",
"column": "watchedValue4",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -521,7 +521,7 @@
]
},
{
"column": "DateTimeCreated",
"column": "dateTimeCreated",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -538,7 +538,7 @@
]
},
{
"column": "DateTimeChanged",
"column": "dateTimeChanged",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -555,7 +555,7 @@
]
},
{
"column": "Status",
"column": "status",
"css_classes": "col-sm-1",
"show": true,
"type": "replace",

View File

@@ -9,7 +9,7 @@
"show_ui": true,
"data_filters": [
{
"compare_column": "Object_PrimaryID",
"compare_column": "objectPrimaryId",
"compare_operator": "==",
"compare_field_id": "txtMacFilter",
"compare_js_template": "'{value}'.toString()",
@@ -364,7 +364,7 @@
],
"database_column_definitions": [
{
"column": "Index",
"column": "index",
"css_classes": "col-sm-2",
"show": true,
"type": "none",
@@ -381,7 +381,7 @@
]
},
{
"column": "Object_PrimaryID",
"column": "objectPrimaryId",
"mapped_to_column": "scanMac",
"css_classes": "col-sm-3",
"show": true,
@@ -399,7 +399,7 @@
]
},
{
"column": "Object_SecondaryID",
"column": "objectSecondaryId",
"mapped_to_column": "scanLastIP",
"css_classes": "col-sm-2",
"show": true,
@@ -417,7 +417,7 @@
]
},
{
"column": "Watched_Value1",
"column": "watchedValue1",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -434,7 +434,7 @@
]
},
{
"column": "Watched_Value2",
"column": "watchedValue2",
"css_classes": "col-sm-2",
"show": true,
"type": "textarea_readonly",
@@ -451,7 +451,7 @@
]
},
{
"column": "Watched_Value3",
"column": "watchedValue3",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -468,7 +468,7 @@
]
},
{
"column": "Watched_Value4",
"column": "watchedValue4",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -506,7 +506,7 @@
]
},
{
"column": "DateTimeCreated",
"column": "dateTimeCreated",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -523,7 +523,7 @@
]
},
{
"column": "DateTimeChanged",
"column": "dateTimeChanged",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -540,7 +540,7 @@
]
},
{
"column": "Status",
"column": "status",
"css_classes": "col-sm-1",
"show": true,
"type": "replace",

View File

@@ -7,7 +7,7 @@
"mapped_to_table": "CurrentScan",
"data_filters": [
{
"compare_column": "Object_PrimaryID",
"compare_column": "objectPrimaryId",
"compare_operator": "==",
"compare_field_id": "txtMacFilter",
"compare_js_template": "'{value}'.toString()",
@@ -324,17 +324,17 @@
"elements": [
{
"elementType": "select",
"elementOptions": [{ "multiple": "true" }],
"elementOptions": [{ "multiple": "true", "ordeable": "true"}],
"transformers": []
}
]
},
"default_value": ["Watched_Value1"],
"default_value": ["watchedValue1"],
"options": [
"Watched_Value1",
"Watched_Value2",
"Watched_Value3",
"Watched_Value4"
"watchedValue1",
"watchedValue2",
"watchedValue3",
"watchedValue4"
],
"localized": ["name", "description"],
"name": [
@@ -354,11 +354,11 @@
"description": [
{
"language_code": "en_us",
"string": "Send a notification if selected values change. Use <code>CTRL + Click</code> to select/deselect. <ul> <li><code>Watched_Value1</code> is Previous IP (not recommended)</li><li><code>Watched_Value2</code> unused</li><li><code>Watched_Value3</code> unused </li><li><code>Watched_Value4</code> type </li></ul>"
"string": "Send a notification if selected values change. Use <code>CTRL + Click</code> to select/deselect. <ul> <li><code>watchedValue1</code> is Previous IP (not recommended)</li><li><code>watchedValue2</code> unused</li><li><code>watchedValue3</code> unused </li><li><code>watchedValue4</code> type </li></ul>"
},
{
"language_code": "de_de",
"string": "Sende eine Benachrichtigung, wenn ein ausgwählter Wert sich ändert. <code>STRG + klicken</code> zum aus-/abwählen. <ul> <li><code>Watched_Value1</code> ist die Vorige IP (nicht empfohlen)</li><li><code>Watched_Value2</code> ist nicht in Verwendung</li><li><code>Watched_Value3</code> ist nicht in Verwendung </li><li><code>Watched_Value4</code> ist nicht in Verwendung </li></ul>"
"string": "Sende eine Benachrichtigung, wenn ein ausgwählter Wert sich ändert. <code>STRG + klicken</code> zum aus-/abwählen. <ul> <li><code>watchedValue1</code> ist die Vorige IP (nicht empfohlen)</li><li><code>watchedValue2</code> ist nicht in Verwendung</li><li><code>watchedValue3</code> ist nicht in Verwendung </li><li><code>watchedValue4</code> ist nicht in Verwendung </li></ul>"
}
]
},
@@ -369,7 +369,7 @@
"elements": [
{
"elementType": "select",
"elementOptions": [{ "multiple": "true" }],
"elementOptions": [{ "multiple": "true", "ordeable": "true"}],
"transformers": []
}
]
@@ -399,15 +399,15 @@
"description": [
{
"language_code": "en_us",
"string": "Send a notification only on these statuses. <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."
"string": "Send a notification only on these statuses. <code>new</code> means a new unique (unique combination of PrimaryId and SecondaryId) object was discovered. <code>watched-changed</code> means that selected <code>watchedValueN</code> columns changed."
},
{
"language_code": "es_es",
"string": "Envíe una notificación solo en estos estados. <code>new</code> significa que se descubrió un nuevo objeto único (una combinación única de PrimaryId y SecondaryId). <code>watched-changed</code> significa que las columnas <code>Watched_ValueN</code> seleccionadas cambiaron."
"string": "Envíe una notificación solo en estos estados. <code>new</code> significa que se descubrió un nuevo objeto único (una combinación única de PrimaryId y SecondaryId). <code>watched-changed</code> significa que las columnas <code>watchedValueN</code> seleccionadas cambiaron."
},
{
"language_code": "de_de",
"string": "Benachrichtige nur bei diesen Status. <code>new</code> bedeutet ein neues eindeutiges (einzigartige Kombination aus PrimaryId und SecondaryId) Objekt wurde erkennt. <code>watched-changed</code> bedeutet eine ausgewählte <code>Watched_ValueN</code>-Spalte hat sich geändert."
"string": "Benachrichtige nur bei diesen Status. <code>new</code> bedeutet ein neues eindeutiges (einzigartige Kombination aus PrimaryId und SecondaryId) Objekt wurde erkennt. <code>watched-changed</code> bedeutet eine ausgewählte <code>watchedValueN</code>-Spalte hat sich geändert."
}
]
},
@@ -478,7 +478,7 @@
],
"database_column_definitions": [
{
"column": "Index",
"column": "index",
"css_classes": "col-sm-2",
"show": true,
"type": "none",
@@ -493,7 +493,7 @@
]
},
{
"column": "Object_PrimaryID",
"column": "objectPrimaryId",
"mapped_to_column": "scanMac",
"css_classes": "col-sm-3",
"show": true,
@@ -517,7 +517,7 @@
]
},
{
"column": "Object_SecondaryID",
"column": "objectSecondaryId",
"mapped_to_column": "scanLastIP",
"css_classes": "col-sm-2",
"show": true,
@@ -541,7 +541,7 @@
]
},
{
"column": "Watched_Value1",
"column": "watchedValue1",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -560,7 +560,7 @@
]
},
{
"column": "Watched_Value2",
"column": "watchedValue2",
"css_classes": "col-sm-2",
"show": true,
"type": "textarea_readonly",
@@ -575,7 +575,7 @@
]
},
{
"column": "Watched_Value3",
"column": "watchedValue3",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -590,7 +590,7 @@
]
},
{
"column": "Watched_Value4",
"column": "watchedValue4",
"mapped_to_column": "scanType",
"css_classes": "col-sm-2",
"show": false,
@@ -633,7 +633,7 @@
]
},
{
"column": "DateTimeCreated",
"column": "dateTimeCreated",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -656,7 +656,7 @@
]
},
{
"column": "DateTimeChanged",
"column": "dateTimeChanged",
"mapped_to_column": "scanLastConnection",
"css_classes": "col-sm-2",
"show": true,
@@ -680,7 +680,7 @@
]
},
{
"column": "Status",
"column": "status",
"css_classes": "col-sm-1",
"show": true,
"type": "replace",

View File

@@ -1,6 +1,6 @@
## Overview
A plugin allowing for executing regular internet speed tests.
A plugin allowing for executing regular internet speed tests.
### Usage
@@ -43,9 +43,9 @@ Inside the container, a Python version of speedtest often exists in the virtual
### Data Mapping
- **Watched_Value1** — Download Speed (Mbps).
- **Watched_Value2** — Upload Speed (Mbps).
- **Watched_Value3** — Full JSON payload (useful for n8n or detailed webhooks).
- **watchedValue1** — Download Speed (Mbps).
- **watchedValue2** — Upload Speed (Mbps).
- **watchedValue3** — Full JSON payload (useful for n8n or detailed webhooks).
### Notes

View File

@@ -39,7 +39,7 @@
"params": [],
"database_column_definitions": [
{
"column": "Index",
"column": "index",
"css_classes": "col-sm-2",
"show": true,
"type": "none",
@@ -54,7 +54,7 @@
]
},
{
"column": "Plugin",
"column": "plugin",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -77,7 +77,7 @@
]
},
{
"column": "Object_PrimaryID",
"column": "objectPrimaryId",
"css_classes": "col-sm-2",
"show": false,
"type": "url",
@@ -96,7 +96,7 @@
]
},
{
"column": "Object_SecondaryID",
"column": "objectSecondaryId",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -119,7 +119,7 @@
]
},
{
"column": "DateTimeCreated",
"column": "dateTimeCreated",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -138,7 +138,7 @@
]
},
{
"column": "DateTimeChanged",
"column": "dateTimeChanged",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -161,7 +161,7 @@
]
},
{
"column": "Watched_Value1",
"column": "watchedValue1",
"css_classes": "col-sm-2",
"show": true,
"type": "threshold",
@@ -197,7 +197,7 @@
]
},
{
"column": "Watched_Value2",
"column": "watchedValue2",
"css_classes": "col-sm-2",
"show": true,
"type": "threshold",
@@ -233,7 +233,7 @@
]
},
{
"column": "Watched_Value3",
"column": "watchedValue3",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -256,7 +256,7 @@
]
},
{
"column": "Watched_Value4",
"column": "watchedValue4",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -279,7 +279,7 @@
]
},
{
"column": "UserData",
"column": "userData",
"css_classes": "col-sm-2",
"show": false,
"type": "textbox_save",
@@ -302,7 +302,7 @@
]
},
{
"column": "Status",
"column": "status",
"css_classes": "col-sm-1",
"show": false,
"type": "replace",
@@ -342,7 +342,7 @@
]
},
{
"column": "Extra",
"column": "extra",
"css_classes": "col-sm-3",
"show": false,
"type": "label",
@@ -561,17 +561,17 @@
"elements": [
{
"elementType": "select",
"elementOptions": [{ "multiple": "true" }],
"elementOptions": [{ "multiple": "true", "ordeable": "true"}],
"transformers": []
}
]
},
"default_value": [],
"options": [
"Watched_Value1",
"Watched_Value2",
"Watched_Value3",
"Watched_Value4"
"watchedValue1",
"watchedValue2",
"watchedValue3",
"watchedValue4"
],
"localized": ["name", "description"],
"name": [
@@ -591,15 +591,15 @@
"description": [
{
"language_code": "en_us",
"string": "Send a notification if selected values change. Use <code>CTRL + Click</code> to select/deselect. <ul> <li><code>Watched_Value1</code> is Download speed (not recommended)</li><li><code>Watched_Value2</code> is Upload speed (not recommended)</li><li><code>Watched_Value3</code> is JSON payload for webhooks (schema varies by engine)</li><li><code>Watched_Value4</code> unused </li></ul>"
"string": "Send a notification if selected values change. Use <code>CTRL + Click</code> to select/deselect. <ul> <li><code>watchedValue1</code> is Download speed (not recommended)</li><li><code>watchedValue2</code> is Upload speed (not recommended)</li><li><code>watchedValue3</code> is JSON payload for webhooks (schema varies by engine)</li><li><code>watchedValue4</code> unused </li></ul>"
},
{
"language_code": "es_es",
"string": "Envíe una notificación si los valores seleccionados cambian. Use <code>CTRL + Clic</code> para seleccionar/deseleccionar. <ul> <li><code>Watched_Value1</code> es la velocidad de descarga (no recomendado)</li><li><code>Watched_Value2</code> es la velocidad de carga (no recomendado)</li><li><code>Watched_Value3</code> es la carga útil JSON para webhooks (el esquema varía según el motor)</li><li><code>Watched_Value4</code> no se usa </li></ul>"
"string": "Envíe una notificación si los valores seleccionados cambian. Use <code>CTRL + Clic</code> para seleccionar/deseleccionar. <ul> <li><code>watchedValue1</code> es la velocidad de descarga (no recomendado)</li><li><code>watchedValue2</code> es la velocidad de carga (no recomendado)</li><li><code>watchedValue3</code> es la carga útil JSON para webhooks (el esquema varía según el motor)</li><li><code>watchedValue4</code> no se usa </li></ul>"
},
{
"language_code": "de_de",
"string": "Sende eine Benachrichtigung, wenn ein ausgwählter Wert sich ändert. <code>STRG + klicken</code> zum aus-/abwählen. <ul> <li><code>Watched_Value1</code> ist die Download-Geschwindigkeit (nicht empfohlen)</li><li><code>Watched_Value2</code> ist die Upload-Geschwindigkeit (nicht empfohlen)</li><li><code>Watched_Value3</code> ist JSON-Payload für Webhooks (Schema variiert je nach Engine)</li><li><code>Watched_Value4</code> ist nicht in Verwendung </li></ul>"
"string": "Sende eine Benachrichtigung, wenn ein ausgwählter Wert sich ändert. <code>STRG + klicken</code> zum aus-/abwählen. <ul> <li><code>watchedValue1</code> ist die Download-Geschwindigkeit (nicht empfohlen)</li><li><code>watchedValue2</code> ist die Upload-Geschwindigkeit (nicht empfohlen)</li><li><code>watchedValue3</code> ist JSON-Payload für Webhooks (Schema variiert je nach Engine)</li><li><code>watchedValue4</code> ist nicht in Verwendung </li></ul>"
}
]
},
@@ -610,7 +610,7 @@
"elements": [
{
"elementType": "select",
"elementOptions": [{ "multiple": "true" }],
"elementOptions": [{ "multiple": "true", "ordeable": "true"}],
"transformers": []
}
]
@@ -640,15 +640,15 @@
"description": [
{
"language_code": "en_us",
"string": "Send a notification only on these statuses. <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."
"string": "Send a notification only on these statuses. <code>new</code> means a new unique (unique combination of PrimaryId and SecondaryId) object was discovered. <code>watched-changed</code> means that selected <code>watchedValueN</code> columns changed."
},
{
"language_code": "es_es",
"string": "Envíe una notificación solo en estos estados. <code>new</code> significa que se descubrió un nuevo objeto único (combinación única de PrimaryId y SecondaryId). <code>watched-changed</code> significa que seleccionó <code>Watched_ValueN Las columnas </code> cambiaron."
"string": "Envíe una notificación solo en estos estados. <code>new</code> significa que se descubrió un nuevo objeto único (combinación única de PrimaryId y SecondaryId). <code>watched-changed</code> significa que las columnas <code>watchedValueN</code> seleccionadas cambiaron."
},
{
"language_code": "de_de",
"string": "Benachrichtige nur bei diesen Status. <code>new</code> bedeutet ein neues eindeutiges (einzigartige Kombination aus PrimaryId und SecondaryId) Objekt wurde erkennt. <code>watched-changed</code> bedeutet eine ausgewählte <code>Watched_ValueN</code>-Spalte hat sich geändert."
"string": "Benachrichtige nur bei diesen Status. <code>new</code> bedeutet ein neues eindeutiges (einzigartige Kombination aus PrimaryId und SecondaryId) Objekt wurde erkennt. <code>watched-changed</code> bedeutet eine ausgewählte <code>watchedValueN</code>-Spalte hat sich geändert."
}
]
}

View File

@@ -8,7 +8,7 @@
"mapped_to_table": "CurrentScan",
"data_filters": [
{
"compare_column": "Object_PrimaryID",
"compare_column": "objectPrimaryId",
"compare_operator": "==",
"compare_field_id": "txtMacFilter",
"compare_js_template": "'{value}'.toString()",
@@ -273,7 +273,7 @@
],
"database_column_definitions": [
{
"column": "Index",
"column": "index",
"css_classes": "col-sm-2",
"show": true,
"type": "none",
@@ -290,7 +290,7 @@
]
},
{
"column": "Object_PrimaryID",
"column": "objectPrimaryId",
"mapped_to_column": "scanMac",
"css_classes": "col-sm-3",
"show": true,
@@ -308,7 +308,7 @@
]
},
{
"column": "Object_SecondaryID",
"column": "objectSecondaryId",
"mapped_to_column": "scanLastIP",
"css_classes": "col-sm-2",
"show": true,
@@ -326,7 +326,7 @@
]
},
{
"column": "Watched_Value1",
"column": "watchedValue1",
"mapped_to_column": "scanName",
"css_classes": "col-sm-2",
"show": true,
@@ -344,7 +344,7 @@
]
},
{
"column": "Watched_Value2",
"column": "watchedValue2",
"mapped_to_column": "scanVendor",
"css_classes": "col-sm-2",
"show": true,
@@ -362,7 +362,7 @@
]
},
{
"column": "Watched_Value3",
"column": "watchedValue3",
"mapped_to_column": "scanType",
"css_classes": "col-sm-2",
"show": true,
@@ -380,7 +380,7 @@
]
},
{
"column": "Watched_Value4",
"column": "watchedValue4",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -418,7 +418,7 @@
]
},
{
"column": "DateTimeCreated",
"column": "dateTimeCreated",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -435,7 +435,7 @@
]
},
{
"column": "DateTimeChanged",
"column": "dateTimeChanged",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -452,7 +452,7 @@
]
},
{
"column": "Status",
"column": "status",
"css_classes": "col-sm-1",
"show": true,
"type": "replace",

View File

@@ -9,7 +9,7 @@
"mapped_to_table": "CurrentScan",
"data_filters": [
{
"compare_column": "Object_PrimaryID",
"compare_column": "objectPrimaryId",
"compare_operator": "==",
"compare_field_id": "txtMacFilter",
"compare_js_template": "'{value}'.toString()",
@@ -450,7 +450,7 @@
],
"database_column_definitions": [
{
"column": "Index",
"column": "index",
"css_classes": "col-sm-2",
"show": true,
"type": "none",
@@ -469,7 +469,7 @@
]
},
{
"column": "Object_PrimaryID",
"column": "objectPrimaryId",
"mapped_to_column": "scanMac",
"css_classes": "col-sm-3",
"show": true,
@@ -489,7 +489,7 @@
]
},
{
"column": "Object_SecondaryID",
"column": "objectSecondaryId",
"mapped_to_column": "scanLastIP",
"css_classes": "col-sm-2",
"show": true,
@@ -509,7 +509,7 @@
]
},
{
"column": "Watched_Value1",
"column": "watchedValue1",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -528,7 +528,7 @@
]
},
{
"column": "Watched_Value2",
"column": "watchedValue2",
"mapped_to_column": "scanName",
"css_classes": "col-sm-2",
"show": true,
@@ -571,7 +571,7 @@
]
},
{
"column": "DateTimeCreated",
"column": "dateTimeCreated",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -590,7 +590,7 @@
]
},
{
"column": "DateTimeChanged",
"column": "dateTimeChanged",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -609,7 +609,7 @@
]
},
{
"column": "Status",
"column": "status",
"css_classes": "col-sm-1",
"show": true,
"type": "replace",

View File

@@ -336,7 +336,7 @@
],
"database_column_definitions": [
{
"column": "Index",
"column": "index",
"css_classes": "col-sm-2",
"show": true,
"type": "none",
@@ -351,7 +351,7 @@
]
},
{
"column": "Object_PrimaryID",
"column": "objectPrimaryId",
"mapped_to_column": "scanMac",
"css_classes": "col-sm-3",
"show": true,
@@ -382,7 +382,7 @@
]
},
{
"column": "Object_SecondaryID",
"column": "objectSecondaryId",
"mapped_to_column": "scanLastIP",
"css_classes": "col-sm-2",
"show": true,
@@ -398,7 +398,7 @@
]
},
{
"column": "Watched_Value1",
"column": "watchedValue1",
"css_classes": "col-sm-2",
"show": true,
"type": "device_ip",
@@ -413,7 +413,7 @@
]
},
{
"column": "Watched_Value2",
"column": "watchedValue2",
"mapped_to_column": "scanName",
"css_classes": "col-sm-2",
"show": true,
@@ -429,7 +429,7 @@
]
},
{
"column": "Watched_Value3",
"column": "watchedValue3",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -444,7 +444,7 @@
]
},
{
"column": "Watched_Value4",
"column": "watchedValue4",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -459,7 +459,7 @@
]
},
{
"column": "HelpVal1",
"column": "helpVal1",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -493,7 +493,7 @@
]
},
{
"column": "DateTimeCreated",
"column": "dateTimeCreated",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -512,7 +512,7 @@
]
},
{
"column": "DateTimeChanged",
"column": "dateTimeChanged",
"css_classes": "col-sm-2",
"show": true,
"type": "label",

View File

@@ -8,7 +8,7 @@
"show_ui": true,
"data_filters": [
{
"compare_column": "Object_PrimaryID",
"compare_column": "objectPrimaryId",
"compare_operator": "==",
"compare_field_id": "txtMacFilter",
"compare_js_template": "'{value}'.toString()",
@@ -299,7 +299,7 @@
],
"database_column_definitions": [
{
"column": "Index",
"column": "index",
"css_classes": "col-sm-2",
"show": true,
"type": "none",
@@ -314,7 +314,7 @@
]
},
{
"column": "Object_PrimaryID",
"column": "objectPrimaryId",
"css_classes": "col-sm-2",
"show": true,
"type": "device_name_mac",
@@ -333,7 +333,7 @@
]
},
{
"column": "Object_SecondaryID",
"column": "objectSecondaryId",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -352,7 +352,7 @@
]
},
{
"column": "Watched_Value1",
"column": "watchedValue1",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -367,7 +367,7 @@
]
},
{
"column": "Watched_Value2",
"column": "watchedValue2",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -382,7 +382,7 @@
]
},
{
"column": "DateTimeCreated",
"column": "dateTimeCreated",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -397,7 +397,7 @@
]
},
{
"column": "DateTimeChanged",
"column": "dateTimeChanged",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -412,7 +412,7 @@
]
},
{
"column": "Status",
"column": "status",
"css_classes": "col-sm-1",
"show": true,
"type": "replace",

View File

@@ -8,7 +8,7 @@
"mapped_to_table": "CurrentScan",
"data_filters": [
{
"compare_column": "Object_PrimaryID",
"compare_column": "objectPrimaryId",
"compare_operator": "==",
"compare_field_id": "txtMacFilter",
"compare_js_template": "'{value}'.toString()",
@@ -290,10 +290,10 @@
},
"default_value": [],
"options": [
"Watched_Value1",
"Watched_Value2",
"Watched_Value3",
"Watched_Value4"
"watchedValue1",
"watchedValue2",
"watchedValue3",
"watchedValue4"
],
"localized": [
"name",
@@ -316,15 +316,15 @@
"description": [
{
"language_code": "en_us",
"string": "Send a notification if selected values change. Use <code>CTRL + Click</code> to select/deselect. <ul> <li><code>Watched_Value1</code> is Name</li><li><code>Watched_Value2</code> is Vendor</li><li><code>Watched_Value3</code> is Interface </li><li><code>Watched_Value4</code> is N/A </li></ul>"
"string": "Send a notification if selected values change. Use <code>CTRL + Click</code> to select/deselect. <ul> <li><code>watchedValue1</code> is Name</li><li><code>watchedValue2</code> is Vendor</li><li><code>watchedValue3</code> is Interface </li><li><code>watchedValue4</code> is N/A </li></ul>"
},
{
"language_code": "es_es",
"string": "Envía una notificación si los valores seleccionados cambian. Utilice <code>CTRL + clic</code> para seleccionar/deseleccionar. <ul> <li><code>Valor_observado1</code> es Name</li><li><code>Valor_observado2</code> es Proveedor</li><li><code>Valor_observado3</code> es Interfaz </li><li><code>Valor_observado4</code> es N/A </li></ul>"
"string": "Envía una notificación si los valores seleccionados cambian. Utilice <code>CTRL + clic</code> para seleccionar/deseleccionar. <ul> <li><code>watchedValue1</code> es Name</li><li><code>watchedValue2</code> es Proveedor</li><li><code>watchedValue3</code> es Interfaz </li><li><code>watchedValue4</code> es N/A </li></ul>"
},
{
"language_code": "de_de",
"string": "Sende eine Benachrichtigung, wenn ein ausgwählter Wert sich ändert. <code>STRG + klicken</code> zum aus-/abwählen. <ul> <li><code>Watched_Value1</code> ist der Namen</li><li><code>Watched_Value2</code> ist der Hersteller</li><li><code>Watched_Value3</code> ist das Interface </li><li><code>Watched_Value4</code> ist nicht in Verwendung </li></ul>"
"string": "Sende eine Benachrichtigung, wenn ein ausgwählter Wert sich ändert. <code>STRG + klicken</code> zum aus-/abwählen. <ul> <li><code>watchedValue1</code> ist der Namen</li><li><code>watchedValue2</code> ist der Hersteller</li><li><code>watchedValue3</code> ist das Interface </li><li><code>watchedValue4</code> ist nicht in Verwendung </li></ul>"
}
]
},
@@ -522,7 +522,7 @@
],
"database_column_definitions": [
{
"column": "Index",
"column": "index",
"css_classes": "col-sm-2",
"show": true,
"type": "none",
@@ -539,7 +539,7 @@
]
},
{
"column": "Object_PrimaryID",
"column": "objectPrimaryId",
"mapped_to_column": "scanMac",
"css_classes": "col-sm-3",
"show": true,
@@ -557,7 +557,7 @@
]
},
{
"column": "Object_SecondaryID",
"column": "objectSecondaryId",
"mapped_to_column": "scanLastIP",
"css_classes": "col-sm-2",
"show": true,
@@ -575,7 +575,7 @@
]
},
{
"column": "Watched_Value1",
"column": "watchedValue1",
"mapped_to_column": "scanName",
"css_classes": "col-sm-2",
"show": true,
@@ -593,7 +593,7 @@
]
},
{
"column": "Watched_Value2",
"column": "watchedValue2",
"mapped_to_column": "scanVendor",
"css_classes": "col-sm-2",
"show": true,
@@ -619,7 +619,7 @@
]
},
{
"column": "Watched_Value3",
"column": "watchedValue3",
"mapped_to_column": "scanLastQuery",
"css_classes": "col-sm-2",
"show": true,
@@ -670,7 +670,7 @@
]
},
{
"column": "DateTimeCreated",
"column": "dateTimeCreated",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -695,7 +695,7 @@
]
},
{
"column": "DateTimeChanged",
"column": "dateTimeChanged",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -720,7 +720,7 @@
]
},
{
"column": "Status",
"column": "status",
"css_classes": "col-sm-1",
"show": true,
"type": "replace",

View File

@@ -72,7 +72,7 @@
],
"database_column_definitions": [
{
"column": "Index",
"column": "index",
"css_classes": "col-sm-2",
"show": true,
"type": "none",
@@ -87,7 +87,7 @@
]
},
{
"column": "Plugin",
"column": "plugin",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -102,7 +102,7 @@
]
},
{
"column": "Object_PrimaryID",
"column": "objectPrimaryId",
"css_classes": "col-sm-2",
"show": true,
"type": "device_name_mac",
@@ -121,7 +121,7 @@
]
},
{
"column": "Object_SecondaryID",
"column": "objectSecondaryId",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -140,7 +140,7 @@
]
},
{
"column": "DateTimeCreated",
"column": "dateTimeCreated",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -159,7 +159,7 @@
]
},
{
"column": "DateTimeChanged",
"column": "dateTimeChanged",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -178,7 +178,7 @@
]
},
{
"column": "Watched_Value1",
"column": "watchedValue1",
"css_classes": "col-sm-1",
"show": true,
"type": "label",
@@ -197,7 +197,7 @@
]
},
{
"column": "Watched_Value2",
"column": "watchedValue2",
"css_classes": "col-sm-1",
"show": true,
"type": "label",
@@ -215,7 +215,7 @@
]
},
{
"column": "Watched_Value3",
"column": "watchedValue3",
"css_classes": "col-sm-1",
"show": true,
"type": "regex.url_http_https",
@@ -239,7 +239,7 @@
]
},
{
"column": "Watched_Value4",
"column": "watchedValue4",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -258,7 +258,7 @@
]
},
{
"column": "Extra",
"column": "extra",
"css_classes": "col-sm-1",
"show": false,
"type": "label",
@@ -277,7 +277,7 @@
]
},
{
"column": "UserData",
"column": "userData",
"css_classes": "col-sm-3",
"show": true,
"type": "textbox_save",
@@ -315,7 +315,7 @@
]
},
{
"column": "Status",
"column": "status",
"css_classes": "col-sm-1",
"show": true,
"type": "replace",
@@ -553,17 +553,17 @@
"elements": [
{
"elementType": "select",
"elementOptions": [{ "multiple": "true" }],
"elementOptions": [{ "multiple": "true", "ordeable": "true"}],
"transformers": []
}
]
},
"default_value": ["Watched_Value1"],
"default_value": ["watchedValue1"],
"options": [
"Watched_Value1",
"Watched_Value2",
"Watched_Value3",
"Watched_Value4"
"watchedValue1",
"watchedValue2",
"watchedValue3",
"watchedValue4"
],
"localized": ["name", "description"],
"name": [
@@ -579,11 +579,11 @@
"description": [
{
"language_code": "en_us",
"string": "Send a notification if selected values change. Use <code>CTRL + Click</code> to select/deselect. <ul> <li><code>Watched_Value1</code> is service type (e.g.: http, ssh)</li><li><code>Watched_Value2</code> is Status (open or closed)</li><li><code>Watched_Value3</code> unused </li><li><code>Watched_Value4</code> unused </li></ul>"
"string": "Send a notification if selected values change. Use <code>CTRL + Click</code> to select/deselect. <ul> <li><code>watchedValue1</code> is service type (e.g.: http, ssh)</li><li><code>watchedValue2</code> is Status (open or closed)</li><li><code>watchedValue3</code> unused </li><li><code>watchedValue4</code> unused </li></ul>"
},
{
"language_code": "es_es",
"string": "Envíe una notificación si los valores seleccionados cambian. Utilice <code>CTRL + clic</code> para seleccionar/deseleccionar. <ul> <li><code>Watched_Value1</code> es el tipo de servicio (p. ej., http, ssh)</li><li><code>Watched_Value2</code> es el estado (abierto o cerrado)</li> <li><code>Watched_Value3</code> no utilizado </li><li><code>Watched_Value4</code> no utilizado </li></ul>"
"string": "Envíe una notificación si los valores seleccionados cambian. Utilice <code>CTRL + clic</code> para seleccionar/deseleccionar. <ul> <li><code>watchedValue1</code> es el tipo de servicio (p. ej., http, ssh)</li><li><code>watchedValue2</code> es el estado (abierto o cerrado)</li> <li><code>watchedValue3</code> no utilizado </li><li><code>watchedValue4</code> no utilizado </li></ul>"
}
]
},
@@ -594,7 +594,7 @@
"elements": [
{
"elementType": "select",
"elementOptions": [{ "multiple": "true" }],
"elementOptions": [{ "multiple": "true", "ordeable": "true"}],
"transformers": []
}
]
@@ -615,11 +615,11 @@
"description": [
{
"language_code": "en_us",
"string": "Send a notification only on these statuses. <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."
"string": "Send a notification only on these statuses. <code>new</code> means a new unique (unique combination of PrimaryId and SecondaryId) object was discovered. <code>watched-changed</code> means that selected <code>watchedValueN</code> columns changed."
},
{
"language_code": "es_es",
"string": "Envíe una notificación solo en estos estados. <code>new</code> significa que se descubrió un nuevo objeto único (combinación única de PrimaryId y SecondaryId). <code>watched-changed</code> significa que seleccionó <code>Watched_ValueN Las columnas </code> cambiaron."
"string": "Envíe una notificación solo en estos estados. <code>new</code> significa que se descubrió un nuevo objeto único (combinación única de PrimaryId y SecondaryId). <code>watched-changed</code> significa que las columnas <code>watchedValueN</code> seleccionadas cambiaron."
}
]
}

View File

@@ -33,12 +33,12 @@ The following notification types are available based on the `NTFPRCS_INCLUDED_SE
- Notifies about specific events triggered by a device.
- The device must have **Alert Events** enabled in its settings.
- Includes events:
- `Connected`, `Down Reconnected`, `Disconnected`,`IP Changed`
- `Connected`, `Down Reconnected`, `Disconnected`,`IP Changed`
- you can exclude devices with a custom where condition via the `NTFPRCS_event_condition` setting
### `plugins`
- Notifies when an event is triggered by a plugin.
- These notifications depend on the plugin's configuration of the `Watched_Value1-4` values and the `<plugin>_REPORT_ON` settings.
- These notifications depend on the plugin's configuration of the `watchedValue1-4` values and the `<plugin>_REPORT_ON` settings.
## Device-Specific Overrides

View File

@@ -180,6 +180,150 @@
"string": "You can specify a SQL where condition to filter out Events from notifications. For example <code>AND devLastIP NOT LIKE '192.168.3.%'</code> will always exclude any Event notifications for all devices with the IP starting with <code>192.168.3.%</code>."
}
]
},
{
"function": "TEXT_SECTION_HEADERS",
"type": {
"dataType": "boolean",
"elements": [
{ "elementType": "input", "elementOptions": [{ "type": "checkbox" }], "transformers": [] }
]
},
"default_value": true,
"options": [],
"localized": ["name", "description"],
"name": [
{
"language_code": "en_us",
"string": "Text Section Headers"
}
],
"description": [
{
"language_code": "en_us",
"string": "Enable or disable section titles (e.g. <code>🆕 New devices \\n---------</code>) in text notifications. Enabled by default for backward compatibility."
}
]
},
{
"function": "TEXT_TEMPLATE_new_devices",
"type": {
"dataType": "string",
"elements": [
{ "elementType": "input", "elementOptions": [], "transformers": [] }
]
},
"default_value": "",
"options": [],
"localized": ["name", "description"],
"name": [
{
"language_code": "en_us",
"string": "Text Template: New Devices"
}
],
"description": [
{
"language_code": "en_us",
"string": "Custom text template for new device notifications. Use <code>{FieldName}</code> placeholders, e.g. <code>{devName} ({eveMac}) - {eveIp}</code>. Leave empty for default formatting. Available fields: <code>{devName}</code>, <code>{eveMac}</code>, <code>{devVendor}</code>, <code>{eveIp}</code>, <code>{eveDateTime}</code>, <code>{eveEventType}</code>, <code>{devComments}</code>."
}
]
},
{
"function": "TEXT_TEMPLATE_down_devices",
"type": {
"dataType": "string",
"elements": [
{ "elementType": "input", "elementOptions": [], "transformers": [] }
]
},
"default_value": "",
"options": [],
"localized": ["name", "description"],
"name": [
{
"language_code": "en_us",
"string": "Text Template: Down Devices"
}
],
"description": [
{
"language_code": "en_us",
"string": "Custom text template for down device notifications. Use <code>{FieldName}</code> placeholders, e.g. <code>{devName} ({eveMac}) - {eveIp}</code>. Leave empty for default formatting. Available fields: <code>{devName}</code>, <code>{eveMac}</code>, <code>{devVendor}</code>, <code>{eveIp}</code>, <code>{eveDateTime}</code>, <code>{eveEventType}</code>, <code>{devComments}</code>."
}
]
},
{
"function": "TEXT_TEMPLATE_down_reconnected",
"type": {
"dataType": "string",
"elements": [
{ "elementType": "input", "elementOptions": [], "transformers": [] }
]
},
"default_value": "",
"options": [],
"localized": ["name", "description"],
"name": [
{
"language_code": "en_us",
"string": "Text Template: Reconnected"
}
],
"description": [
{
"language_code": "en_us",
"string": "Custom text template for reconnected device notifications. Use <code>{FieldName}</code> placeholders, e.g. <code>{devName} ({eveMac}) reconnected at {eveDateTime}</code>. Leave empty for default formatting. Available fields: <code>{devName}</code>, <code>{eveMac}</code>, <code>{devVendor}</code>, <code>{eveIp}</code>, <code>{eveDateTime}</code>, <code>{eveEventType}</code>, <code>{devComments}</code>."
}
]
},
{
"function": "TEXT_TEMPLATE_events",
"type": {
"dataType": "string",
"elements": [
{ "elementType": "input", "elementOptions": [], "transformers": [] }
]
},
"default_value": "",
"options": [],
"localized": ["name", "description"],
"name": [
{
"language_code": "en_us",
"string": "Text Template: Events"
}
],
"description": [
{
"language_code": "en_us",
"string": "Custom text template for event notifications. Use <code>{FieldName}</code> placeholders, e.g. <code>{devName} ({eveMac}) {eveEventType} at {eveDateTime}</code>. Leave empty for default formatting. Available fields: <code>{devName}</code>, <code>{eveMac}</code>, <code>{devVendor}</code>, <code>{eveIp}</code>, <code>{eveDateTime}</code>, <code>{eveEventType}</code>, <code>{devComments}</code>."
}
]
},
{
"function": "TEXT_TEMPLATE_plugins",
"type": {
"dataType": "string",
"elements": [
{ "elementType": "input", "elementOptions": [], "transformers": [] }
]
},
"default_value": "",
"options": [],
"localized": ["name", "description"],
"name": [
{
"language_code": "en_us",
"string": "Text Template: Plugins"
}
],
"description": [
{
"language_code": "en_us",
"string": "Custom text template for plugin event notifications. Use <code>{FieldName}</code> placeholders, e.g. <code>{plugin}: {objectPrimaryId} - {status}</code>. Leave empty for default formatting. Available fields: <code>{plugin}</code>, <code>{objectPrimaryId}</code>, <code>{objectSecondaryId}</code>, <code>{dateTimeChanged}</code>, <code>{watchedValue1}</code>, <code>{watchedValue2}</code>, <code>{watchedValue3}</code>, <code>{watchedValue4}</code>, <code>{status}</code>."
}
]
}
],

View File

@@ -8,7 +8,7 @@
"show_ui": true,
"data_filters": [
{
"compare_column": "Object_PrimaryID",
"compare_column": "objectPrimaryId",
"compare_operator": "==",
"compare_field_id": "txtMacFilter",
"compare_js_template": "'{value}'.toString()",
@@ -299,7 +299,7 @@
],
"database_column_definitions": [
{
"column": "Index",
"column": "index",
"css_classes": "col-sm-2",
"show": true,
"type": "none",
@@ -314,7 +314,7 @@
]
},
{
"column": "Object_PrimaryID",
"column": "objectPrimaryId",
"css_classes": "col-sm-3",
"show": true,
"type": "device_name_mac",
@@ -329,7 +329,7 @@
]
},
{
"column": "Object_SecondaryID",
"column": "objectSecondaryId",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -344,7 +344,7 @@
]
},
{
"column": "Watched_Value1",
"column": "watchedValue1",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -359,7 +359,7 @@
]
},
{
"column": "Watched_Value2",
"column": "watchedValue2",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -374,7 +374,7 @@
]
},
{
"column": "DateTimeCreated",
"column": "dateTimeCreated",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -393,7 +393,7 @@
]
},
{
"column": "DateTimeChanged",
"column": "dateTimeChanged",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -412,7 +412,7 @@
]
},
{
"column": "Status",
"column": "status",
"css_classes": "col-sm-1",
"show": true,
"type": "replace",

View File

@@ -8,7 +8,7 @@
"mapped_to_table": "CurrentScan",
"data_filters": [
{
"compare_column": "Object_PrimaryID",
"compare_column": "objectPrimaryId",
"compare_operator": "==",
"compare_field_id": "txtMacFilter",
"compare_js_template": "'{value}'.toString()",
@@ -403,7 +403,7 @@
"description": [
{
"language_code": "en_us",
"string": "Send a notification if selected values change. Use <code>CTRL + Click</code> to select/deselect. <ul> <li><code>Watched_Value1</code> is Hostname </li><li><code>Watched_Value2</code> is Parent Node </li><li><code>Watched_Value3</code> is Port </li><li><code>Watched_Value4</code> is SSID </li></ul>"
"string": "Send a notification if selected values change. Use <code>CTRL + Click</code> to select/deselect. <ul> <li><code>watchedValue1</code> is Hostname </li><li><code>watchedValue2</code> is Parent Node </li><li><code>watchedValue3</code> is Port </li><li><code>watchedValue4</code> is SSID </li></ul>"
}
],
"function": "WATCH",
@@ -419,17 +419,17 @@
}
],
"options": [
"Watched_Value1",
"Watched_Value2",
"Watched_Value3",
"Watched_Value4"
"watchedValue1",
"watchedValue2",
"watchedValue3",
"watchedValue4"
],
"type": {
"dataType": "array",
"elements": [
{
"elementType": "select",
"elementOptions": [{ "multiple": "true" }],
"elementOptions": [{ "multiple": "true", "ordeable": "true"}],
"transformers": []
}
]
@@ -440,7 +440,7 @@
"description": [
{
"language_code": "en_us",
"string": "Send a notification only on these statuses. <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."
"string": "Send a notification only on these statuses. <code>new</code> means a new unique (unique combination of PrimaryId and SecondaryId) object was discovered. <code>watched-changed</code> means that selected <code>watchedValueN</code> columns changed."
}
],
"function": "REPORT_ON",
@@ -462,7 +462,7 @@
"elements": [
{
"elementType": "select",
"elementOptions": [{ "multiple": "true" }],
"elementOptions": [{ "multiple": "true", "ordeable": "true"}],
"transformers": []
}
]
@@ -542,7 +542,7 @@
],
"database_column_definitions": [
{
"column": "Index",
"column": "index",
"css_classes": "col-sm-2",
"show": true,
"type": "none",
@@ -557,7 +557,7 @@
]
},
{
"column": "Object_PrimaryID",
"column": "objectPrimaryId",
"mapped_to_column": "scanMac",
"css_classes": "col-sm-3",
"show": true,
@@ -581,7 +581,7 @@
]
},
{
"column": "Object_SecondaryID",
"column": "objectSecondaryId",
"mapped_to_column": "scanLastIP",
"css_classes": "col-sm-2",
"show": true,
@@ -605,7 +605,7 @@
]
},
{
"column": "Watched_Value1",
"column": "watchedValue1",
"mapped_to_column": "scanName",
"css_classes": "col-sm-2",
"show": true,
@@ -621,7 +621,7 @@
]
},
{
"column": "Watched_Value2",
"column": "watchedValue2",
"mapped_to_column": "scanParentMAC",
"css_classes": "col-sm-2",
"show": true,
@@ -637,7 +637,7 @@
]
},
{
"column": "Watched_Value3",
"column": "watchedValue3",
"mapped_to_column": "scanParentPort",
"css_classes": "col-sm-2",
"show": true,
@@ -653,7 +653,7 @@
]
},
{
"column": "Watched_Value4",
"column": "watchedValue4",
"mapped_to_column": "scanSSID",
"css_classes": "col-sm-2",
"show": true,
@@ -669,7 +669,7 @@
]
},
{
"column": "Extra",
"column": "extra",
"mapped_to_column": "scanType",
"css_classes": "col-sm-2",
"show": false,
@@ -712,7 +712,7 @@
]
},
{
"column": "DateTimeCreated",
"column": "dateTimeCreated",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -735,7 +735,7 @@
]
},
{
"column": "DateTimeChanged",
"column": "dateTimeChanged",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -758,7 +758,7 @@
]
},
{
"column": "Status",
"column": "status",
"css_classes": "col-sm-1",
"show": true,
"type": "replace",

View File

@@ -319,7 +319,7 @@ def main():
# make sure the below mapping is mapped in config.json, for example:
# "database_column_definitions": [
# {
# "column": "Object_PrimaryID", <--------- the value I save into primaryId
# "column": "objectPrimaryId", <--------- the value I save into primaryId
# "mapped_to_column": "scanMac", <--------- gets unserted into the CurrentScan DB table column scanMac
# watched1 = 'null' ,
# figure a way to run my udpate script delayed

View File

@@ -8,7 +8,7 @@
"mapped_to_table": "CurrentScan",
"data_filters": [
{
"compare_column": "Object_PrimaryID",
"compare_column": "objectPrimaryId",
"compare_operator": "==",
"compare_field_id": "txtMacFilter",
"compare_js_template": "'{value}'.toString()",
@@ -380,7 +380,7 @@
"description": [
{
"language_code": "en_us",
"string": "Send a notification if selected values change. Use <code>CTRL + Click</code> to select/deselect. <ul> <li><code>Watched_Value1</code> is Device Name </li><li><code>Watched_Value2</code> is Parent Node MAC</li><li><code>Watched_Value3</code> is Parent Node Port </li><li><code>Watched_Value4</code> is Parent Node SSID </li></ul>"
"string": "Send a notification if selected values change. Use <code>CTRL + Click</code> to select/deselect. <ul> <li><code>watchedValue1</code> is Device Name </li><li><code>watchedValue2</code> is Parent Node MAC</li><li><code>watchedValue3</code> is Parent Node Port </li><li><code>watchedValue4</code> is Parent Node SSID </li></ul>"
}
],
"function": "WATCH",
@@ -392,17 +392,17 @@
}
],
"options": [
"Watched_Value1",
"Watched_Value2",
"Watched_Value3",
"Watched_Value4"
"watchedValue1",
"watchedValue2",
"watchedValue3",
"watchedValue4"
],
"type": {
"dataType": "array",
"elements": [
{
"elementType": "select",
"elementOptions": [{ "multiple": "true" }],
"elementOptions": [{ "multiple": "true", "ordeable": "true"}],
"transformers": []
}
]
@@ -413,7 +413,7 @@
"description": [
{
"language_code": "en_us",
"string": "Send a notification only on these statuses. <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."
"string": "Send a notification only on these statuses. <code>new</code> means a new unique (unique combination of PrimaryId and SecondaryId) object was discovered. <code>watched-changed</code> means that selected <code>watchedValueN</code> columns changed."
}
],
"function": "REPORT_ON",
@@ -435,7 +435,7 @@
"elements": [
{
"elementType": "select",
"elementOptions": [{ "multiple": "true" }],
"elementOptions": [{ "multiple": "true", "ordeable": "true"}],
"transformers": []
}
]
@@ -517,7 +517,7 @@
],
"database_column_definitions": [
{
"column": "Index",
"column": "index",
"css_classes": "col-sm-2",
"show": true,
"type": "none",
@@ -532,7 +532,7 @@
]
},
{
"column": "Object_PrimaryID",
"column": "objectPrimaryId",
"mapped_to_column": "scanMac",
"css_classes": "col-sm-3",
"show": true,
@@ -548,7 +548,7 @@
]
},
{
"column": "Object_SecondaryID",
"column": "objectSecondaryId",
"mapped_to_column": "scanLastIP",
"css_classes": "col-sm-2",
"show": true,
@@ -564,7 +564,7 @@
]
},
{
"column": "Watched_Value1",
"column": "watchedValue1",
"mapped_to_column": "scanName",
"css_classes": "col-sm-2",
"show": true,
@@ -580,7 +580,7 @@
]
},
{
"column": "Watched_Value2",
"column": "watchedValue2",
"mapped_to_column": "scanParentMAC",
"css_classes": "col-sm-2",
"show": true,
@@ -596,7 +596,7 @@
]
},
{
"column": "Watched_Value3",
"column": "watchedValue3",
"mapped_to_column": "scanParentPort",
"css_classes": "col-sm-2",
"show": true,
@@ -612,7 +612,7 @@
]
},
{
"column": "Watched_Value4",
"column": "watchedValue4",
"mapped_to_column": "scanSSID",
"css_classes": "col-sm-2",
"show": true,
@@ -628,7 +628,7 @@
]
},
{
"column": "Extra",
"column": "extra",
"mapped_to_column": "scanType",
"css_classes": "col-sm-2",
"show": false,
@@ -663,7 +663,7 @@
]
},
{
"column": "DateTimeCreated",
"column": "dateTimeCreated",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -678,7 +678,7 @@
]
},
{
"column": "DateTimeChanged",
"column": "dateTimeChanged",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -693,7 +693,7 @@
]
},
{
"column": "HelpVal1",
"column": "helpVal1",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -708,7 +708,7 @@
]
},
{
"column": "HelpVal2",
"column": "helpVal2",
"mapped_to_column": "scanSite",
"css_classes": "col-sm-2",
"show": true,
@@ -724,7 +724,7 @@
]
},
{
"column": "HelpVal3",
"column": "helpVal3",
"css_classes": "col-sm-2",
"show": true,
"type": "label",

View File

@@ -8,7 +8,7 @@
"mapped_to_table": "CurrentScan",
"data_filters": [
{
"compare_column": "Object_PrimaryID",
"compare_column": "objectPrimaryId",
"compare_operator": "==",
"compare_field_id": "txtMacFilter",
"compare_js_template": "'{value}'.toString()",
@@ -441,7 +441,7 @@
],
"database_column_definitions": [
{
"column": "Index",
"column": "index",
"css_classes": "col-sm-2",
"show": true,
"type": "none",
@@ -456,7 +456,7 @@
]
},
{
"column": "Object_PrimaryID",
"column": "objectPrimaryId",
"mapped_to_column": "scanMac",
"css_classes": "col-sm-3",
"show": true,
@@ -472,7 +472,7 @@
]
},
{
"column": "Object_SecondaryID",
"column": "objectSecondaryId",
"mapped_to_column": "scanLastIP",
"css_classes": "col-sm-2",
"show": true,
@@ -488,7 +488,7 @@
]
},
{
"column": "Watched_Value1",
"column": "watchedValue1",
"mapped_to_column": "scanName",
"css_classes": "col-sm-2",
"show": true,
@@ -504,7 +504,7 @@
]
},
{
"column": "Watched_Value2",
"column": "watchedValue2",
"mapped_to_column": "scanVendor",
"css_classes": "col-sm-2",
"show": true,
@@ -520,7 +520,7 @@
]
},
{
"column": "Watched_Value3",
"column": "watchedValue3",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -535,7 +535,7 @@
]
},
{
"column": "Watched_Value4",
"column": "watchedValue4",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -569,7 +569,7 @@
]
},
{
"column": "DateTimeCreated",
"column": "dateTimeCreated",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -584,7 +584,7 @@
]
},
{
"column": "DateTimeChanged",
"column": "dateTimeChanged",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -599,7 +599,7 @@
]
},
{
"column": "Status",
"column": "status",
"css_classes": "col-sm-1",
"show": true,
"type": "replace",

View File

@@ -8,7 +8,7 @@
"mapped_to_table": "CurrentScan",
"data_filters": [
{
"compare_column": "Object_PrimaryID",
"compare_column": "objectPrimaryId",
"compare_operator": "==",
"compare_field_id": "txtMacFilter",
"compare_js_template": "'{value}'.toString()",
@@ -105,7 +105,7 @@
{ "elementType": "input", "elementOptions": [], "transformers": [] }
]
},
"default_value": "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",
"default_value": "SELECT n.hwaddr AS objectPrimaryId, {s-quote}null{s-quote} AS objectSecondaryId, datetime() AS dateTimeChanged, na.ip AS watchedValue1, n.lastQuery AS watchedValue2, na.name AS watchedValue3, n.macVendor AS watchedValue4, {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",
"options": [],
"localized": ["name", "description"],
"name": [
@@ -290,17 +290,17 @@
"elements": [
{
"elementType": "select",
"elementOptions": [{ "multiple": "true" }],
"elementOptions": [{ "multiple": "true", "ordeable": "true"}],
"transformers": []
}
]
},
"default_value": ["Watched_Value1", "Watched_Value2"],
"default_value": ["watchedValue1", "watchedValue2"],
"options": [
"Watched_Value1",
"Watched_Value2",
"Watched_Value3",
"Watched_Value4"
"watchedValue1",
"watchedValue2",
"watchedValue3",
"watchedValue4"
],
"localized": ["name", "description"],
"name": [
@@ -316,11 +316,11 @@
"description": [
{
"language_code": "en_us",
"string": "Send a notification if selected values change. Use <code>CTRL + Click</code> to select/deselect. <ul> <li><code>Watched_Value1</code> is IP</li><li><code>Watched_Value2</code> is Last Query</li><li><code>Watched_Value3</code> is Name </li><li><code>Watched_Value4</code> is N/A </li></ul>"
"string": "Send a notification if selected values change. Use <code>CTRL + Click</code> to select/deselect. <ul> <li><code>watchedValue1</code> is IP</li><li><code>watchedValue2</code> is Last Query</li><li><code>watchedValue3</code> is Name </li><li><code>watchedValue4</code> is N/A </li></ul>"
},
{
"language_code": "es_es",
"string": "Envíe una notificación si los valores seleccionados cambian. Utilice <code>CTRL + clic</code> para seleccionar/deseleccionar. <ul> <li><code>Watched_Value1</code> es IP</li><li><code>Watched_Value2</code> es Proveedor</li><li><code>Watched_Value3</code> is es Interfaz</li><li><code>Watched_Value4</code> es N/A</li></ul>"
"string": "Envíe una notificación si los valores seleccionados cambian. Utilice <code>CTRL + clic</code> para seleccionar/deseleccionar. <ul> <li><code>watchedValue1</code> es IP</li><li><code>watchedValue2</code> es Proveedor</li><li><code>watchedValue3</code> is es Interfaz</li><li><code>watchedValue4</code> es N/A</li></ul>"
}
]
},
@@ -331,7 +331,7 @@
"elements": [
{
"elementType": "select",
"elementOptions": [{ "multiple": "true" }],
"elementOptions": [{ "multiple": "true", "ordeable": "true"}],
"transformers": []
}
]
@@ -369,7 +369,7 @@
"database_column_definitions": [
{
"column": "Index",
"column": "index",
"css_classes": "col-sm-2",
"show": true,
"type": "none",
@@ -384,7 +384,7 @@
]
},
{
"column": "Object_PrimaryID",
"column": "objectPrimaryId",
"mapped_to_column": "scanMac",
"css_classes": "col-sm-2",
"show": true,
@@ -404,7 +404,7 @@
]
},
{
"column": "Object_SecondaryID",
"column": "objectSecondaryId",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -419,7 +419,7 @@
]
},
{
"column": "Watched_Value1",
"column": "watchedValue1",
"mapped_to_column": "scanLastIP",
"css_classes": "col-sm-2",
"show": true,
@@ -439,7 +439,7 @@
]
},
{
"column": "Watched_Value2",
"column": "watchedValue2",
"mapped_to_column": "scanLastQuery",
"css_classes": "col-sm-2",
"show": true,
@@ -455,7 +455,7 @@
]
},
{
"column": "Watched_Value3",
"column": "watchedValue3",
"mapped_to_column": "scanName",
"css_classes": "col-sm-2",
"show": true,
@@ -471,7 +471,7 @@
]
},
{
"column": "Watched_Value4",
"column": "watchedValue4",
"mapped_to_column": "scanVendor",
"css_classes": "col-sm-2",
"show": true,
@@ -510,7 +510,7 @@
]
},
{
"column": "DateTimeCreated",
"column": "dateTimeCreated",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -529,7 +529,7 @@
]
},
{
"column": "DateTimeChanged",
"column": "dateTimeChanged",
"css_classes": "col-sm-2",
"show": true,
"type": "label",

View File

@@ -7,7 +7,7 @@
"data_source": "script",
"data_filters": [
{
"compare_column": "Object_PrimaryID",
"compare_column": "objectPrimaryId",
"compare_operator": "==",
"compare_field_id": "txtMacFilter",
"compare_js_template": "'{value}'.toString()",
@@ -65,7 +65,7 @@
],
"database_column_definitions": [
{
"column": "Index",
"column": "index",
"css_classes": "col-sm-2",
"show": true,
"type": "none",
@@ -80,7 +80,7 @@
]
},
{
"column": "Plugin",
"column": "plugin",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -99,7 +99,7 @@
]
},
{
"column": "Object_PrimaryID",
"column": "objectPrimaryId",
"mapped_to_column": "scanMac",
"css_classes": "col-sm-2",
"show": true,
@@ -119,7 +119,7 @@
]
},
{
"column": "Object_SecondaryID",
"column": "objectSecondaryId",
"mapped_to_column": "scanLastIP",
"css_classes": "col-sm-2",
"show": true,
@@ -139,7 +139,7 @@
]
},
{
"column": "DateTimeCreated",
"column": "dateTimeCreated",
"mapped_to_column": "scanLastConnection",
"css_classes": "col-sm-2",
"show": true,
@@ -159,7 +159,7 @@
]
},
{
"column": "Watched_Value1",
"column": "watchedValue1",
"mapped_to_column": "scanName",
"css_classes": "col-sm-2",
"show": true,
@@ -179,7 +179,7 @@
]
},
{
"column": "Watched_Value2",
"column": "watchedValue2",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -198,7 +198,7 @@
]
},
{
"column": "Watched_Value3",
"column": "watchedValue3",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -217,7 +217,7 @@
]
},
{
"column": "Watched_Value4",
"column": "watchedValue4",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -236,7 +236,7 @@
]
},
{
"column": "UserData",
"column": "userData",
"css_classes": "col-sm-2",
"show": false,
"type": "textbox_save",
@@ -255,7 +255,7 @@
]
},
{
"column": "Extra",
"column": "extra",
"css_classes": "col-sm-3",
"show": true,
"type": "label",
@@ -297,7 +297,7 @@
]
},
{
"column": "Status",
"column": "status",
"css_classes": "col-sm-1",
"show": true,
"type": "replace",
@@ -658,17 +658,17 @@
"elements": [
{
"elementType": "select",
"elementOptions": [{ "multiple": "true" }],
"elementOptions": [{ "multiple": "true", "ordeable": "true"}],
"transformers": []
}
]
},
"default_value": ["Watched_Value1"],
"default_value": ["watchedValue1"],
"options": [
"Watched_Value1",
"Watched_Value2",
"Watched_Value3",
"Watched_Value4"
"watchedValue1",
"watchedValue2",
"watchedValue3",
"watchedValue4"
],
"localized": ["name", "description"],
"name": [
@@ -684,11 +684,11 @@
"description": [
{
"language_code": "en_us",
"string": "Send a notification if selected values change. Use <code>CTRL + Click</code> to select/deselect. <ul> <li><code>Watched_Value1</code> is Hostname (not discoverable) </li><li><code>Watched_Value2</code> is Router IP </li><li><code>Watched_Value3</code> is not used </li><li><code>Watched_Value4</code> is not used </li></ul>"
"string": "Send a notification if selected values change. Use <code>CTRL + Click</code> to select/deselect. <ul> <li><code>watchedValue1</code> is Hostname (not discoverable) </li><li><code>watchedValue2</code> is Router IP </li><li><code>watchedValue3</code> is not used </li><li><code>watchedValue4</code> is not used </li></ul>"
},
{
"language_code": "es_es",
"string": "Envíe una notificación si los valores seleccionados cambian. Utilice <code>CTRL + clic</code> para seleccionar/deseleccionar. <ul> <li><code>Watched_Value1</code> es el nombre de host (no detectable) </li><li><code>Watched_Value2</code> es la IP del enrutador </li><li><code>Watched_Value3< /code> no se utiliza </li><li><code>Watched_Value4</code> no se utiliza </li></ul>"
"string": "Envíe una notificación si los valores seleccionados cambian. Utilice <code>CTRL + clic</code> para seleccionar/deseleccionar. <ul> <li><code>watchedValue1</code> es el nombre de host (no detectable) </li><li><code>watchedValue2</code> es la IP del enrutador </li><li><code>watchedValue3</code> no se utiliza </li><li><code>watchedValue4</code> no se utiliza </li></ul>"
}
]
},
@@ -699,7 +699,7 @@
"elements": [
{
"elementType": "select",
"elementOptions": [{ "multiple": "true" }],
"elementOptions": [{ "multiple": "true", "ordeable": "true"}],
"transformers": []
}
]
@@ -725,11 +725,11 @@
"description": [
{
"language_code": "en_us",
"string": "Send a notification only on these statuses. <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."
"string": "Send a notification only on these statuses. <code>new</code> means a new unique (unique combination of PrimaryId and SecondaryId) object was discovered. <code>watched-changed</code> means that selected <code>watchedValueN</code> columns changed."
},
{
"language_code": "es_es",
"string": "Envíe una notificación solo en estos estados. <code>new</code> significa que se descubrió un nuevo objeto único (una combinación única de PrimaryId y SecondaryId). <code>watched-changed</code> significa que las columnas <code>Watched_ValueN</code> seleccionadas cambiaron."
"string": "Envíe una notificación solo en estos estados. <code>new</code> significa que se descubrió un nuevo objeto único (una combinación única de PrimaryId y SecondaryId). <code>watched-changed</code> significa que las columnas <code>watchedValueN</code> seleccionadas cambiaron."
}
]
}

View File

@@ -7,7 +7,7 @@
"mapped_to_table": "CurrentScan",
"data_filters": [
{
"compare_column": "Object_PrimaryID",
"compare_column": "objectPrimaryId",
"compare_operator": "==",
"compare_field_id": "txtMacFilter",
"compare_js_template": "'{value}'.toString()",
@@ -667,7 +667,7 @@
],
"database_column_definitions": [
{
"column": "Index",
"column": "index",
"css_classes": "col-sm-2",
"show": true,
"type": "none",
@@ -684,7 +684,7 @@
]
},
{
"column": "Object_PrimaryID",
"column": "objectPrimaryId",
"mapped_to_column": "scanMac",
"css_classes": "col-sm-3",
"show": true,
@@ -710,7 +710,7 @@
]
},
{
"column": "Object_SecondaryID",
"column": "objectSecondaryId",
"mapped_to_column": "scanLastIP",
"css_classes": "col-sm-2",
"show": true,
@@ -736,7 +736,7 @@
]
},
{
"column": "Watched_Value1",
"column": "watchedValue1",
"mapped_to_column": "scanName",
"css_classes": "col-sm-2",
"show": true,
@@ -754,7 +754,7 @@
]
},
{
"column": "Watched_Value2",
"column": "watchedValue2",
"mapped_to_column": "scanVendor",
"css_classes": "col-sm-2",
"show": true,
@@ -772,7 +772,7 @@
]
},
{
"column": "Watched_Value3",
"column": "watchedValue3",
"mapped_to_column": "scanSyncHubNode",
"css_classes": "col-sm-2",
"show": true,
@@ -790,7 +790,7 @@
]
},
{
"column": "Watched_Value4",
"column": "watchedValue4",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -836,7 +836,7 @@
]
},
{
"column": "DateTimeCreated",
"column": "dateTimeCreated",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -861,7 +861,7 @@
]
},
{
"column": "DateTimeChanged",
"column": "dateTimeChanged",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -886,7 +886,7 @@
]
},
{
"column": "Status",
"column": "status",
"css_classes": "col-sm-1",
"show": true,
"type": "replace",

View File

@@ -8,7 +8,7 @@
"mapped_to_table": "CurrentScan",
"data_filters": [
{
"compare_column": "Object_PrimaryID",
"compare_column": "objectPrimaryId",
"compare_operator": "==",
"compare_field_id": "txtMacFilter",
"compare_js_template": "'{value}'.toString()",
@@ -569,7 +569,7 @@
],
"database_column_definitions": [
{
"column": "Index",
"column": "index",
"css_classes": "col-sm-2",
"show": true,
"type": "none",
@@ -586,7 +586,7 @@
]
},
{
"column": "Object_PrimaryID",
"column": "objectPrimaryId",
"mapped_to_column": "scanMac",
"css_classes": "col-sm-3",
"show": true,
@@ -604,7 +604,7 @@
]
},
{
"column": "Object_SecondaryID",
"column": "objectSecondaryId",
"mapped_to_column": "scanLastIP",
"css_classes": "col-sm-2",
"show": true,
@@ -622,7 +622,7 @@
]
},
{
"column": "Watched_Value1",
"column": "watchedValue1",
"mapped_to_column": "scanName",
"css_classes": "col-sm-2",
"show": true,
@@ -640,7 +640,7 @@
]
},
{
"column": "Watched_Value2",
"column": "watchedValue2",
"mapped_to_column": "scanType",
"css_classes": "col-sm-2",
"show": true,
@@ -658,7 +658,7 @@
]
},
{
"column": "Watched_Value3",
"column": "watchedValue3",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -675,7 +675,7 @@
]
},
{
"column": "Watched_Value4",
"column": "watchedValue4",
"mapped_to_column": "scanParentMAC",
"css_classes": "col-sm-2",
"show": true,
@@ -714,7 +714,7 @@
]
},
{
"column": "DateTimeCreated",
"column": "dateTimeCreated",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -731,7 +731,7 @@
]
},
{
"column": "DateTimeChanged",
"column": "dateTimeChanged",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -748,7 +748,7 @@
]
},
{
"column": "Status",
"column": "status",
"css_classes": "col-sm-1",
"show": true,
"type": "replace",

View File

@@ -72,7 +72,7 @@
],
"data_filters": [
{
"compare_column": "Object_PrimaryID",
"compare_column": "objectPrimaryId",
"compare_field_id": "txtMacFilter",
"compare_js_template": "'{value}'.toString()",
"compare_operator": "==",
@@ -81,7 +81,7 @@
],
"database_column_definitions": [
{
"column": "Index",
"column": "index",
"css_classes": "col-sm-2",
"show": true,
"type": "none",
@@ -96,7 +96,7 @@
]
},
{
"column": "Plugin",
"column": "plugin",
"css_classes": "col-sm-2",
"default_value": "",
"localized": ["name"],
@@ -119,7 +119,7 @@
"type": "label"
},
{
"column": "Object_PrimaryID",
"column": "objectPrimaryId",
"css_classes": "col-sm-2",
"default_value": "",
"localized": ["name"],
@@ -143,7 +143,7 @@
"type": "device_mac"
},
{
"column": "Object_SecondaryID",
"column": "objectSecondaryId",
"css_classes": "col-sm-2",
"default_value": "",
"localized": ["name"],
@@ -167,7 +167,7 @@
"type": "device_ip"
},
{
"column": "DateTimeCreated",
"column": "dateTimeCreated",
"css_classes": "col-sm-2",
"default_value": "",
"localized": ["name"],
@@ -190,7 +190,7 @@
"type": "label"
},
{
"column": "DateTimeChanged",
"column": "dateTimeChanged",
"css_classes": "col-sm-2",
"default_value": "",
"localized": ["name"],
@@ -214,7 +214,7 @@
"type": "label"
},
{
"column": "Watched_Value1",
"column": "watchedValue1",
"css_classes": "col-sm-2",
"default_value": "",
"localized": ["name"],
@@ -238,7 +238,7 @@
"type": "label"
},
{
"column": "Watched_Value2",
"column": "watchedValue2",
"mapped_to_column": "scanVendor",
"css_classes": "col-sm-2",
"default_value": "",
@@ -262,7 +262,7 @@
"type": "label"
},
{
"column": "Watched_Value3",
"column": "watchedValue3",
"mapped_to_column": "scanType",
"css_classes": "col-sm-2",
"default_value": "",
@@ -286,7 +286,7 @@
"type": "label"
},
{
"column": "Watched_Value4",
"column": "watchedValue4",
"css_classes": "col-sm-2",
"default_value": "",
"localized": ["name"],
@@ -309,7 +309,7 @@
"type": "label"
},
{
"column": "UserData",
"column": "userData",
"css_classes": "col-sm-2",
"default_value": "",
"localized": ["name"],
@@ -359,7 +359,7 @@
"type": "label"
},
{
"column": "Extra",
"column": "extra",
"css_classes": "col-sm-3",
"default_value": "",
"localized": ["name"],
@@ -382,7 +382,7 @@
"type": "label"
},
{
"column": "HelpVal1",
"column": "helpVal1",
"mapped_to_column": "scanParentMAC",
"css_classes": "col-sm-2",
"default_value": "",
@@ -398,7 +398,7 @@
"type": "label"
},
{
"column": "HelpVal2",
"column": "helpVal2",
"mapped_to_column": "scanParentPort",
"css_classes": "col-sm-2",
"default_value": "",
@@ -414,7 +414,7 @@
"type": "label"
},
{
"column": "Status",
"column": "status",
"css_classes": "col-sm-1",
"default_value": "",
"localized": ["name"],
@@ -992,15 +992,15 @@
]
},
{
"default_value": ["Watched_Value1", "Watched_Value4"],
"default_value": ["watchedValue1", "watchedValue4"],
"description": [
{
"language_code": "en_us",
"string": "Send a notification if selected values change. Use <code>CTRL + Click</code> to select/deselect. <ul> <li><code>Watched_Value1</code> is Hostname </li><li><code>Watched_Value2</code> is Vendor </li><li><code>Watched_Value3</code> is Type </li><li><code>Watched_Value4</code> is Online </li></ul>"
"string": "Send a notification if selected values change. Use <code>CTRL + Click</code> to select/deselect. <ul> <li><code>watchedValue1</code> is Hostname </li><li><code>watchedValue2</code> is Vendor </li><li><code>watchedValue3</code> is Type </li><li><code>watchedValue4</code> is Online </li></ul>"
},
{
"language_code": "es_es",
"string": "Envíe una notificación si los valores seleccionados cambian. Utilice <code>CTRL + clic</code> para seleccionar/deseleccionar. <ul> <li><code>Watched_Value1</code> es el nombre de host </li><li><code>Watched_Value2</code> es el proveedor </li><li><code>Watched_Value3</code> es el tipo </li><li><code>Watched_Value4</code> es Online </li></ul>"
"string": "Envíe una notificación si los valores seleccionados cambian. Utilice <code>CTRL + clic</code> para seleccionar/deseleccionar. <ul> <li><code>watchedValue1</code> es el nombre de host </li><li><code>watchedValue2</code> es el proveedor </li><li><code>watchedValue3</code> es el tipo </li><li><code>watchedValue4</code> es Online </li></ul>"
}
],
"function": "WATCH",
@@ -1016,17 +1016,17 @@
}
],
"options": [
"Watched_Value1",
"Watched_Value2",
"Watched_Value3",
"Watched_Value4"
"watchedValue1",
"watchedValue2",
"watchedValue3",
"watchedValue4"
],
"type": {
"dataType": "array",
"elements": [
{
"elementType": "select",
"elementOptions": [{ "multiple": "true" }],
"elementOptions": [{ "multiple": "true", "ordeable": "true"}],
"transformers": []
}
]
@@ -1037,11 +1037,11 @@
"description": [
{
"language_code": "en_us",
"string": "Send a notification only on these statuses. <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."
"string": "Send a notification only on these statuses. <code>new</code> means a new unique (unique combination of PrimaryId and SecondaryId) object was discovered. <code>watched-changed</code> means that selected <code>watchedValueN</code> columns changed."
},
{
"language_code": "es_es",
"string": "Envíe una notificación solo en estos estados. <code>new</code> significa que se descubrió un nuevo objeto único (una combinación única de PrimaryId y SecondaryId). <code>watched-changed</code> significa que las columnas <code>Watched_ValueN</code> seleccionadas cambiaron."
"string": "Envíe una notificación solo en estos estados. <code>new</code> significa que se descubrió un nuevo objeto único (una combinación única de PrimaryId y SecondaryId). <code>watched-changed</code> significa que las columnas <code>watchedValueN</code> seleccionadas cambiaron."
}
],
"function": "REPORT_ON",
@@ -1067,7 +1067,7 @@
"elements": [
{
"elementType": "select",
"elementOptions": [{ "multiple": "true" }],
"elementOptions": [{ "multiple": "true", "ordeable": "true"}],
"transformers": []
}
]

View File

@@ -299,17 +299,17 @@
"elements": [
{
"elementType": "select",
"elementOptions": [{ "multiple": "true" }],
"elementOptions": [{ "multiple": "true", "ordeable": "true"}],
"transformers": []
}
]
},
"default_value": ["Watched_Value1"],
"default_value": ["watchedValue1"],
"options": [
"Watched_Value1",
"Watched_Value2",
"Watched_Value3",
"Watched_Value4"
"watchedValue1",
"watchedValue2",
"watchedValue3",
"watchedValue4"
],
"localized": ["name", "description"],
"name": [
@@ -329,11 +329,11 @@
"description": [
{
"language_code": "en_us",
"string": "Send a notification if selected values change. Use <code>CTRL + Click</code> to select/deselect. <ul> <li><code>Watched_Value1</code> is vendor name</li><li><code>Watched_Value2</code> is device name</li><li><code>Watched_Value3</code> unused </li><li><code>Watched_Value4</code> unused </li></ul>"
"string": "Send a notification if selected values change. Use <code>CTRL + Click</code> to select/deselect. <ul> <li><code>watchedValue1</code> is vendor name</li><li><code>watchedValue2</code> is device name</li><li><code>watchedValue3</code> unused </li><li><code>watchedValue4</code> unused </li></ul>"
},
{
"language_code": "de_de",
"string": "Sende eine Benachrichtigung, wenn ein ausgwählter Wert sich ändert. <code>STRG + klicken</code> zum aus-/abwählen. <ul> <li><code>Watched_Value1</code> ist der Herstellername</li><li><code>Watched_Value2</code> ist der Gerätename</li><li><code>Watched_Value3</code> ist nicht in Verwendung </li><li><code>Watched_Value4</code> ist nicht in Verwendung </li></ul>"
"string": "Sende eine Benachrichtigung, wenn ein ausgwählter Wert sich ändert. <code>STRG + klicken</code> zum aus-/abwählen. <ul> <li><code>watchedValue1</code> ist der Herstellername</li><li><code>watchedValue2</code> ist der Gerätename</li><li><code>watchedValue3</code> ist nicht in Verwendung </li><li><code>watchedValue4</code> ist nicht in Verwendung </li></ul>"
}
]
},
@@ -344,7 +344,7 @@
"elements": [
{
"elementType": "select",
"elementOptions": [{ "multiple": "true" }],
"elementOptions": [{ "multiple": "true", "ordeable": "true"}],
"transformers": []
}
]
@@ -374,22 +374,22 @@
"description": [
{
"language_code": "en_us",
"string": "Send a notification only on these statuses. <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."
"string": "Send a notification only on these statuses. <code>new</code> means a new unique (unique combination of PrimaryId and SecondaryId) object was discovered. <code>watched-changed</code> means that selected <code>watchedValueN</code> columns changed."
},
{
"language_code": "es_es",
"string": "Envíe una notificación solo en estos estados. <code>new</code> significa que se descubrió un nuevo objeto único (combinación única de PrimaryId y SecondaryId). <code>watched-changed</code> significa que seleccionó <code>Watched_ValueN Las columnas </code> cambiaron."
"string": "Envíe una notificación solo en estos estados. <code>new</code> significa que se descubrió un nuevo objeto único (combinación única de PrimaryId y SecondaryId). <code>watched-changed</code> significa que las columnas <code>watchedValueN</code> seleccionadas cambiaron."
},
{
"language_code": "de_de",
"string": "Benachrichtige nur bei diesen Status. <code>new</code> bedeutet ein neues eindeutiges (einzigartige Kombination aus PrimaryId und SecondaryId) Objekt wurde erkennt. <code>watched-changed</code> bedeutet eine ausgewählte <code>Watched_ValueN</code>-Spalte hat sich geändert."
"string": "Benachrichtige nur bei diesen Status. <code>new</code> bedeutet ein neues eindeutiges (einzigartige Kombination aus PrimaryId und SecondaryId) Objekt wurde erkennt. <code>watched-changed</code> bedeutet eine ausgewählte <code>watchedValueN</code>-Spalte hat sich geändert."
}
]
}
],
"database_column_definitions": [
{
"column": "Index",
"column": "index",
"css_classes": "col-sm-2",
"show": true,
"type": "none",
@@ -404,7 +404,7 @@
]
},
{
"column": "Plugin",
"column": "plugin",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -427,7 +427,7 @@
]
},
{
"column": "Object_PrimaryID",
"column": "objectPrimaryId",
"mapped_to_column": "scanMac",
"css_classes": "col-sm-2",
"show": true,
@@ -451,7 +451,7 @@
]
},
{
"column": "Object_SecondaryID",
"column": "objectSecondaryId",
"mapped_to_column": "scanLastIP",
"css_classes": "col-sm-2",
"show": true,
@@ -475,7 +475,7 @@
]
},
{
"column": "DateTimeCreated",
"column": "dateTimeCreated",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -498,7 +498,7 @@
]
},
{
"column": "DateTimeChanged",
"column": "dateTimeChanged",
"mapped_to_column": "scanLastConnection",
"css_classes": "col-sm-2",
"show": true,
@@ -549,7 +549,7 @@
]
},
{
"column": "Watched_Value1",
"column": "watchedValue1",
"mapped_to_column": "scanVendor",
"css_classes": "col-sm-2",
"show": true,
@@ -569,7 +569,7 @@
]
},
{
"column": "Watched_Value2",
"column": "watchedValue2",
"mapped_to_column": "scanName",
"css_classes": "col-sm-2",
"show": true,
@@ -593,7 +593,7 @@
]
},
{
"column": "Watched_Value3",
"column": "watchedValue3",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -612,7 +612,7 @@
]
},
{
"column": "Watched_Value4",
"column": "watchedValue4",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -631,7 +631,7 @@
]
},
{
"column": "UserData",
"column": "userData",
"css_classes": "col-sm-2",
"show": false,
"type": "textbox_save",
@@ -654,7 +654,7 @@
]
},
{
"column": "Extra",
"column": "extra",
"css_classes": "col-sm-3",
"show": false,
"type": "label",
@@ -673,7 +673,7 @@
]
},
{
"column": "Status",
"column": "status",
"css_classes": "col-sm-1",
"show": true,
"type": "replace",

View File

@@ -7,7 +7,7 @@
"data_source": "script",
"data_filters": [
{
"compare_column": "Object_PrimaryID",
"compare_column": "objectPrimaryId",
"compare_operator": "==",
"compare_field_id": "txtMacFilter",
"compare_js_template": "'{value}'.toString()",
@@ -356,7 +356,7 @@
],
"database_column_definitions": [
{
"column": "Index",
"column": "index",
"css_classes": "col-sm-2",
"show": true,
"type": "none",
@@ -371,7 +371,7 @@
]
},
{
"column": "Object_PrimaryID",
"column": "objectPrimaryId",
"css_classes": "col-sm-2",
"show": true,
"type": "device_name_mac",
@@ -386,7 +386,7 @@
]
},
{
"column": "Object_SecondaryID",
"column": "objectSecondaryId",
"css_classes": "col-sm-2",
"show": true,
"type": "device_ip",
@@ -401,7 +401,7 @@
]
},
{
"column": "Watched_Value1",
"column": "watchedValue1",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -416,7 +416,7 @@
]
},
{
"column": "Watched_Value2",
"column": "watchedValue2",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -431,7 +431,7 @@
]
},
{
"column": "Watched_Value3",
"column": "watchedValue3",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -446,7 +446,7 @@
]
},
{
"column": "Watched_Value4",
"column": "watchedValue4",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -476,7 +476,7 @@
]
},
{
"column": "DateTimeCreated",
"column": "dateTimeCreated",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -491,7 +491,7 @@
]
},
{
"column": "DateTimeChanged",
"column": "dateTimeChanged",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -506,7 +506,7 @@
]
},
{
"column": "Status",
"column": "status",
"css_classes": "col-sm-1",
"show": true,
"type": "replace",

View File

@@ -51,7 +51,7 @@
],
"database_column_definitions": [
{
"column": "Index",
"column": "index",
"css_classes": "col-sm-2",
"show": true,
"type": "none",
@@ -66,7 +66,7 @@
]
},
{
"column": "Plugin",
"column": "plugin",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -85,7 +85,7 @@
]
},
{
"column": "Object_PrimaryID",
"column": "objectPrimaryId",
"css_classes": "col-sm-2",
"show": true,
"type": "url",
@@ -104,7 +104,7 @@
]
},
{
"column": "Object_SecondaryID",
"column": "objectSecondaryId",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -123,7 +123,7 @@
]
},
{
"column": "DateTimeCreated",
"column": "dateTimeCreated",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -142,7 +142,7 @@
]
},
{
"column": "DateTimeChanged",
"column": "dateTimeChanged",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -161,7 +161,7 @@
]
},
{
"column": "Watched_Value1",
"column": "watchedValue1",
"css_classes": "col-sm-2",
"show": true,
"type": "threshold",
@@ -201,7 +201,7 @@
]
},
{
"column": "Watched_Value2",
"column": "watchedValue2",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
@@ -220,7 +220,7 @@
]
},
{
"column": "Watched_Value3",
"column": "watchedValue3",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -239,7 +239,7 @@
]
},
{
"column": "Watched_Value4",
"column": "watchedValue4",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
@@ -258,7 +258,7 @@
]
},
{
"column": "UserData",
"column": "userData",
"css_classes": "col-sm-2",
"show": true,
"type": "textbox_save",
@@ -277,7 +277,7 @@
]
},
{
"column": "Status",
"column": "status",
"css_classes": "col-sm-1",
"show": true,
"type": "replace",
@@ -313,7 +313,7 @@
]
},
{
"column": "Extra",
"column": "extra",
"css_classes": "col-sm-3",
"show": false,
"type": "label",
@@ -538,17 +538,17 @@
"elements": [
{
"elementType": "select",
"elementOptions": [{ "multiple": "true" }],
"elementOptions": [{ "multiple": "true", "ordeable": "true"}],
"transformers": []
}
]
},
"default_value": ["Watched_Value1"],
"default_value": ["watchedValue1"],
"options": [
"Watched_Value1",
"Watched_Value2",
"Watched_Value3",
"Watched_Value4"
"watchedValue1",
"watchedValue2",
"watchedValue3",
"watchedValue4"
],
"localized": ["name", "description"],
"name": [
@@ -564,11 +564,11 @@
"description": [
{
"language_code": "en_us",
"string": "Send a notification if selected values change. Use <code>CTRL + Click</code> to select/deselect. <ul> <li><code>Watched_Value1</code> is response status code (e.g.: 200, 404)</li><li><code>Watched_Value2</code> is Latency (not recommended)</li><li><code>Watched_Value3</code> unused </li><li><code>Watched_Value4</code> unused </li></ul>"
"string": "Send a notification if selected values change. Use <code>CTRL + Click</code> to select/deselect. <ul> <li><code>watchedValue1</code> is response status code (e.g.: 200, 404)</li><li><code>watchedValue2</code> is Latency (not recommended)</li><li><code>watchedValue3</code> unused </li><li><code>watchedValue4</code> unused </li></ul>"
},
{
"language_code": "es_es",
"string": "Envíe una notificación si los valores seleccionados cambian. Use <code>CTRL + Click</code> para seleccionar/deseleccionar. <ul> <li><code>Watched_Value1</code> es un código de estado de respuesta (por ejemplo: 200, 404) </li><li><code>Valor_observado2</code> es Latencia (no recomendado)</li><li><code>Valor_observado3</code> no utilizado </li><li><code>Valor_observado4 </ code> sin usar </li></ul>"
"string": "Envíe una notificación si los valores seleccionados cambian. Use <code>CTRL + Click</code> para seleccionar/deseleccionar. <ul> <li><code>watchedValue1</code> es un código de estado de respuesta (por ejemplo: 200, 404) </li><li><code>watchedValue2</code> es Latencia (no recomendado)</li><li><code>watchedValue3</code> no utilizado </li><li><code>watchedValue4</code> sin usar </li></ul>"
}
]
},
@@ -579,7 +579,7 @@
"elements": [
{
"elementType": "select",
"elementOptions": [{ "multiple": "true" }],
"elementOptions": [{ "multiple": "true", "ordeable": "true"}],
"transformers": []
}
]
@@ -605,11 +605,11 @@
"description": [
{
"language_code": "en_us",
"string": "Send a notification only on these statuses. <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."
"string": "Send a notification only on these statuses. <code>new</code> means a new unique (unique combination of objectPrimaryId and objectSecondaryId) object was discovered. <code>watched-changed</code> means that selected <code>watchedValueN</code> columns changed."
},
{
"language_code": "es_es",
"string": "Envíe una notificación solo en estos estados. <code>new</code> significa que se descubrió un nuevo objeto único (combinación única de PrimaryId y SecondaryId). <code>watched-changed</code> significa que seleccionó <code>Watched_ValueN Las columnas </code> cambiaron."
"string": "Envíe una notificación solo en estos estados. <code>new</code> significa que se descubrió un nuevo objeto único (combinación única de PrimaryId y SecondaryId). <code>watched-changed</code> significa que seleccionó <code>watchedValueN Las columnas </code> cambiaron."
}
]
},

View File

@@ -246,9 +246,9 @@ function genericSaveData (id) {
headers: { "Authorization": `Bearer ${apiToken}` },
data: JSON.stringify({
dbtable: "Plugins_Objects",
columnName: "Index",
columnName: "index",
id: index,
columns: "UserData",
columns: "userData",
values: columnValue
}),
contentType: "application/json",
@@ -273,26 +273,23 @@ function genericSaveData (id) {
// -----------------------------------------------------------------------------
pluginDefinitions = []
pluginUnprocessedEvents = []
pluginObjects = []
pluginHistory = []
// Global counts map, populated before tabs are rendered.
// null = counts unavailable (fail-open: show all plugins)
let pluginCounts = null;
async function getData() {
try {
showSpinner();
console.log("Plugins getData called");
const [plugins, events, objects, history] = await Promise.all([
fetchJson('plugins.json'),
fetchJson('table_plugins_events.json'),
fetchJson('table_plugins_objects.json'),
fetchJson('table_plugins_history.json')
]);
const plugins = await fetchJson('plugins.json');
pluginDefinitions = plugins.data;
pluginUnprocessedEvents = events.data;
pluginObjects = objects.data;
pluginHistory = history.data;
// Fetch counts BEFORE rendering tabs so we can skip empty plugins (no flicker).
// fetchPluginCounts never throws — returns null on failure (fail-open).
const prefixes = pluginDefinitions.filter(p => p.show_ui).map(p => p.unique_prefix);
pluginCounts = await fetchPluginCounts(prefixes);
generateTabs();
} catch (err) {
@@ -306,6 +303,169 @@ async function fetchJson(filename) {
return await response.json();
}
// GraphQL helper — fires a paginated plugin table query and calls back with
// the DataTables-compatible response plus the raw GraphQL result object.
function postPluginGraphQL(gqlField, prefix, foreignKey, dtRequest, callback) {
const apiToken = getSetting("API_TOKEN");
const apiBase = getApiBase();
const page = Math.floor(dtRequest.start / dtRequest.length) + 1;
const limit = dtRequest.length;
const search = dtRequest.search?.value || null;
let sort = [];
if (dtRequest.order?.length > 0) {
const order = dtRequest.order[0];
sort.push({ field: dtRequest.columns[order.column].data, order: order.dir });
}
const query = `
query PluginData($options: PluginQueryOptionsInput) {
${gqlField}(options: $options) {
count
dbCount
entries {
index plugin objectPrimaryId objectSecondaryId
dateTimeCreated dateTimeChanged
watchedValue1 watchedValue2 watchedValue3 watchedValue4
status extra userData foreignKey
syncHubNodeName helpVal1 helpVal2 helpVal3 helpVal4 objectGuid
}
}
}
`;
$.ajax({
method: "POST",
url: `${apiBase}/graphql`,
headers: { "Authorization": `Bearer ${apiToken}`, "Content-Type": "application/json" },
data: JSON.stringify({
query,
variables: { options: { page, limit, search, sort, plugin: prefix, foreignKey } }
}),
success: function(response) {
if (response.errors) {
console.error("[plugins] GraphQL errors:", response.errors);
callback({ data: [], recordsTotal: 0, recordsFiltered: 0 });
return;
}
const result = response.data[gqlField];
callback({ data: result.entries, recordsTotal: result.dbCount, recordsFiltered: result.count }, result);
},
error: function() {
callback({ data: [], recordsTotal: 0, recordsFiltered: 0 });
}
});
}
// Fetch counts for all plugins. Returns { PREFIX: { objects, events, history } }
// or null on failure (fail-open so tabs still render).
// Unfiltered: static JSON (~1KB pre-computed).
// MAC-filtered: lightweight REST endpoint (single SQL query).
async function fetchPluginCounts(prefixes) {
if (prefixes.length === 0) return {};
const mac = $("#txtMacFilter").val();
const foreignKey = (mac && mac !== "--") ? mac : null;
try {
let counts = {};
let rows;
if (!foreignKey) {
// ---- FAST PATH: pre-computed static JSON ----
const stats = await fetchJson('table_plugins_stats.json');
rows = stats.data;
} else {
// ---- MAC-FILTERED PATH: single SQL via REST endpoint ----
const apiToken = getSetting("API_TOKEN");
const apiBase = getApiBase();
const response = await $.ajax({
method: "GET",
url: `${apiBase}/plugins/stats?foreignKey=${encodeURIComponent(foreignKey)}`,
headers: { "Authorization": `Bearer ${apiToken}` },
});
if (!response.success) {
console.error("[plugins] /plugins/stats error:", response.error);
return null;
}
rows = response.data;
}
for (const row of rows) {
const p = row.tableName; // 'objects' | 'events' | 'history'
const plugin = row.plugin;
if (!counts[plugin]) counts[plugin] = { objects: 0, events: 0, history: 0 };
counts[plugin][p] = row.cnt;
}
return counts;
} catch (err) {
console.error('[plugins] fetchPluginCounts failed (fail-open):', err);
return null;
}
}
// Apply pre-fetched counts to the DOM badges and hide empty tabs/sub-tabs.
function applyPluginBadges(counts, prefixes) {
// Update DOM badges
for (const [prefix, c] of Object.entries(counts)) {
$(`#badge_${prefix}`).text(c.objects);
$(`#objCount_${prefix}`).text(c.objects);
$(`#evtCount_${prefix}`).text(c.events);
$(`#histCount_${prefix}`).text(c.history);
}
// Zero out plugins with no rows in any table
prefixes.forEach(prefix => {
if (!counts[prefix]) {
$(`#badge_${prefix}`).text(0);
$(`#objCount_${prefix}`).text(0);
$(`#evtCount_${prefix}`).text(0);
$(`#histCount_${prefix}`).text(0);
}
});
// Auto-hide sub-tabs with zero results (outer tabs already excluded during creation)
autoHideEmptyTabs(counts, prefixes);
}
// ---------------------------------------------------------------
// Within visible plugins, hide inner sub-tabs (Objects/Events/History) whose count is 0.
// Outer plugin tabs with zero total are already excluded during tab creation.
function autoHideEmptyTabs(counts, prefixes) {
prefixes.forEach(prefix => {
const c = counts[prefix] || { objects: 0, events: 0, history: 0 };
const $pane = $(`#tabs-content-location > #${prefix}`);
// Hide inner sub-tabs with zero count
const subTabs = [
{ href: `#objectsTarget_${prefix}`, count: c.objects },
{ href: `#eventsTarget_${prefix}`, count: c.events },
{ href: `#historyTarget_${prefix}`, count: c.history },
];
let activeSubHidden = false;
subTabs.forEach(st => {
const $subLi = $pane.find(`ul.nav-tabs li:has(a[href="${st.href}"])`);
const $subPane = $pane.find(st.href);
if (st.count === 0) {
if ($subLi.hasClass('active')) activeSubHidden = true;
$subLi.hide();
$subPane.removeClass('active').css('display', '');
} else {
$subLi.show();
$subPane.css('display', '');
}
});
// If the active inner sub-tab was hidden, activate the first visible one
// via Bootstrap's tab lifecycle so shown.bs.tab fires for deferred DataTable init
if (activeSubHidden) {
const $firstVisibleSubA = $pane.find('ul.nav-tabs li:visible:first a');
if ($firstVisibleSubA.length) {
$firstVisibleSubA.tab('show');
}
}
});
}
function generateTabs() {
@@ -315,18 +475,41 @@ function generateTabs() {
// Sort pluginDefinitions by unique_prefix alphabetically
pluginDefinitions.sort((a, b) => a.unique_prefix.localeCompare(b.unique_prefix));
assignActive = true;
let assignActive = true;
// Iterate over the sorted pluginDefinitions to create tab headers and content
pluginDefinitions.forEach(pluginObj => {
if (pluginObj.show_ui) {
stats = createTabContent(pluginObj, assignActive); // Create the content for each tab
// When counts are available, skip plugins with 0 total count (no flicker).
// When counts are null (fetch failed), show all show_ui plugins (fail-open).
const countsAvailable = pluginCounts !== null;
const visiblePlugins = pluginDefinitions.filter(pluginObj => {
if (!pluginObj.show_ui) return false;
if (!countsAvailable) return true; // fail-open: show all
const c = pluginCounts[pluginObj.unique_prefix] || { objects: 0, events: 0, history: 0 };
return (c.objects + c.events + c.history) > 0;
});
if(stats.objectDataCount > 0)
{
createTabHeader(pluginObj, stats, assignActive); // Create the header for each tab
assignActive = false; // only mark first with content active
}
// Create tab DOM for visible plugins only
visiblePlugins.forEach(pluginObj => {
const prefix = pluginObj.unique_prefix;
const c = countsAvailable ? (pluginCounts[prefix] || { objects: 0, events: 0, history: 0 }) : null;
createTabContent(pluginObj, assignActive, c);
createTabHeader(pluginObj, assignActive, c);
assignActive = false;
});
// Now that ALL DOM elements exist (both <a> headers and tab panes),
// wire up DataTable initialization: immediate for the active tab,
// deferred via shown.bs.tab for the rest.
let firstVisible = true;
visiblePlugins.forEach(pluginObj => {
const prefix = pluginObj.unique_prefix;
const colDefinitions = getColumnDefinitions(pluginObj);
if (firstVisible) {
initializeDataTables(prefix, colDefinitions, pluginObj);
firstVisible = false;
} else {
$(`a[href="#${prefix}"]`).one('shown.bs.tab', function() {
initializeDataTables(prefix, colDefinitions, pluginObj);
});
}
});
@@ -338,6 +521,12 @@ function generateTabs() {
tabContainer: '#tabs-location'
});
// Apply badge counts to the DOM and hide empty inner sub-tabs (only if counts loaded)
if (countsAvailable) {
const prefixes = visiblePlugins.map(p => p.unique_prefix);
applyPluginBadges(pluginCounts, prefixes);
}
hideSpinner()
}
@@ -349,20 +538,18 @@ function resetTabs() {
// ---------------------------------------------------------------
// left headers
function createTabHeader(pluginObj, stats, assignActive) {
const prefix = pluginObj.unique_prefix; // Get the unique prefix for the plugin
function createTabHeader(pluginObj, assignActive, counts) {
const prefix = pluginObj.unique_prefix;
const activeClass = assignActive ? "active" : "";
const badgeText = counts ? counts.objects : '…';
// Determine the active class for the first tab
assignActive ? activeClass = "active" : activeClass = "";
// Append the tab header to the tabs location
$('#tabs-location').append(`
<li class="left-nav ${activeClass} ">
<a class="col-sm-12 textOverflow" href="#${prefix}" data-plugin-prefix="${prefix}" id="${prefix}_id" data-toggle="tab">
${getString(`${prefix}_icon`)} ${getString(`${prefix}_display_name`)}
</a>
${stats.objectDataCount > 0 ? `<div class="pluginBadgeWrap"><span title="" class="badge pluginBadge" >${stats.objectDataCount}</span></div>` : ""}
<div class="pluginBadgeWrap"><span title="" class="badge pluginBadge" id="badge_${prefix}">${badgeText}</span></div>
</li>
`);
@@ -370,23 +557,17 @@ function createTabHeader(pluginObj, stats, assignActive) {
// ---------------------------------------------------------------
// Content of selected plugin (header)
function createTabContent(pluginObj, assignActive) {
const prefix = pluginObj.unique_prefix; // Get the unique prefix for the plugin
const colDefinitions = getColumnDefinitions(pluginObj); // Get column definitions for DataTables
function createTabContent(pluginObj, assignActive, counts) {
const prefix = pluginObj.unique_prefix;
const colDefinitions = getColumnDefinitions(pluginObj);
// Get data for events, objects, and history related to the plugin
const objectData = getObjectData(prefix, colDefinitions, pluginObj);
const eventData = getEventData(prefix, colDefinitions, pluginObj);
const historyData = getHistoryData(prefix, colDefinitions, pluginObj);
// Append the content structure for the plugin's tab to the content location
$('#tabs-content-location').append(`
<div id="${prefix}" class="tab-pane ${objectData.length > 0 && assignActive? 'active' : ''}">
${generateTabNavigation(prefix, objectData.length, eventData.length, historyData.length)} <!-- Create tab navigation -->
<div id="${prefix}" class="tab-pane ${assignActive ? 'active' : ''}">
${generateTabNavigation(prefix, counts)} <!-- Create tab navigation -->
<div class="tab-content">
${generateDataTable(prefix, 'Objects', objectData, colDefinitions)}
${generateDataTable(prefix, 'Events', eventData, colDefinitions)}
${generateDataTable(prefix, 'History', historyData, colDefinitions)}
${generateDataTable(prefix, 'Objects', colDefinitions)}
${generateDataTable(prefix, 'Events', colDefinitions)}
${generateDataTable(prefix, 'History', colDefinitions)}
</div>
<div class='plugins-description'>
${getString(`${prefix}_description`)} <!-- Display the plugin description -->
@@ -395,14 +576,7 @@ function createTabContent(pluginObj, assignActive) {
</div>
`);
// Initialize DataTables for the respective sections
initializeDataTables(prefix, objectData, eventData, historyData, colDefinitions);
return {
"objectDataCount": objectData.length,
"eventDataCount": eventData.length,
"historyDataCount": historyData.length
}
// DataTable init is handled by generateTabs() after all DOM elements exist.
}
function getColumnDefinitions(pluginObj) {
@@ -410,53 +584,29 @@ function getColumnDefinitions(pluginObj) {
return pluginObj["database_column_definitions"].filter(colDef => colDef.show);
}
function getEventData(prefix, colDefinitions, pluginObj) {
// Extract event data specific to the plugin and format it for DataTables
return pluginUnprocessedEvents
.filter(event => event.Plugin === prefix && shouldBeShown(event, pluginObj)) // Filter events for the specific plugin
.map(event => colDefinitions.map(colDef => event[colDef.column] || '')); // Map to the defined columns
}
function generateTabNavigation(prefix, counts) {
const objCount = counts ? counts.objects : '…';
const evtCount = counts ? counts.events : '…';
const histCount = counts ? counts.history : '…';
function getObjectData(prefix, colDefinitions, pluginObj) {
// Extract object data specific to the plugin and format it for DataTables
return pluginObjects
.filter(object => object.Plugin === prefix && shouldBeShown(object, pluginObj)) // Filter objects for the specific plugin
.map(object => colDefinitions.map(colDef => getFormControl(colDef, object[colDef.column], object["Index"], colDefinitions, object))); // Map to the defined columns
}
function getHistoryData(prefix, colDefinitions, pluginObj) {
return pluginHistory
.filter(history => history.Plugin === prefix && shouldBeShown(history, pluginObj)) // First, filter based on the plugin prefix
.sort((a, b) => b.Index - a.Index) // Then, sort by the Index field in descending order
.slice(0, 50) // Limit the result to the first 50 entries
.map(object =>
colDefinitions.map(colDef =>
getFormControl(colDef, object[colDef.column], object["Index"], colDefinitions, object)
)
);
}
function generateTabNavigation(prefix, objectCount, eventCount, historyCount) {
// Create navigation tabs for Objects, Unprocessed Events, and History
return `
<div class="nav-tabs-custom" style="margin-bottom: 0px">
<ul class="nav nav-tabs">
<li class="active">
<a href="#objectsTarget_${prefix}" data-toggle="tab"><i class="fa fa-cube"></i> ${getString('Plugins_Objects')} (${objectCount})</a>
<a href="#objectsTarget_${prefix}" data-toggle="tab"><i class="fa fa-cube"></i> ${getString('Plugins_Objects')} (<span id="objCount_${prefix}">${objCount}</span>)</a>
</li>
<li>
<a href="#eventsTarget_${prefix}" data-toggle="tab"><i class="fa fa-bolt"></i> ${getString('Plugins_Unprocessed_Events')} (${eventCount})</a>
<a href="#eventsTarget_${prefix}" data-toggle="tab"><i class="fa fa-bolt"></i> ${getString('Plugins_Unprocessed_Events')} (<span id="evtCount_${prefix}">${evtCount}</span>)</a>
</li>
<li>
<a href="#historyTarget_${prefix}" data-toggle="tab"><i class="fa fa-clock"></i> ${getString('Plugins_History')} (${historyCount})</a>
<a href="#historyTarget_${prefix}" data-toggle="tab"><i class="fa fa-clock"></i> ${getString('Plugins_History')} (<span id="histCount_${prefix}">${histCount}</span>)</a>
</li>
</ul>
</div>
`;
}
function generateDataTable(prefix, tableType, data, colDefinitions) {
function generateDataTable(prefix, tableType, colDefinitions) {
// Generate HTML for a DataTable and associated buttons for a given table type
const headersHtml = colDefinitions.map(colDef => `<th class="${colDef.css_classes}">${getString(`${prefix}_${colDef.column}_name`)}</th>`).join('');
@@ -473,34 +623,72 @@ function generateDataTable(prefix, tableType, data, colDefinitions) {
`;
}
function initializeDataTables(prefix, objectData, eventData, historyData, colDefinitions) {
// Common settings for DataTables initialization
const commonDataTableSettings = {
orderable: false, // Disable ordering
createdRow: function(row, data) {
$(row).attr('data-my-index', data[0]); // Set data attribute for indexing
function initializeDataTables(prefix, colDefinitions, pluginObj) {
const mac = $("#txtMacFilter").val();
const foreignKey = (mac && mac !== "--") ? mac : null;
const tableConfigs = [
{ tableId: `objectsTable_${prefix}`, gqlField: 'pluginsObjects', countId: `objCount_${prefix}`, badgeId: `badge_${prefix}` },
{ tableId: `eventsTable_${prefix}`, gqlField: 'pluginsEvents', countId: `evtCount_${prefix}`, badgeId: null },
{ tableId: `historyTable_${prefix}`, gqlField: 'pluginsHistory', countId: `histCount_${prefix}`, badgeId: null },
];
function buildDT(tableId, gqlField, countId, badgeId) {
if ($.fn.DataTable.isDataTable(`#${tableId}`)) {
return; // already initialized
}
};
$(`#${tableId}`).DataTable({
processing: true,
serverSide: true,
paging: true,
searching: true,
ordering: false,
pageLength: 25,
lengthMenu: [[10, 25, 50, 100], [10, 25, 50, 100]],
createdRow: function(row, data) {
$(row).attr('data-my-index', data.index);
},
ajax: function(dtRequest, callback) {
postPluginGraphQL(gqlField, prefix, foreignKey, dtRequest, function(dtResponse, result) {
if (result) {
$(`#${countId}`).text(result.count);
if (badgeId) $(`#${badgeId}`).text(result.dbCount);
}
callback(dtResponse);
});
},
columns: colDefinitions.map(colDef => ({
data: colDef.column,
title: getString(`${prefix}_${colDef.column}_name`),
className: colDef.css_classes || '',
createdCell: function(td, cellData, rowData) {
$(td).html(getFormControl(colDef, cellData, rowData.index));
}
}))
});
}
// Initialize DataTable for Objects
$(`#objectsTable_${prefix}`).DataTable({
data: objectData,
columns: colDefinitions.map(colDef => ({ title: getString(`${prefix}_${colDef.column}_name`) })), // Column titles
...commonDataTableSettings // Spread common settings
});
// Initialize the DataTable for whichever inner sub-tab is currently active
// (may not be Objects if autoHideEmptyTabs switched it).
// Defer the remaining sub-tabs until their shown.bs.tab fires.
const [objCfg, evtCfg, histCfg] = tableConfigs;
const allCfgs = [
{ cfg: objCfg, href: `#objectsTarget_${prefix}` },
{ cfg: evtCfg, href: `#eventsTarget_${prefix}` },
{ cfg: histCfg, href: `#historyTarget_${prefix}` },
];
// Initialize DataTable for Unprocessed Events
$(`#eventsTable_${prefix}`).DataTable({
data: eventData,
columns: colDefinitions.map(colDef => ({ title: getString(`${prefix}_${colDef.column}_name`) })), // Column titles
...commonDataTableSettings // Spread common settings
});
// Initialize DataTable for History
$(`#historyTable_${prefix}`).DataTable({
data: historyData,
columns: colDefinitions.map(colDef => ({ title: getString(`${prefix}_${colDef.column}_name`) })), // Column titles
...commonDataTableSettings // Spread common settings
allCfgs.forEach(({ cfg, href }) => {
const $subPane = $(href);
if ($subPane.hasClass('active') && $subPane.is(':visible')) {
// This sub-tab is the currently active one — initialize immediately
buildDT(cfg.tableId, cfg.gqlField, cfg.countId, cfg.badgeId);
} else if ($subPane.closest('.tab-pane').length) {
// Defer until shown
$(`a[href="${href}"]`).one('shown.bs.tab', function() {
buildDT(cfg.tableId, cfg.gqlField, cfg.countId, cfg.badgeId);
});
}
});
}

View File

@@ -43,9 +43,9 @@
<?= lang('report_select_format') ;?>
</label>
<select id="formatSelect" class="pointer">
<option value="HTML">HTML</option>
<option value="JSON">JSON</option>
<option value="Text">Text</option>
<option value="html">HTML</option>
<option value="json">JSON</option>
<option value="text">Text</option>
</select>
</div>
@@ -100,23 +100,23 @@
// Display the selected format data and update timestamp
switch (format) {
case 'HTML':
case 'html':
notificationData.innerHTML = formatData;
break;
case 'JSON':
case 'json':
notificationData.innerHTML = `<pre class="logs" cols="70" rows="10" wrap="off" readonly="">
${jsonSyntaxHighlight(JSON.stringify(JSON.parse(formatData), undefined, 4))}
</pre>`;
break;
case 'Text':
notificationData.innerHTML = `<pre class="logs" cols="70" rows="10" wrap="off" readonly">${formatData}</pre>`;
case 'text':
notificationData.innerHTML = `<pre class="logs" cols="70" rows="10" wrap="off" readonly="">${formatData}</pre>`;
break;
}
// console.log(notification)
timestamp.textContent = localizeTimestamp(notification.DateTimeCreated);
notiGuid.textContent = notification.GUID;
timestamp.textContent = localizeTimestamp(notification.dateTimeCreated);
notiGuid.textContent = notification.guid;
currentIndex = index;
$("#notificationOutOff").html(`${currentIndex + 1}/${data.data.length}`);
@@ -131,7 +131,7 @@
// Function to find the index of a notification by GUID
function findIndexByGUID(data, guid) {
return data.findIndex(notification => notification.GUID == guid);
return data.findIndex(notification => notification.guid == guid);
}
// Listen for format selection changes
@@ -174,7 +174,7 @@
} else {
// Initial data load
updateData('HTML', -1); // Default format to HTML and load the latest report
updateData('html', -1); // Default format to HTML and load the latest report
}
});

View File

@@ -42,11 +42,11 @@
"title": "🔴 Down devices",
"columnNames": [
"devName",
"eve_MAC",
"eveMac",
"devVendor",
"eve_IP",
"eve_DateTime",
"eve_EventType"
"eveIp",
"eveDateTime",
"eveEventType"
]
},
"down_devices": [],
@@ -64,22 +64,22 @@
"down_reconnected": [
{
"devName": "Phone - Moto 82",
"eve_MAC": "74:ac:74:ac:74:ac",
"eveMac": "74:ac:74:ac:74:ac",
"devVendor": "Motorola Mobility LLC, a Lenovo Company",
"eve_IP": "192.168.1.167",
"eve_DateTime": "2025-01-11 10:05:01+11:00",
"eve_EventType": "Down Reconnected"
"eveIp": "192.168.1.167",
"eveDateTime": "2025-01-11 10:05:01+11:00",
"eveEventType": "Down Reconnected"
}
],
"down_reconnected_meta": {
"title": "🔁 Reconnected down devices",
"columnNames": [
"devName",
"eve_MAC",
"eveMac",
"devVendor",
"eve_IP",
"eve_DateTime",
"eve_EventType"
"eveIp",
"eveDateTime",
"eveEventType"
]
},
"events": [
@@ -103,28 +103,28 @@
"plugins_meta": {
"title": "🔌 Plugins",
"columnNames": [
"Plugin",
"Object_PrimaryID",
"Object_SecondaryID",
"DateTimeChanged",
"Watched_Value1",
"Watched_Value2",
"Watched_Value3",
"Watched_Value4",
"Status"
"plugin",
"objectPrimaryId",
"objectSecondaryId",
"dateTimeChanged",
"watchedValue1",
"watchedValue2",
"watchedValue3",
"watchedValue4",
"status"
]
},
"plugins": [
{
"Plugin": "ARPSCAN",
"Object_PrimaryID": "74:ac:74:ac:74:ac",
"Object_SecondaryID": "192.168.1.114",
"DateTimeChanged": "2025-01-11 12:21:00",
"Watched_Value1": "192.168.1.114",
"Watched_Value2": "Microsoft Corporation",
"Watched_Value3": "192.168.1.0/24 --interface=eth1",
"Watched_Value4": "",
"Status": "new"
"plugin": "ARPSCAN",
"objectPrimaryId": "74:ac:74:ac:74:ac",
"objectSecondaryId": "192.168.1.114",
"dateTimeChanged": "2025-01-11 12:21:00",
"watchedValue1": "192.168.1.114",
"watchedValue2": "Microsoft Corporation",
"watchedValue3": "192.168.1.0/24 --interface=eth1",
"watchedValue4": "",
"status": "new"
}
]
}

View File

@@ -53,6 +53,7 @@ nav:
- Advanced guides:
- Remote Networks: REMOTE_NETWORKS.md
- Notifications Guide: NOTIFICATIONS.md
- Notification Text Templates: NOTIFICATION_TEMPLATES.md
- Custom PUID/GUID: PUID_PGID_SECURITY.md
- Name Resolution: NAME_RESOLUTION.md
- Authelia: AUTHELIA.md

View File

@@ -34,12 +34,12 @@ def check_and_clean_device():
# Check all tables for MAC
tables_checks = [
f"SELECT 'Events' as source, * FROM Events WHERE eve_MAC='{mac}'",
f"SELECT 'Devices' as source, * FROM Devices WHERE dev_MAC='{mac}'",
f"SELECT 'Events' as source, * FROM Events WHERE eveMac='{mac}'",
f"SELECT 'Devices' as source, * FROM Devices WHERE devMac='{mac}'",
f"SELECT 'CurrentScan' as source, * FROM CurrentScan WHERE scanMac='{mac}'",
f"SELECT 'Notifications' as source, * FROM Notifications WHERE JSON LIKE '%{mac}%'",
f"SELECT 'AppEvents' as source, * FROM AppEvents WHERE ObjectPrimaryID LIKE '%{mac}%' OR ObjectSecondaryID LIKE '%{mac}%'",
f"SELECT 'Plugins_Objects' as source, * FROM Plugins_Objects WHERE Object_PrimaryID LIKE '%{mac}%'"
f"SELECT 'AppEvents' as source, * FROM AppEvents WHERE objectPrimaryId LIKE '%{mac}%' OR objectSecondaryId LIKE '%{mac}%'",
f"SELECT 'Plugins_Objects' as source, * FROM Plugins_Objects WHERE objectPrimaryId LIKE '%{mac}%'"
]
found = False
@@ -54,12 +54,12 @@ def check_and_clean_device():
if confirm.lower() == 'y':
# Delete from all tables
deletes = [
f"DELETE FROM Events WHERE eve_MAC='{mac}'",
f"DELETE FROM Devices WHERE dev_MAC='{mac}'",
f"DELETE FROM Events WHERE eveMac='{mac}'",
f"DELETE FROM Devices WHERE devMac='{mac}'",
f"DELETE FROM CurrentScan WHERE scanMac='{mac}'",
f"DELETE FROM Notifications WHERE JSON LIKE '%{mac}%'",
f"DELETE FROM AppEvents WHERE ObjectPrimaryID LIKE '%{mac}%' OR ObjectSecondaryID LIKE '%{mac}%'",
f"DELETE FROM Plugins_Objects WHERE Object_PrimaryID LIKE '%{mac}%'"
f"DELETE FROM AppEvents WHERE objectPrimaryId LIKE '%{mac}%' OR objectSecondaryId LIKE '%{mac}%'",
f"DELETE FROM Plugins_Objects WHERE objectPrimaryId LIKE '%{mac}%'"
]
for delete in deletes:
@@ -73,12 +73,12 @@ def check_and_clean_device():
# Check all tables for IP
tables_checks = [
f"SELECT 'Events' as source, * FROM Events WHERE eve_IP='{ip}'",
f"SELECT 'Devices' as source, * FROM Devices WHERE dev_LastIP='{ip}'",
f"SELECT 'Events' as source, * FROM Events WHERE eveIp='{ip}'",
f"SELECT 'Devices' as source, * FROM Devices WHERE devLastIp='{ip}'",
f"SELECT 'CurrentScan' as source, * FROM CurrentScan WHERE scanLastIP='{ip}'",
f"SELECT 'Notifications' as source, * FROM Notifications WHERE JSON LIKE '%{ip}%'",
f"SELECT 'AppEvents' as source, * FROM AppEvents WHERE ObjectSecondaryID LIKE '%{ip}%'",
f"SELECT 'Plugins_Objects' as source, * FROM Plugins_Objects WHERE Object_SecondaryID LIKE '%{ip}%'"
f"SELECT 'AppEvents' as source, * FROM AppEvents WHERE objectSecondaryId LIKE '%{ip}%'",
f"SELECT 'Plugins_Objects' as source, * FROM Plugins_Objects WHERE objectSecondaryId LIKE '%{ip}%'"
]
found = False
@@ -93,12 +93,12 @@ def check_and_clean_device():
if confirm.lower() == 'y':
# Delete from all tables
deletes = [
f"DELETE FROM Events WHERE eve_IP='{ip}'",
f"DELETE FROM Devices WHERE dev_LastIP='{ip}'",
f"DELETE FROM Events WHERE eveIp='{ip}'",
f"DELETE FROM Devices WHERE devLastIp='{ip}'",
f"DELETE FROM CurrentScan WHERE scanLastIP='{ip}'",
f"DELETE FROM Notifications WHERE JSON LIKE '%{ip}%'",
f"DELETE FROM AppEvents WHERE ObjectSecondaryID LIKE '%{ip}%'",
f"DELETE FROM Plugins_Objects WHERE Object_SecondaryID LIKE '%{ip}%'"
f"DELETE FROM AppEvents WHERE objectSecondaryId LIKE '%{ip}%'",
f"DELETE FROM Plugins_Objects WHERE objectSecondaryId LIKE '%{ip}%'"
]
for delete in deletes:

View File

@@ -10,17 +10,19 @@ from const import (
apiPath,
sql_appevents,
sql_devices_all,
sql_events_all,
sql_events_pending_alert,
sql_settings,
sql_plugins_events,
sql_plugins_history,
sql_plugins_objects,
sql_plugins_stats,
sql_language_strings,
sql_notifications_all,
sql_online_history,
sql_devices_tiles,
sql_devices_filters,
)
from db.db_helper import get_sql_devices_tiles
from logger import mylog
from helper import write_file, get_setting_value
from utils.datetime_utils import timeNowUTC
@@ -59,19 +61,28 @@ def update_api(
dataSourcesSQLs = [
["appevents", sql_appevents],
["devices", sql_devices_all],
["events", sql_events_all],
["events_pending_alert", sql_events_pending_alert],
["settings", sql_settings],
["plugins_events", sql_plugins_events],
["plugins_history", sql_plugins_history],
["plugins_objects", sql_plugins_objects],
["plugins_stats", sql_plugins_stats],
["plugins_language_strings", sql_language_strings],
["notifications", sql_notifications_all],
["online_history", sql_online_history],
["devices_tiles", sql_devices_tiles],
["devices_tiles", get_sql_devices_tiles()],
["devices_filters", sql_devices_filters],
["custom_endpoint", conf.API_CUSTOM_SQL],
]
# plugins_stats is derived from plugins_objects/events/history —
# ensure it is refreshed when any of its sources are partially updated.
_STATS_SOURCES = {"plugins_objects", "plugins_events", "plugins_history"}
if updateOnlyDataSources and _STATS_SOURCES & set(updateOnlyDataSources):
if "plugins_stats" not in updateOnlyDataSources:
updateOnlyDataSources = list(updateOnlyDataSources) + ["plugins_stats"]
# Save selected database tables
for dsSQL in dataSourcesSQLs:
if not updateOnlyDataSources or dsSQL[0] in updateOnlyDataSources:

View File

@@ -43,6 +43,7 @@ from .sync_endpoint import handle_sync_post, handle_sync_get # noqa: E402 [flak
from .logs_endpoint import clean_log # noqa: E402 [flake8 lint suppression]
from .health_endpoint import get_health_status # noqa: E402 [flake8 lint suppression]
from .languages_endpoint import get_languages # noqa: E402 [flake8 lint suppression]
from models.plugin_object_instance import PluginObjectInstance # noqa: E402 [flake8 lint suppression]
from models.user_events_queue_instance import UserEventsQueueInstance # noqa: E402 [flake8 lint suppression]
from models.event_instance import EventInstance # noqa: E402 [flake8 lint suppression]
@@ -97,6 +98,7 @@ from .openapi.schemas import ( # noqa: E402 [flake8 lint suppression]
AddToQueueRequest, GetSettingResponse,
RecentEventsRequest, SetDeviceAliasRequest,
LanguagesResponse,
PluginStatsResponse,
)
from .sse_endpoint import ( # noqa: E402 [flake8 lint suppression]
@@ -1750,8 +1752,13 @@ def api_device_sessions(mac, payload=None):
summary="Get Session Events",
description="Retrieve events associated with sessions.",
query_params=[
{"name": "type", "description": "Event type", "required": False, "schema": {"type": "string", "default": "all"}},
{"name": "period", "description": "Time period", "required": False, "schema": {"type": "string", "default": "7 days"}}
{"name": "type", "description": "Event type", "required": False, "schema": {"type": "string", "default": "all"}},
{"name": "period", "description": "Time period", "required": False, "schema": {"type": "string", "default": "7 days"}},
{"name": "page", "description": "Page number (1-based)", "required": False, "schema": {"type": "integer", "default": 1}},
{"name": "limit", "description": "Rows per page (max 1000)", "required": False, "schema": {"type": "integer", "default": 100}},
{"name": "search", "description": "Free-text search filter", "required": False, "schema": {"type": "string"}},
{"name": "sortCol", "description": "Column index to sort by (0-based)", "required": False, "schema": {"type": "integer", "default": 0}},
{"name": "sortDir", "description": "Sort direction: asc or desc", "required": False, "schema": {"type": "string", "default": "desc"}}
],
tags=["sessions"],
auth_callable=is_authorized
@@ -1759,7 +1766,12 @@ def api_device_sessions(mac, payload=None):
def api_get_session_events(payload=None):
session_event_type = request.args.get("type", "all")
period = get_date_from_period(request.args.get("period", "7 days"))
return get_session_events(session_event_type, period)
page = request.args.get("page", 1, type=int)
limit = request.args.get("limit", 100, type=int)
search = request.args.get("search", None)
sort_col = request.args.get("sortCol", 0, type=int)
sort_dir = request.args.get("sortDir", "desc")
return get_session_events(session_event_type, period, page=page, limit=limit, search=search, sort_col=sort_col, sort_dir=sort_dir)
# --------------------------
@@ -1992,6 +2004,33 @@ def list_languages(payload=None):
}), 500
# --------------------------
# Plugin Stats endpoint
# --------------------------
@app.route("/plugins/stats", methods=["GET"])
@validate_request(
operation_id="get_plugin_stats",
summary="Get Plugin Row Counts",
description="Return per-plugin row counts across Objects, Events, and History tables. Optionally filter by foreignKey (MAC).",
response_model=PluginStatsResponse,
tags=["plugins"],
auth_callable=is_authorized,
query_params=[{
"name": "foreignKey",
"in": "query",
"required": False,
"description": "Filter counts to rows matching this foreignKey (typically a MAC address)",
"schema": {"type": "string"}
}]
)
def api_plugin_stats(payload=None):
"""Get per-plugin row counts, optionally filtered by foreignKey."""
foreign_key = request.args.get("foreignKey", None)
handler = PluginObjectInstance()
data = handler.getStats(foreign_key)
return jsonify({"success": True, "data": data})
# --------------------------
# Background Server Start
# --------------------------

View File

@@ -1,7 +1,5 @@
import graphene
from graphene import (
ObjectType, String, Int, Boolean, List, Field, InputObjectType, Argument
)
from graphene import ObjectType, List, Field, Argument, String
import json
import sys
import os
@@ -19,175 +17,30 @@ from helper import ( # noqa: E402 [flake8 lint suppression]
get_setting_value,
)
# Define a base URL with the user's home directory
from .graphql_types import ( # noqa: E402 [flake8 lint suppression]
FilterOptionsInput, PageQueryOptionsInput,
Device, DeviceResult,
Setting, SettingResult,
LangString, LangStringResult,
AppEvent, AppEventResult,
PluginQueryOptionsInput, PluginEntry,
PluginsObjectsResult, PluginsEventsResult, PluginsHistoryResult,
EventQueryOptionsInput, EventEntry, EventsResult,
)
from .graphql_helpers import ( # noqa: E402 [flake8 lint suppression]
mixed_type_sort_key,
apply_common_pagination,
apply_plugin_filters,
apply_events_filters,
)
folder = apiPath
# --- DEVICES ---
# Pagination and Sorting Input Types
class SortOptionsInput(InputObjectType):
field = String()
order = String()
class FilterOptionsInput(InputObjectType):
filterColumn = String()
filterValue = String()
class PageQueryOptionsInput(InputObjectType):
page = Int()
limit = Int()
sort = List(SortOptionsInput)
search = String()
status = String()
filters = List(FilterOptionsInput)
# Device ObjectType
class Device(ObjectType):
rowid = Int(description="Database row ID")
devMac = String(description="Device MAC address (e.g., 00:11:22:33:44:55)")
devName = String(description="Device display name/alias")
devOwner = String(description="Device owner")
devType = String(description="Device type classification")
devVendor = String(description="Hardware vendor from OUI lookup")
devFavorite = Int(description="Favorite flag (0 or 1)")
devGroup = String(description="Device group")
devComments = String(description="User comments")
devFirstConnection = String(description="Timestamp of first discovery")
devLastConnection = String(description="Timestamp of last connection")
devLastIP = String(description="Last known IP address")
devPrimaryIPv4 = String(description="Primary IPv4 address")
devPrimaryIPv6 = String(description="Primary IPv6 address")
devVlan = String(description="VLAN identifier")
devForceStatus = String(description="Force device status (online/offline/dont_force)")
devStaticIP = Int(description="Static IP flag (0 or 1)")
devScan = Int(description="Scan flag (0 or 1)")
devLogEvents = Int(description="Log events flag (0 or 1)")
devAlertEvents = Int(description="Alert events flag (0 or 1)")
devAlertDown = Int(description="Alert on down flag (0 or 1)")
devSkipRepeated = Int(description="Skip repeated alerts flag (0 or 1)")
devLastNotification = String(description="Timestamp of last notification")
devPresentLastScan = Int(description="Present in last scan flag (0 or 1)")
devIsNew = Int(description="Is new device flag (0 or 1)")
devLocation = String(description="Device location")
devIsArchived = Int(description="Is archived flag (0 or 1)")
devParentMAC = String(description="Parent device MAC address")
devParentPort = String(description="Parent device port")
devIcon = String(description="Base64-encoded HTML/SVG markup used to render the device icon")
devGUID = String(description="Unique device GUID")
devSite = String(description="Site name")
devSSID = String(description="SSID connected to")
devSyncHubNode = String(description="Sync hub node name")
devSourcePlugin = String(description="Plugin that discovered the device")
devCustomProps = String(description="Base64-encoded custom properties in JSON format")
devStatus = String(description="Online/Offline status")
devIsRandomMac = Int(description="Calculated: Is MAC address randomized?")
devParentChildrenCount = Int(description="Calculated: Number of children attached to this parent")
devIpLong = String(description="Calculated: IP address in long format (returned as string to support the full unsigned 32-bit range)")
devFilterStatus = String(description="Calculated: Device status for UI filtering")
devFQDN = String(description="Fully Qualified Domain Name")
devParentRelType = String(description="Relationship type to parent")
devReqNicsOnline = Int(description="Required NICs online flag")
devMacSource = String(description="Source tracking for devMac (USER, LOCKED, NEWDEV, or plugin prefix)")
devNameSource = String(description="Source tracking for devName (USER, LOCKED, NEWDEV, or plugin prefix)")
devFQDNSource = String(description="Source tracking for devFQDN (USER, LOCKED, NEWDEV, or plugin prefix)")
devLastIPSource = String(description="Source tracking for devLastIP (USER, LOCKED, NEWDEV, or plugin prefix)")
devVendorSource = String(description="Source tracking for devVendor (USER, LOCKED, NEWDEV, or plugin prefix)")
devSSIDSource = String(description="Source tracking for devSSID (USER, LOCKED, NEWDEV, or plugin prefix)")
devParentMACSource = String(description="Source tracking for devParentMAC (USER, LOCKED, NEWDEV, or plugin prefix)")
devParentPortSource = String(description="Source tracking for devParentPort (USER, LOCKED, NEWDEV, or plugin prefix)")
devParentRelTypeSource = String(description="Source tracking for devParentRelType (USER, LOCKED, NEWDEV, or plugin prefix)")
devVlanSource = String(description="Source tracking for devVlan")
devFlapping = Int(description="Indicates flapping device (device changing between online/offline states frequently)")
devCanSleep = Int(description="Can this device sleep? (0 or 1). When enabled, offline periods within NTFPRCS_sleep_time are reported as Sleeping instead of Down.")
devIsSleeping = Int(description="Computed: Is device currently in a sleep window? (0 or 1)")
class DeviceResult(ObjectType):
devices = List(Device)
count = Int()
db_count = Int(description="Total device count in the database, before any status/filter/search is applied")
# --- SETTINGS ---
# Setting ObjectType
class Setting(ObjectType):
setKey = String(description="Unique configuration key")
setName = String(description="Human-readable setting name")
setDescription = String(description="Detailed description of the setting")
setType = String(description="Config-driven type definition used to determine value type and UI rendering")
setOptions = String(description="JSON string of available options")
setGroup = String(description="UI group for categorization")
setValue = String(description="Current value")
setEvents = String(description="JSON string of events")
setOverriddenByEnv = Boolean(description="Whether the value is currently overridden by an environment variable")
class SettingResult(ObjectType):
settings = List(Setting, description="List of setting objects")
count = Int(description="Total count of settings")
# --- LANGSTRINGS ---
# In-memory cache for lang strings
_langstrings_cache = {} # caches lists per file (core JSON or plugin)
_langstrings_cache_mtime = {} # tracks last modified times
# LangString ObjectType
class LangString(ObjectType):
langCode = String(description="Language code (e.g., en_us, de_de)")
langStringKey = String(description="Unique translation key")
langStringText = String(description="Translated text content")
class LangStringResult(ObjectType):
langStrings = List(LangString, description="List of language string objects")
count = Int(description="Total count of strings")
# --- APP EVENTS ---
class AppEvent(ObjectType):
Index = Int(description="Internal index")
GUID = String(description="Unique event GUID")
AppEventProcessed = Int(description="Processing status (0 or 1)")
DateTimeCreated = String(description="Event creation timestamp")
ObjectType = String(description="Type of the related object (Device, Setting, etc.)")
ObjectGUID = String(description="GUID of the related object")
ObjectPlugin = String(description="Plugin associated with the object")
ObjectPrimaryID = String(description="Primary identifier of the object")
ObjectSecondaryID = String(description="Secondary identifier of the object")
ObjectForeignKey = String(description="Foreign key reference")
ObjectIndex = Int(description="Object index")
ObjectIsNew = Int(description="Is the object new? (0 or 1)")
ObjectIsArchived = Int(description="Is the object archived? (0 or 1)")
ObjectStatusColumn = String(description="Column used for status")
ObjectStatus = String(description="Object status value")
AppEventType = String(description="Type of application event")
Helper1 = String(description="Generic helper field 1")
Helper2 = String(description="Generic helper field 2")
Helper3 = String(description="Generic helper field 3")
Extra = String(description="Additional JSON data")
class AppEventResult(ObjectType):
appEvents = List(AppEvent, description="List of application events")
count = Int(description="Total count of events")
# ----------------------------------------------------------------------------------------------
# Define Query Type with Pagination Support
class Query(ObjectType):
# --- DEVICES ---
devices = Field(DeviceResult, options=PageQueryOptionsInput())
@@ -499,18 +352,18 @@ class Query(ObjectType):
search_term = options.search.lower()
searchable_fields = [
"GUID",
"ObjectType",
"ObjectGUID",
"ObjectPlugin",
"ObjectPrimaryID",
"ObjectSecondaryID",
"ObjectStatus",
"AppEventType",
"Helper1",
"Helper2",
"Helper3",
"Extra",
"guid",
"objectType",
"objectGuid",
"objectPlugin",
"objectPrimaryId",
"objectSecondaryId",
"objectStatus",
"appEventType",
"helper1",
"helper2",
"helper3",
"extra",
]
events_data = [
@@ -616,9 +469,9 @@ class Query(ObjectType):
plugin_data = json.load(f).get("data", [])
plugin_list = [
LangString(
langCode=entry.get("Language_Code"),
langStringKey=entry.get("String_Key"),
langStringText=entry.get("String_Value")
langCode=entry.get("languageCode"),
langStringKey=entry.get("stringKey"),
langStringText=entry.get("stringValue")
) for entry in plugin_data
]
_langstrings_cache[cache_key] = plugin_list
@@ -652,15 +505,75 @@ class Query(ObjectType):
return LangStringResult(langStrings=langStrings, count=len(langStrings))
# --- PLUGINS_OBJECTS ---
pluginsObjects = Field(PluginsObjectsResult, options=PluginQueryOptionsInput())
# helps sorting inconsistent dataset mixed integers and strings
def mixed_type_sort_key(value):
if value is None or value == "":
return (2, "") # Place None or empty strings last
def resolve_pluginsObjects(self, info, options=None):
return _resolve_plugin_table("table_plugins_objects.json", options, PluginsObjectsResult)
# --- PLUGINS_EVENTS ---
pluginsEvents = Field(PluginsEventsResult, options=PluginQueryOptionsInput())
def resolve_pluginsEvents(self, info, options=None):
return _resolve_plugin_table("table_plugins_events.json", options, PluginsEventsResult)
# --- PLUGINS_HISTORY ---
pluginsHistory = Field(PluginsHistoryResult, options=PluginQueryOptionsInput())
def resolve_pluginsHistory(self, info, options=None):
return _resolve_plugin_table("table_plugins_history.json", options, PluginsHistoryResult)
# --- EVENTS ---
events = Field(EventsResult, options=EventQueryOptionsInput())
def resolve_events(self, info, options=None):
try:
with open(folder + "table_events.json", "r") as f:
data = json.load(f).get("data", [])
except (FileNotFoundError, json.JSONDecodeError) as e:
mylog("none", f"[graphql_schema] Error loading events data: {e}")
return EventsResult(entries=[], count=0, db_count=0)
db_count = len(data)
data = apply_events_filters(data, options)
data, total_count = apply_common_pagination(data, options)
return EventsResult(
entries=[EventEntry(**r) for r in data],
count=total_count,
db_count=db_count,
)
# ---------------------------------------------------------------------------
# Private resolver helper — shared by all three plugin table resolvers
# ---------------------------------------------------------------------------
def _resolve_plugin_table(json_file, options, ResultType):
try:
return (0, int(value)) # Integers get priority
except (ValueError, TypeError):
return (1, str(value)) # Strings come next
with open(folder + json_file, "r") as f:
data = json.load(f).get("data", [])
except (FileNotFoundError, json.JSONDecodeError) as e:
mylog("none", f"[graphql_schema] Error loading {json_file}: {e}")
return ResultType(entries=[], count=0, db_count=0)
# Scope to the requested plugin + foreignKey FIRST so db_count
# reflects the total for THIS plugin, not the entire table.
if options:
if options.plugin:
pl = options.plugin.lower()
data = [r for r in data if str(r.get("plugin", "")).lower() == pl]
if options.foreignKey:
fk = options.foreignKey.lower()
data = [r for r in data if str(r.get("foreignKey", "")).lower() == fk]
db_count = len(data)
data = apply_plugin_filters(data, options)
data, total_count = apply_common_pagination(data, options)
return ResultType(
entries=[PluginEntry(**r) for r in data],
count=total_count,
db_count=db_count,
)
# Schema Definition

View File

@@ -0,0 +1,141 @@
"""
graphql_helpers.py — Shared utility functions for GraphQL resolvers.
"""
_MAX_LIMIT = 1000
_DEFAULT_LIMIT = 100
def mixed_type_sort_key(value):
"""Sort key that handles mixed int/string datasets without crashing.
Ordering priority:
0 — integers (sorted numerically)
1 — strings (sorted lexicographically)
2 — None / empty string (always last)
"""
if value is None or value == "":
return (2, "")
try:
return (0, int(value))
except (ValueError, TypeError):
return (1, str(value))
def apply_common_pagination(data, options):
"""Apply sort + capture total_count + paginate.
Returns (paged_data, total_count).
Enforces a hard limit cap of _MAX_LIMIT — never returns unbounded results.
"""
if not options:
return data, len(data)
# --- SORT ---
if options.sort:
for sort_option in reversed(options.sort):
field = sort_option.field
reverse = (sort_option.order or "asc").lower() == "desc"
data = sorted(
data,
key=lambda x: mixed_type_sort_key(x.get(field)),
reverse=reverse,
)
total_count = len(data)
# --- PAGINATE ---
if options.page is not None and options.limit is not None:
effective_limit = min(options.limit, _MAX_LIMIT)
page = max(1, options.page)
start = (page - 1) * effective_limit
end = start + effective_limit
data = data[start:end]
return data, total_count
def apply_plugin_filters(data, options):
"""Filter a list of plugin table rows (Plugins_Objects/Events/History).
Handles: date range, column filters, free-text search.
NOTE: plugin prefix and foreignKey scoping is done in the resolver
BEFORE db_count is captured — do NOT duplicate here.
"""
if not options:
return data
# Date-range filter on dateTimeCreated
if options.dateFrom:
data = [r for r in data if str(r.get("dateTimeCreated", "")) >= options.dateFrom]
if options.dateTo:
data = [r for r in data if str(r.get("dateTimeCreated", "")) <= options.dateTo]
# Column-value exact-match filters
if options.filters:
for f in options.filters:
if f.filterColumn and f.filterValue is not None:
data = [
r for r in data
if str(r.get(f.filterColumn, "")).lower() == str(f.filterValue).lower()
]
# Free-text search
if options.search:
term = options.search.lower()
searchable = [
"plugin", "objectPrimaryId", "objectSecondaryId",
"watchedValue1", "watchedValue2", "watchedValue3", "watchedValue4",
"status", "extra", "foreignKey", "objectGuid", "userData",
]
data = [
r for r in data
if any(term in str(r.get(field, "")).lower() for field in searchable)
]
return data
def apply_events_filters(data, options):
"""Filter a list of Events table rows.
Handles: eveMac, eventType, date range, column filters, free-text search.
"""
if not options:
return data
# MAC filter
if options.eveMac:
mac = options.eveMac.lower()
data = [r for r in data if str(r.get("eveMac", "")).lower() == mac]
# Event-type filter
if options.eventType:
et = options.eventType.lower()
data = [r for r in data if str(r.get("eveEventType", "")).lower() == et]
# Date-range filter on eveDateTime
if options.dateFrom:
data = [r for r in data if str(r.get("eveDateTime", "")) >= options.dateFrom]
if options.dateTo:
data = [r for r in data if str(r.get("eveDateTime", "")) <= options.dateTo]
# Column-value exact-match filters
if options.filters:
for f in options.filters:
if f.filterColumn and f.filterValue is not None:
data = [
r for r in data
if str(r.get(f.filterColumn, "")).lower() == str(f.filterValue).lower()
]
# Free-text search
if options.search:
term = options.search.lower()
searchable = ["eveMac", "eveIp", "eveEventType", "eveAdditionalInfo"]
data = [
r for r in data
if any(term in str(r.get(field, "")).lower() for field in searchable)
]
return data

View File

@@ -0,0 +1,261 @@
import graphene # noqa: F401 (re-exported for schema creation in graphql_endpoint.py)
from graphene import (
ObjectType, String, Int, Boolean, List, InputObjectType,
)
# ---------------------------------------------------------------------------
# Shared Input Types
# ---------------------------------------------------------------------------
class SortOptionsInput(InputObjectType):
field = String()
order = String()
class FilterOptionsInput(InputObjectType):
filterColumn = String()
filterValue = String()
class PageQueryOptionsInput(InputObjectType):
page = Int()
limit = Int()
sort = List(SortOptionsInput)
search = String()
status = String()
filters = List(FilterOptionsInput)
# ---------------------------------------------------------------------------
# Devices
# ---------------------------------------------------------------------------
class Device(ObjectType):
rowid = Int(description="Database row ID")
devMac = String(description="Device MAC address (e.g., 00:11:22:33:44:55)")
devName = String(description="Device display name/alias")
devOwner = String(description="Device owner")
devType = String(description="Device type classification")
devVendor = String(description="Hardware vendor from OUI lookup")
devFavorite = Int(description="Favorite flag (0 or 1)")
devGroup = String(description="Device group")
devComments = String(description="User comments")
devFirstConnection = String(description="Timestamp of first discovery")
devLastConnection = String(description="Timestamp of last connection")
devLastIP = String(description="Last known IP address")
devPrimaryIPv4 = String(description="Primary IPv4 address")
devPrimaryIPv6 = String(description="Primary IPv6 address")
devVlan = String(description="VLAN identifier")
devForceStatus = String(description="Force device status (online/offline/dont_force)")
devStaticIP = Int(description="Static IP flag (0 or 1)")
devScan = Int(description="Scan flag (0 or 1)")
devLogEvents = Int(description="Log events flag (0 or 1)")
devAlertEvents = Int(description="Alert events flag (0 or 1)")
devAlertDown = Int(description="Alert on down flag (0 or 1)")
devSkipRepeated = Int(description="Skip repeated alerts flag (0 or 1)")
devLastNotification = String(description="Timestamp of last notification")
devPresentLastScan = Int(description="Present in last scan flag (0 or 1)")
devIsNew = Int(description="Is new device flag (0 or 1)")
devLocation = String(description="Device location")
devIsArchived = Int(description="Is archived flag (0 or 1)")
devParentMAC = String(description="Parent device MAC address")
devParentPort = String(description="Parent device port")
devIcon = String(description="Base64-encoded HTML/SVG markup used to render the device icon")
devGUID = String(description="Unique device GUID")
devSite = String(description="Site name")
devSSID = String(description="SSID connected to")
devSyncHubNode = String(description="Sync hub node name")
devSourcePlugin = String(description="Plugin that discovered the device")
devCustomProps = String(description="Base64-encoded custom properties in JSON format")
devStatus = String(description="Online/Offline status")
devIsRandomMac = Int(description="Calculated: Is MAC address randomized?")
devParentChildrenCount = Int(description="Calculated: Number of children attached to this parent")
devIpLong = String(description="Calculated: IP address in long format (returned as string to support the full unsigned 32-bit range)")
devFilterStatus = String(description="Calculated: Device status for UI filtering")
devFQDN = String(description="Fully Qualified Domain Name")
devParentRelType = String(description="Relationship type to parent")
devReqNicsOnline = Int(description="Required NICs online flag")
devMacSource = String(description="Source tracking for devMac (USER, LOCKED, NEWDEV, or plugin prefix)")
devNameSource = String(description="Source tracking for devName (USER, LOCKED, NEWDEV, or plugin prefix)")
devFQDNSource = String(description="Source tracking for devFQDN (USER, LOCKED, NEWDEV, or plugin prefix)")
devLastIPSource = String(description="Source tracking for devLastIP (USER, LOCKED, NEWDEV, or plugin prefix)")
devVendorSource = String(description="Source tracking for devVendor (USER, LOCKED, NEWDEV, or plugin prefix)")
devSSIDSource = String(description="Source tracking for devSSID (USER, LOCKED, NEWDEV, or plugin prefix)")
devParentMACSource = String(description="Source tracking for devParentMAC (USER, LOCKED, NEWDEV, or plugin prefix)")
devParentPortSource = String(description="Source tracking for devParentPort (USER, LOCKED, NEWDEV, or plugin prefix)")
devParentRelTypeSource = String(description="Source tracking for devParentRelType (USER, LOCKED, NEWDEV, or plugin prefix)")
devVlanSource = String(description="Source tracking for devVlan")
devFlapping = Int(description="Indicates flapping device (device changing between online/offline states frequently)")
devCanSleep = Int(description="Can this device sleep? (0 or 1). When enabled, offline periods within NTFPRCS_sleep_time are reported as Sleeping instead of Down.")
devIsSleeping = Int(description="Computed: Is device currently in a sleep window? (0 or 1)")
class DeviceResult(ObjectType):
devices = List(Device)
count = Int()
db_count = Int(description="Total device count in the database, before any status/filter/search is applied")
# ---------------------------------------------------------------------------
# Settings
# ---------------------------------------------------------------------------
class Setting(ObjectType):
setKey = String(description="Unique configuration key")
setName = String(description="Human-readable setting name")
setDescription = String(description="Detailed description of the setting")
setType = String(description="Config-driven type definition used to determine value type and UI rendering")
setOptions = String(description="JSON string of available options")
setGroup = String(description="UI group for categorization")
setValue = String(description="Current value")
setEvents = String(description="JSON string of events")
setOverriddenByEnv = Boolean(description="Whether the value is currently overridden by an environment variable")
class SettingResult(ObjectType):
settings = List(Setting, description="List of setting objects")
count = Int(description="Total count of settings")
# ---------------------------------------------------------------------------
# Language Strings
# ---------------------------------------------------------------------------
class LangString(ObjectType):
langCode = String(description="Language code (e.g., en_us, de_de)")
langStringKey = String(description="Unique translation key")
langStringText = String(description="Translated text content")
class LangStringResult(ObjectType):
langStrings = List(LangString, description="List of language string objects")
count = Int(description="Total count of strings")
# ---------------------------------------------------------------------------
# App Events
# ---------------------------------------------------------------------------
class AppEvent(ObjectType):
index = Int(description="Internal index")
guid = String(description="Unique event GUID")
appEventProcessed = Int(description="Processing status (0 or 1)")
dateTimeCreated = String(description="Event creation timestamp")
objectType = String(description="Type of the related object (Device, Setting, etc.)")
objectGuid = String(description="GUID of the related object")
objectPlugin = String(description="Plugin associated with the object")
objectPrimaryId = String(description="Primary identifier of the object")
objectSecondaryId = String(description="Secondary identifier of the object")
objectForeignKey = String(description="Foreign key reference")
objectIndex = Int(description="Object index")
objectIsNew = Int(description="Is the object new? (0 or 1)")
objectIsArchived = Int(description="Is the object archived? (0 or 1)")
objectStatusColumn = String(description="Column used for status")
objectStatus = String(description="Object status value")
appEventType = String(description="Type of application event")
helper1 = String(description="Generic helper field 1")
helper2 = String(description="Generic helper field 2")
helper3 = String(description="Generic helper field 3")
extra = String(description="Additional JSON data")
class AppEventResult(ObjectType):
appEvents = List(AppEvent, description="List of application events")
count = Int(description="Total count of events")
# ---------------------------------------------------------------------------
# Plugin tables (Plugins_Objects, Plugins_Events, Plugins_History)
# All three tables share the same schema — one ObjectType, three result wrappers.
# GraphQL requires distinct named types even when fields are identical.
# ---------------------------------------------------------------------------
class PluginQueryOptionsInput(InputObjectType):
page = Int()
limit = Int()
sort = List(SortOptionsInput)
search = String()
filters = List(FilterOptionsInput)
plugin = String(description="Filter by plugin prefix (e.g. 'ARPSCAN')")
foreignKey = String(description="Filter by foreignKey (e.g. device MAC)")
dateFrom = String(description="dateTimeCreated >= dateFrom (ISO datetime string)")
dateTo = String(description="dateTimeCreated <= dateTo (ISO datetime string)")
class PluginEntry(ObjectType):
index = Int(description="Auto-increment primary key")
plugin = String(description="Plugin prefix identifier")
objectPrimaryId = String(description="Primary identifier (e.g. MAC, IP)")
objectSecondaryId = String(description="Secondary identifier")
dateTimeCreated = String(description="Record creation timestamp")
dateTimeChanged = String(description="Record last-changed timestamp")
watchedValue1 = String(description="Monitored value 1")
watchedValue2 = String(description="Monitored value 2")
watchedValue3 = String(description="Monitored value 3")
watchedValue4 = String(description="Monitored value 4")
status = String(description="Record status")
extra = String(description="Extra JSON payload")
userData = String(description="User-supplied data")
foreignKey = String(description="Foreign key (e.g. device MAC)")
syncHubNodeName = String(description="Sync hub node name")
helpVal1 = String(description="Helper value 1")
helpVal2 = String(description="Helper value 2")
helpVal3 = String(description="Helper value 3")
helpVal4 = String(description="Helper value 4")
objectGuid = String(description="Object GUID")
class PluginsObjectsResult(ObjectType):
entries = List(PluginEntry, description="Plugins_Objects rows")
count = Int(description="Filtered count (before pagination)")
db_count = Int(description="Total rows in table before any filter")
class PluginsEventsResult(ObjectType):
entries = List(PluginEntry, description="Plugins_Events rows")
count = Int(description="Filtered count (before pagination)")
db_count = Int(description="Total rows in table before any filter")
class PluginsHistoryResult(ObjectType):
entries = List(PluginEntry, description="Plugins_History rows")
count = Int(description="Filtered count (before pagination)")
db_count = Int(description="Total rows in table before any filter")
# ---------------------------------------------------------------------------
# Events table (device presence events)
# ---------------------------------------------------------------------------
class EventQueryOptionsInput(InputObjectType):
page = Int()
limit = Int()
sort = List(SortOptionsInput)
search = String()
filters = List(FilterOptionsInput)
eveMac = String(description="Filter by device MAC address")
eventType = String(description="Filter by eveEventType (exact match)")
dateFrom = String(description="eveDateTime >= dateFrom (ISO datetime string)")
dateTo = String(description="eveDateTime <= dateTo (ISO datetime string)")
class EventEntry(ObjectType):
rowid = Int(description="SQLite rowid")
eveMac = String(description="Device MAC address")
eveIp = String(description="Device IP at event time")
eveDateTime = String(description="Event timestamp")
eveEventType = String(description="Event type (Connected, New Device, etc.)")
eveAdditionalInfo = String(description="Additional event info")
evePendingAlertEmail = Int(description="Pending alert flag (0 or 1)")
evePairEventRowid = Int(description="Paired event rowid (for session pairing)")
class EventsResult(ObjectType):
entries = List(EventEntry, description="Events table rows")
count = Int(description="Filtered count (before pagination)")
db_count = Int(description="Total rows in table before any filter")

View File

@@ -1084,3 +1084,32 @@ class GraphQLRequest(BaseModel):
"""Request payload for GraphQL queries."""
query: str = Field(..., description="GraphQL query string", json_schema_extra={"examples": ["{ devices { devMac devName } }"]})
variables: Optional[Dict[str, Any]] = Field(None, description="Variables for the GraphQL query")
# =============================================================================
# PLUGIN SCHEMAS
# =============================================================================
class PluginStatsEntry(BaseModel):
"""Per-plugin row count for one table."""
tableName: str = Field(..., description="Table category: objects, events, or history")
plugin: str = Field(..., description="Plugin unique prefix")
cnt: int = Field(..., ge=0, description="Row count")
class PluginStatsResponse(BaseResponse):
"""Response for GET /plugins/stats — per-plugin row counts."""
model_config = ConfigDict(
extra="allow",
json_schema_extra={
"examples": [{
"success": True,
"data": [
{"tableName": "objects", "plugin": "ARPSCAN", "cnt": 42},
{"tableName": "events", "plugin": "ARPSCAN", "cnt": 5},
{"tableName": "history", "plugin": "ARPSCAN", "cnt": 100}
]
}]
}
)
data: List[PluginStatsEntry] = Field(default_factory=list, description="Per-plugin row counts")

View File

@@ -33,8 +33,8 @@ def create_session(
cur.execute(
"""
INSERT INTO Sessions (ses_MAC, ses_IP, ses_DateTimeConnection, ses_DateTimeDisconnection,
ses_EventTypeConnection, ses_EventTypeDisconnection)
INSERT INTO Sessions (sesMac, sesIp, sesDateTimeConnection, sesDateTimeDisconnection,
sesEventTypeConnection, sesEventTypeDisconnection)
VALUES (?, ?, ?, ?, ?, ?)
""",
(mac, ip, start_time, end_time, event_type_conn, event_type_disc),
@@ -52,7 +52,7 @@ def delete_session(mac):
conn = get_temp_db_connection()
cur = conn.cursor()
cur.execute("DELETE FROM Sessions WHERE ses_MAC = ?", (mac,))
cur.execute("DELETE FROM Sessions WHERE sesMac = ?", (mac,))
conn.commit()
conn.close()
@@ -69,13 +69,13 @@ def get_sessions(mac=None, start_date=None, end_date=None):
params = []
if mac:
sql += " AND ses_MAC = ?"
sql += " AND sesMac = ?"
params.append(mac)
if start_date:
sql += " AND ses_DateTimeConnection >= ?"
sql += " AND sesDateTimeConnection >= ?"
params.append(start_date)
if end_date:
sql += " AND ses_DateTimeDisconnection <= ?"
sql += " AND sesDateTimeDisconnection <= ?"
params.append(end_date)
cur.execute(sql, tuple(params))
@@ -106,49 +106,49 @@ def get_sessions_calendar(start_date, end_date, mac):
sql = """
SELECT
SES1.ses_MAC,
SES1.ses_EventTypeConnection,
SES1.ses_DateTimeConnection,
SES1.ses_EventTypeDisconnection,
SES1.ses_DateTimeDisconnection,
SES1.ses_IP,
SES1.ses_AdditionalInfo,
SES1.ses_StillConnected,
SES1.sesMac,
SES1.sesEventTypeConnection,
SES1.sesDateTimeConnection,
SES1.sesEventTypeDisconnection,
SES1.sesDateTimeDisconnection,
SES1.sesIp,
SES1.sesAdditionalInfo,
SES1.sesStillConnected,
CASE
WHEN SES1.ses_EventTypeConnection = '<missing event>' THEN
WHEN SES1.sesEventTypeConnection = '<missing event>' THEN
IFNULL(
(
SELECT MAX(SES2.ses_DateTimeDisconnection)
SELECT MAX(SES2.sesDateTimeDisconnection)
FROM Sessions AS SES2
WHERE SES2.ses_MAC = SES1.ses_MAC
AND SES2.ses_DateTimeDisconnection < SES1.ses_DateTimeDisconnection
AND SES2.ses_DateTimeDisconnection BETWEEN Date(?) AND Date(?)
WHERE SES2.sesMac = SES1.sesMac
AND SES2.sesDateTimeDisconnection < SES1.sesDateTimeDisconnection
AND SES2.sesDateTimeDisconnection BETWEEN Date(?) AND Date(?)
),
DATETIME(SES1.ses_DateTimeDisconnection, '-1 hour')
DATETIME(SES1.sesDateTimeDisconnection, '-1 hour')
)
ELSE SES1.ses_DateTimeConnection
END AS ses_DateTimeConnectionCorrected,
ELSE SES1.sesDateTimeConnection
END AS sesDateTimeConnectionCorrected,
CASE
WHEN SES1.ses_EventTypeDisconnection = '<missing event>' THEN
WHEN SES1.sesEventTypeDisconnection = '<missing event>' THEN
(
SELECT MIN(SES2.ses_DateTimeConnection)
SELECT MIN(SES2.sesDateTimeConnection)
FROM Sessions AS SES2
WHERE SES2.ses_MAC = SES1.ses_MAC
AND SES2.ses_DateTimeConnection > SES1.ses_DateTimeConnection
AND SES2.ses_DateTimeConnection BETWEEN Date(?) AND Date(?)
WHERE SES2.sesMac = SES1.sesMac
AND SES2.sesDateTimeConnection > SES1.sesDateTimeConnection
AND SES2.sesDateTimeConnection BETWEEN Date(?) AND Date(?)
)
ELSE SES1.ses_DateTimeDisconnection
END AS ses_DateTimeDisconnectionCorrected
ELSE SES1.sesDateTimeDisconnection
END AS sesDateTimeDisconnectionCorrected
FROM Sessions AS SES1
WHERE (
(SES1.ses_DateTimeConnection BETWEEN Date(?) AND Date(?))
OR (SES1.ses_DateTimeDisconnection BETWEEN Date(?) AND Date(?))
OR SES1.ses_StillConnected = 1
(SES1.sesDateTimeConnection BETWEEN Date(?) AND Date(?))
OR (SES1.sesDateTimeDisconnection BETWEEN Date(?) AND Date(?))
OR SES1.sesStillConnected = 1
)
AND (? IS NULL OR SES1.ses_MAC = ?)
AND (? IS NULL OR SES1.sesMac = ?)
"""
cur.execute(
@@ -173,31 +173,31 @@ def get_sessions_calendar(start_date, end_date, mac):
# Color logic (unchanged from PHP)
if (
row["ses_EventTypeConnection"] == "<missing event>" or row["ses_EventTypeDisconnection"] == "<missing event>"
row["sesEventTypeConnection"] == "<missing event>" or row["sesEventTypeDisconnection"] == "<missing event>"
):
color = "#f39c12"
elif row["ses_StillConnected"] == 1:
elif row["sesStillConnected"] == 1:
color = "#00a659"
else:
color = "#0073b7"
# --- IMPORTANT FIX ---
# FullCalendar v3 CANNOT handle end = null
end_dt = row["ses_DateTimeDisconnectionCorrected"]
if not end_dt and row["ses_StillConnected"] == 1:
end_dt = row["sesDateTimeDisconnectionCorrected"]
if not end_dt and row["sesStillConnected"] == 1:
end_dt = now_iso
tooltip = (
f"Connection: {format_event_date(row['ses_DateTimeConnection'], row['ses_EventTypeConnection'])}\n"
f"Disconnection: {format_event_date(row['ses_DateTimeDisconnection'], row['ses_EventTypeDisconnection'])}\n"
f"IP: {row['ses_IP']}"
f"Connection: {format_event_date(row['sesDateTimeConnection'], row['sesEventTypeConnection'])}\n"
f"Disconnection: {format_event_date(row['sesDateTimeDisconnection'], row['sesEventTypeDisconnection'])}\n"
f"IP: {row['sesIp']}"
)
events.append(
{
"resourceId": row["ses_MAC"],
"resourceId": row["sesMac"],
"title": "",
"start": format_date_iso(row["ses_DateTimeConnectionCorrected"]),
"start": format_date_iso(row["sesDateTimeConnectionCorrected"]),
"end": format_date_iso(end_dt),
"color": color,
"tooltip": tooltip,
@@ -219,20 +219,20 @@ def get_device_sessions(mac, period):
sql = f"""
SELECT
IFNULL(ses_DateTimeConnection, ses_DateTimeDisconnection) AS ses_DateTimeOrder,
ses_EventTypeConnection,
ses_DateTimeConnection,
ses_EventTypeDisconnection,
ses_DateTimeDisconnection,
ses_StillConnected,
ses_IP,
ses_AdditionalInfo
IFNULL(sesDateTimeConnection, sesDateTimeDisconnection) AS sesDateTimeOrder,
sesEventTypeConnection,
sesDateTimeConnection,
sesEventTypeDisconnection,
sesDateTimeDisconnection,
sesStillConnected,
sesIp,
sesAdditionalInfo
FROM Sessions
WHERE ses_MAC = ?
WHERE sesMac = ?
AND (
ses_DateTimeConnection >= {period_date}
OR ses_DateTimeDisconnection >= {period_date}
OR ses_StillConnected = 1
sesDateTimeConnection >= {period_date}
OR sesDateTimeDisconnection >= {period_date}
OR sesStillConnected = 1
)
"""
@@ -245,44 +245,44 @@ def get_device_sessions(mac, period):
for row in rows:
# Connection DateTime
if row["ses_EventTypeConnection"] == "<missing event>":
ini = row["ses_EventTypeConnection"]
if row["sesEventTypeConnection"] == "<missing event>":
ini = row["sesEventTypeConnection"]
else:
ini = format_date(row["ses_DateTimeConnection"])
ini = format_date(row["sesDateTimeConnection"])
# Disconnection DateTime
if row["ses_StillConnected"]:
if row["sesStillConnected"]:
end = "..."
elif row["ses_EventTypeDisconnection"] == "<missing event>":
end = row["ses_EventTypeDisconnection"]
elif row["sesEventTypeDisconnection"] == "<missing event>":
end = row["sesEventTypeDisconnection"]
else:
end = format_date(row["ses_DateTimeDisconnection"])
end = format_date(row["sesDateTimeDisconnection"])
# Duration
if row["ses_EventTypeConnection"] in ("<missing event>", None) or row[
"ses_EventTypeDisconnection"
if row["sesEventTypeConnection"] in ("<missing event>", None) or row[
"sesEventTypeDisconnection"
] in ("<missing event>", None):
dur = "..."
elif row["ses_StillConnected"]:
dur = format_date_diff(row["ses_DateTimeConnection"], None, tz_name)["text"]
elif row["sesStillConnected"]:
dur = format_date_diff(row["sesDateTimeConnection"], None, tz_name)["text"]
else:
dur = format_date_diff(row["ses_DateTimeConnection"], row["ses_DateTimeDisconnection"], tz_name)["text"]
dur = format_date_diff(row["sesDateTimeConnection"], row["sesDateTimeDisconnection"], tz_name)["text"]
# Additional Info
info = row["ses_AdditionalInfo"]
if row["ses_EventTypeConnection"] == "New Device":
info = f"{row['ses_EventTypeConnection']}: {info}"
info = row["sesAdditionalInfo"]
if row["sesEventTypeConnection"] == "New Device":
info = f"{row['sesEventTypeConnection']}: {info}"
# Push row data
table_data["data"].append(
{
"ses_MAC": mac,
"ses_DateTimeOrder": row["ses_DateTimeOrder"],
"ses_Connection": ini,
"ses_Disconnection": end,
"ses_Duration": dur,
"ses_IP": row["ses_IP"],
"ses_Info": info,
"sesMac": mac,
"sesDateTimeOrder": row["sesDateTimeOrder"],
"sesConnection": ini,
"sesDisconnection": end,
"sesDuration": dur,
"sesIp": row["sesIp"],
"sesInfo": info,
}
)
@@ -295,10 +295,16 @@ def get_device_sessions(mac, period):
return jsonify({"success": True, "sessions": sessions})
def get_session_events(event_type, period_date):
def get_session_events(event_type, period_date, page=1, limit=100, search=None, sort_col=0, sort_dir="desc"):
"""
Fetch events or sessions based on type and period.
Supports server-side pagination (page/limit), free-text search, and sorting.
Returns { data, total, recordsFiltered } so callers can drive DataTables serverSide mode.
"""
_MAX_LIMIT = 1000
limit = min(max(1, int(limit)), _MAX_LIMIT)
page = max(1, int(page))
conn = get_temp_db_connection()
conn.row_factory = sqlite3.Row
cur = conn.cursor()
@@ -307,42 +313,42 @@ def get_session_events(event_type, period_date):
# Base SQLs
sql_events = f"""
SELECT
eve_DateTime AS eve_DateTimeOrder,
eveDateTime AS eveDateTimeOrder,
devName,
devOwner,
eve_DateTime,
eve_EventType,
eveDateTime,
eveEventType,
NULL,
NULL,
NULL,
NULL,
eve_IP,
eveIp,
NULL,
eve_AdditionalInfo,
eveAdditionalInfo,
NULL,
devMac,
eve_PendingAlertEmail
evePendingAlertEmail
FROM Events_Devices
WHERE eve_DateTime >= {period_date}
WHERE eveDateTime >= {period_date}
"""
sql_sessions = """
SELECT
IFNULL(ses_DateTimeConnection, ses_DateTimeDisconnection) AS ses_DateTimeOrder,
IFNULL(sesDateTimeConnection, sesDateTimeDisconnection) AS sesDateTimeOrder,
devName,
devOwner,
NULL,
NULL,
ses_DateTimeConnection,
ses_DateTimeDisconnection,
sesDateTimeConnection,
sesDateTimeDisconnection,
NULL,
NULL,
ses_IP,
sesIp,
NULL,
ses_AdditionalInfo,
ses_StillConnected,
sesAdditionalInfo,
sesStillConnected,
devMac,
0 AS ses_PendingAlertEmail
0 AS sesPendingAlertEmail
FROM Sessions_Devices
"""
@@ -353,9 +359,9 @@ def get_session_events(event_type, period_date):
sql = (
sql_sessions + f"""
WHERE (
ses_DateTimeConnection >= {period_date}
OR ses_DateTimeDisconnection >= {period_date}
OR ses_StillConnected = 1
sesDateTimeConnection >= {period_date}
OR sesDateTimeDisconnection >= {period_date}
OR sesStillConnected = 1
)
"""
)
@@ -363,17 +369,17 @@ def get_session_events(event_type, period_date):
sql = (
sql_sessions + f"""
WHERE (
(ses_DateTimeConnection IS NULL AND ses_DateTimeDisconnection >= {period_date})
OR (ses_DateTimeDisconnection IS NULL AND ses_StillConnected = 0 AND ses_DateTimeConnection >= {period_date})
(sesDateTimeConnection IS NULL AND sesDateTimeDisconnection >= {period_date})
OR (sesDateTimeDisconnection IS NULL AND sesStillConnected = 0 AND sesDateTimeConnection >= {period_date})
)
"""
)
elif event_type == "voided":
sql = sql_events + ' AND eve_EventType LIKE "VOIDED%"'
sql = sql_events + ' AND eveEventType LIKE "VOIDED%"'
elif event_type == "new":
sql = sql_events + ' AND eve_EventType = "New Device"'
sql = sql_events + ' AND eveEventType = "New Device"'
elif event_type == "down":
sql = sql_events + ' AND eve_EventType = "Device Down"'
sql = sql_events + ' AND eveEventType = "Device Down"'
else:
sql = sql_events + " AND 1=0"
@@ -420,4 +426,30 @@ def get_session_events(event_type, period_date):
table_data["data"].append(row)
return jsonify(table_data)
all_rows = table_data["data"]
# --- Sorting ---
num_cols = len(all_rows[0]) if all_rows else 0
if 0 <= sort_col < num_cols:
reverse = sort_dir.lower() == "desc"
all_rows.sort(
key=lambda r: (r[sort_col] is None, r[sort_col] if r[sort_col] is not None else ""),
reverse=reverse,
)
total = len(all_rows)
# --- Free-text search (applied after formatting so display values are searchable) ---
if search:
search_lower = search.strip().lower()
def _row_matches(r):
return any(search_lower in str(v).lower() for v in r if v is not None)
all_rows = [r for r in all_rows if _row_matches(r)]
records_filtered = len(all_rows)
# --- Pagination ---
offset = (page - 1) * limit
paged_rows = all_rows[offset: offset + limit]
return jsonify({"data": paged_rows, "total": total, "recordsFiltered": records_filtered})

View File

@@ -67,42 +67,7 @@ sql_devices_all = """
FROM DevicesView
"""
sql_appevents = """select * from AppEvents order by DateTimeCreated desc"""
# The below query calculates counts of devices in various categories:
# (connected/online, offline, down, new, archived),
# as well as a combined count for devices that match any status listed in the UI_MY_DEVICES setting
sql_devices_tiles = """
WITH Statuses AS (
SELECT setValue
FROM Settings
WHERE setKey = 'UI_MY_DEVICES'
),
MyDevicesFilter AS (
SELECT
-- Build a dynamic filter for devices matching any status in UI_MY_DEVICES
devPresentLastScan, devAlertDown, devIsNew, devIsArchived
FROM Devices
WHERE
(instr((SELECT setValue FROM Statuses), 'online') > 0 AND devPresentLastScan = 1) OR
(instr((SELECT setValue FROM Statuses), 'offline') > 0 AND devPresentLastScan = 0 AND devIsArchived = 0) OR
(instr((SELECT setValue FROM Statuses), 'down') > 0 AND devPresentLastScan = 0 AND devAlertDown = 1) OR
(instr((SELECT setValue FROM Statuses), 'new') > 0 AND devIsNew = 1) OR
(instr((SELECT setValue FROM Statuses), 'archived') > 0 AND devIsArchived = 1)
)
SELECT
-- Counts for each individual status
(SELECT COUNT(*) FROM Devices WHERE devPresentLastScan = 1) AS connected,
(SELECT COUNT(*) FROM Devices WHERE devPresentLastScan = 0) AS offline,
(SELECT COUNT(*) FROM Devices WHERE devPresentLastScan = 0 AND devAlertDown = 1) AS down,
(SELECT COUNT(*) FROM Devices WHERE devIsNew = 1) AS new,
(SELECT COUNT(*) FROM Devices WHERE devIsArchived = 1) AS archived,
(SELECT COUNT(*) FROM Devices WHERE devFavorite = 1) AS favorites,
(SELECT COUNT(*) FROM Devices) AS "all",
(SELECT COUNT(*) FROM Devices) AS "all_devices",
-- My Devices count
(SELECT COUNT(*) FROM MyDevicesFilter) AS my_devices
FROM Statuses;
"""
sql_appevents = """select * from AppEvents order by dateTimeCreated desc"""
sql_devices_filters = """
SELECT DISTINCT 'devSite' AS columnName, devSite AS columnValue
FROM Devices WHERE devSite NOT IN ('', 'null') AND devSite IS NOT NULL
@@ -141,32 +106,38 @@ sql_devices_filters = """
sql_devices_stats = f"""
SELECT
Online_Devices as online,
Down_Devices as down,
All_Devices as 'all',
Archived_Devices as archived,
onlineDevices as online,
downDevices as down,
allDevices as 'all',
archivedDevices as archived,
(SELECT COUNT(*) FROM Devices a WHERE devIsNew = 1) as new,
(SELECT COUNT(*) FROM Devices a WHERE devName IN ({NULL_EQUIVALENTS_SQL}) OR devName IS NULL) as unknown
FROM Online_History
ORDER BY Scan_Date DESC
ORDER BY scanDate DESC
LIMIT 1
"""
sql_events_pending_alert = "SELECT * FROM Events where eve_PendingAlertEmail is not 0"
sql_events_pending_alert = "SELECT * FROM Events where evePendingAlertEmail is not 0"
sql_events_all = "SELECT rowid, * FROM Events ORDER BY eveDateTime DESC"
sql_settings = "SELECT * FROM Settings"
sql_plugins_objects = "SELECT * FROM Plugins_Objects"
sql_plugins_stats = """SELECT 'objects' AS tableName, plugin, COUNT(*) AS cnt FROM Plugins_Objects GROUP BY plugin
UNION ALL
SELECT 'events', plugin, COUNT(*) FROM Plugins_Events GROUP BY plugin
UNION ALL
SELECT 'history', plugin, COUNT(*) FROM Plugins_History GROUP BY plugin"""
sql_language_strings = "SELECT * FROM Plugins_Language_Strings"
sql_notifications_all = "SELECT * FROM Notifications"
sql_online_history = "SELECT * FROM Online_History"
sql_plugins_events = "SELECT * FROM Plugins_Events"
sql_plugins_history = "SELECT * FROM Plugins_History ORDER BY DateTimeChanged DESC"
sql_plugins_history = "SELECT * FROM Plugins_History ORDER BY dateTimeChanged DESC"
sql_new_devices = """SELECT * FROM (
SELECT eve_IP as devLastIP,
eve_MAC as devMac,
MAX(eve_DateTime) as lastEvent
SELECT eveIp as devLastIP,
eveMac as devMac,
MAX(eveDateTime) as lastEvent
FROM Events_Devices
WHERE eve_PendingAlertEmail = 1
AND eve_EventType = 'New Device'
GROUP BY eve_MAC
WHERE evePendingAlertEmail = 1
AND eveEventType = 'New Device'
GROUP BY eveMac
ORDER BY lastEvent
) t1
LEFT JOIN

View File

@@ -16,6 +16,7 @@ from db.db_upgrade import (
ensure_Settings,
ensure_Indexes,
ensure_mac_lowercase_triggers,
migrate_to_camelcase,
migrate_timestamps_to_utc,
)
@@ -194,6 +195,10 @@ class DB:
if not ensure_column(self.sql, "Devices", "devCanSleep", "INTEGER"):
raise RuntimeError("ensure_column(devCanSleep) failed")
# CamelCase column migration (must run before UTC migration and
# before ensure_plugins_tables which uses IF NOT EXISTS with new names)
migrate_to_camelcase(self.sql)
# Settings table setup
ensure_Settings(self.sql)

View File

@@ -66,6 +66,55 @@ def get_device_condition_by_status(device_status):
return get_device_conditions().get(device_status, "WHERE 1=0")
# -------------------------------------------------------------------------------
def get_sql_devices_tiles():
"""Build the device tiles count SQL using get_device_conditions() to avoid duplicating filter logic."""
conds = get_device_conditions()
def f(key):
"""Strip 'WHERE ' prefix for use inside SELECT subqueries."""
return conds[key][len("WHERE "):]
# UI_MY_DEVICES setting values mapped to their device_conditions keys
my_devices_setting_map = [
("online", "connected"),
("offline", "offline"),
("down", "down"),
("new", "new"),
("archived", "archived"),
]
my_devices_clauses = "\n OR ".join(
f"(instr((SELECT setValue FROM Statuses), '{sk}') > 0 AND {f(ck)})"
for sk, ck in my_devices_setting_map
)
return f"""
WITH Statuses AS (
SELECT setValue
FROM Settings
WHERE setKey = 'UI_MY_DEVICES'
),
MyDevicesFilter AS (
SELECT devMac, devIsSleeping
FROM DevicesView
WHERE
{my_devices_clauses}
)
SELECT
(SELECT COUNT(*) FROM DevicesView WHERE {f('connected')}) AS connected,
(SELECT COUNT(*) FROM DevicesView WHERE {f('offline')}) AS offline,
(SELECT COUNT(*) FROM DevicesView WHERE {f('down')}) AS down,
(SELECT COUNT(*) FROM DevicesView WHERE {f('new')}) AS new,
(SELECT COUNT(*) FROM DevicesView WHERE {f('archived')}) AS archived,
(SELECT COUNT(*) FROM DevicesView WHERE {f('favorites')}) AS favorites,
(SELECT COUNT(*) FROM DevicesView WHERE {f('all')}) AS "all",
(SELECT COUNT(*) FROM DevicesView) AS "all_devices",
(SELECT COUNT(*) FROM MyDevicesFilter) AS my_devices
FROM Statuses;
"""
# -------------------------------------------------------------------------------
# Creates a JSON-like dictionary from a database row
def row_to_json(names, row):

View File

@@ -157,7 +157,7 @@ def ensure_views(sql) -> bool:
sql.execute(""" CREATE VIEW Events_Devices AS
SELECT *
FROM Events
LEFT JOIN Devices ON eve_MAC = devMac;
LEFT JOIN Devices ON eveMac = devMac;
""")
sql.execute(""" DROP VIEW IF EXISTS LatestEventsPerMAC;""")
@@ -165,7 +165,7 @@ def ensure_views(sql) -> bool:
WITH RankedEvents AS (
SELECT
e.*,
ROW_NUMBER() OVER (PARTITION BY e.eve_MAC ORDER BY e.eve_DateTime DESC) AS row_num
ROW_NUMBER() OVER (PARTITION BY e.eveMac ORDER BY e.eveDateTime DESC) AS row_num
FROM Events AS e
)
SELECT
@@ -173,43 +173,43 @@ def ensure_views(sql) -> bool:
d.*,
c.*
FROM RankedEvents AS e
LEFT JOIN Devices AS d ON e.eve_MAC = d.devMac
INNER JOIN CurrentScan AS c ON e.eve_MAC = c.scanMac
LEFT JOIN Devices AS d ON e.eveMac = d.devMac
INNER JOIN CurrentScan AS c ON e.eveMac = c.scanMac
WHERE e.row_num = 1;""")
sql.execute(""" DROP VIEW IF EXISTS Sessions_Devices;""")
sql.execute(
"""CREATE VIEW Sessions_Devices AS SELECT * FROM Sessions LEFT JOIN "Devices" ON ses_MAC = devMac;"""
"""CREATE VIEW Sessions_Devices AS SELECT * FROM Sessions LEFT JOIN "Devices" ON sesMac = devMac;"""
)
# handling the Convert_Events_to_Sessions / Sessions screens
sql.execute("""DROP VIEW IF EXISTS Convert_Events_to_Sessions;""")
sql.execute("""CREATE VIEW Convert_Events_to_Sessions AS SELECT EVE1.eve_MAC,
EVE1.eve_IP,
EVE1.eve_EventType AS eve_EventTypeConnection,
EVE1.eve_DateTime AS eve_DateTimeConnection,
CASE WHEN EVE2.eve_EventType IN ('Disconnected', 'Device Down') OR
EVE2.eve_EventType IS NULL THEN EVE2.eve_EventType ELSE '<missing event>' END AS eve_EventTypeDisconnection,
CASE WHEN EVE2.eve_EventType IN ('Disconnected', 'Device Down') THEN EVE2.eve_DateTime ELSE NULL END AS eve_DateTimeDisconnection,
CASE WHEN EVE2.eve_EventType IS NULL THEN 1 ELSE 0 END AS eve_StillConnected,
EVE1.eve_AdditionalInfo
sql.execute("""CREATE VIEW Convert_Events_to_Sessions AS SELECT EVE1.eveMac,
EVE1.eveIp,
EVE1.eveEventType AS eveEventTypeConnection,
EVE1.eveDateTime AS eveDateTimeConnection,
CASE WHEN EVE2.eveEventType IN ('Disconnected', 'Device Down') OR
EVE2.eveEventType IS NULL THEN EVE2.eveEventType ELSE '<missing event>' END AS eveEventTypeDisconnection,
CASE WHEN EVE2.eveEventType IN ('Disconnected', 'Device Down') THEN EVE2.eveDateTime ELSE NULL END AS eveDateTimeDisconnection,
CASE WHEN EVE2.eveEventType IS NULL THEN 1 ELSE 0 END AS eveStillConnected,
EVE1.eveAdditionalInfo
FROM Events AS EVE1
LEFT JOIN
Events AS EVE2 ON EVE1.eve_PairEventRowID = EVE2.RowID
WHERE EVE1.eve_EventType IN ('New Device', 'Connected','Down Reconnected')
Events AS EVE2 ON EVE1.evePairEventRowid = EVE2.RowID
WHERE EVE1.eveEventType IN ('New Device', 'Connected','Down Reconnected')
UNION
SELECT eve_MAC,
eve_IP,
'<missing event>' AS eve_EventTypeConnection,
NULL AS eve_DateTimeConnection,
eve_EventType AS eve_EventTypeDisconnection,
eve_DateTime AS eve_DateTimeDisconnection,
0 AS eve_StillConnected,
eve_AdditionalInfo
SELECT eveMac,
eveIp,
'<missing event>' AS eveEventTypeConnection,
NULL AS eveDateTimeConnection,
eveEventType AS eveEventTypeDisconnection,
eveDateTime AS eveDateTimeDisconnection,
0 AS eveStillConnected,
eveAdditionalInfo
FROM Events AS EVE1
WHERE (eve_EventType = 'Device Down' OR
eve_EventType = 'Disconnected') AND
EVE1.eve_PairEventRowID IS NULL;
WHERE (eveEventType = 'Device Down' OR
eveEventType = 'Disconnected') AND
EVE1.evePairEventRowid IS NULL;
""")
sql.execute(""" DROP VIEW IF EXISTS LatestDeviceScan;""")
@@ -316,10 +316,10 @@ def ensure_views(sql) -> bool:
WHEN EXISTS (
SELECT 1
FROM Events e
WHERE LOWER(e.eve_MAC) = LOWER(Devices.devMac)
AND e.eve_EventType IN ('Connected','Disconnected','Device Down','Down Reconnected')
AND e.eve_DateTime >= datetime('now', '-{FLAP_WINDOW_HOURS} hours')
GROUP BY e.eve_MAC
WHERE LOWER(e.eveMac) = LOWER(Devices.devMac)
AND e.eveEventType IN ('Connected','Disconnected','Device Down','Down Reconnected')
AND e.eveDateTime >= datetime('now', '-{FLAP_WINDOW_HOURS} hours')
GROUP BY e.eveMac
HAVING COUNT(*) >= {FLAP_THRESHOLD}
)
THEN 1
@@ -360,10 +360,10 @@ def ensure_Indexes(sql) -> bool:
SELECT MIN(rowid)
FROM Events
GROUP BY
eve_MAC,
eve_IP,
eve_EventType,
eve_DateTime
eveMac,
eveIp,
eveEventType,
eveDateTime
);
"""
@@ -373,32 +373,32 @@ def ensure_Indexes(sql) -> bool:
# Sessions
(
"idx_ses_mac_date",
"CREATE INDEX idx_ses_mac_date ON Sessions(ses_MAC, ses_DateTimeConnection, ses_DateTimeDisconnection, ses_StillConnected)",
"CREATE INDEX idx_ses_mac_date ON Sessions(sesMac, sesDateTimeConnection, sesDateTimeDisconnection, sesStillConnected)",
),
# Events
(
"idx_eve_mac_date_type",
"CREATE INDEX idx_eve_mac_date_type ON Events(eve_MAC, eve_DateTime, eve_EventType)",
"CREATE INDEX idx_eve_mac_date_type ON Events(eveMac, eveDateTime, eveEventType)",
),
(
"idx_eve_alert_pending",
"CREATE INDEX idx_eve_alert_pending ON Events(eve_PendingAlertEmail)",
"CREATE INDEX idx_eve_alert_pending ON Events(evePendingAlertEmail)",
),
(
"idx_eve_mac_datetime_desc",
"CREATE INDEX idx_eve_mac_datetime_desc ON Events(eve_MAC, eve_DateTime DESC)",
"CREATE INDEX idx_eve_mac_datetime_desc ON Events(eveMac, eveDateTime DESC)",
),
(
"idx_eve_pairevent",
"CREATE INDEX idx_eve_pairevent ON Events(eve_PairEventRowID)",
"CREATE INDEX idx_eve_pairevent ON Events(evePairEventRowid)",
),
(
"idx_eve_type_date",
"CREATE INDEX idx_eve_type_date ON Events(eve_EventType, eve_DateTime)",
"CREATE INDEX idx_eve_type_date ON Events(eveEventType, eveDateTime)",
),
(
"idx_events_unique",
"CREATE UNIQUE INDEX idx_events_unique ON Events (eve_MAC, eve_IP, eve_EventType, eve_DateTime)",
"CREATE UNIQUE INDEX idx_events_unique ON Events (eveMac, eveIp, eveEventType, eveDateTime)",
),
# Devices
("idx_dev_mac", "CREATE INDEX idx_dev_mac ON Devices(devMac)"),
@@ -436,15 +436,15 @@ def ensure_Indexes(sql) -> bool:
# Plugins_Objects
(
"idx_plugins_plugin_mac_ip",
"CREATE INDEX idx_plugins_plugin_mac_ip ON Plugins_Objects(Plugin, Object_PrimaryID, Object_SecondaryID)",
"CREATE INDEX idx_plugins_plugin_mac_ip ON Plugins_Objects(plugin, objectPrimaryId, objectSecondaryId)",
), # Issue #1251: Optimize name resolution lookup
# Plugins_History: covers both the db_cleanup window function
# (PARTITION BY Plugin ORDER BY DateTimeChanged DESC) and the
# API query (SELECT * … ORDER BY DateTimeChanged DESC).
# (PARTITION BY plugin ORDER BY dateTimeChanged DESC) and the
# API query (SELECT * … ORDER BY dateTimeChanged DESC).
# Without this, both ops do a full 48k-row table sort on every cycle.
(
"idx_plugins_history_plugin_dt",
"CREATE INDEX idx_plugins_history_plugin_dt ON Plugins_History(Plugin, DateTimeChanged DESC)",
"CREATE INDEX idx_plugins_history_plugin_dt ON Plugins_History(plugin, dateTimeChanged DESC)",
),
]
@@ -547,94 +547,295 @@ def ensure_plugins_tables(sql) -> bool:
# Plugin state
sql_Plugins_Objects = """ CREATE TABLE IF NOT EXISTS Plugins_Objects(
"Index" INTEGER,
Plugin TEXT NOT NULL,
Object_PrimaryID TEXT NOT NULL,
Object_SecondaryID TEXT NOT NULL,
DateTimeCreated TEXT NOT NULL,
DateTimeChanged TEXT NOT NULL,
Watched_Value1 TEXT NOT NULL,
Watched_Value2 TEXT NOT NULL,
Watched_Value3 TEXT NOT NULL,
Watched_Value4 TEXT NOT NULL,
Status TEXT NOT NULL,
Extra TEXT NOT NULL,
UserData TEXT NOT NULL,
ForeignKey TEXT NOT NULL,
SyncHubNodeName TEXT,
"HelpVal1" TEXT,
"HelpVal2" TEXT,
"HelpVal3" TEXT,
"HelpVal4" TEXT,
ObjectGUID TEXT,
PRIMARY KEY("Index" AUTOINCREMENT)
"index" INTEGER,
plugin TEXT NOT NULL,
objectPrimaryId TEXT NOT NULL,
objectSecondaryId TEXT NOT NULL,
dateTimeCreated TEXT NOT NULL,
dateTimeChanged TEXT NOT NULL,
watchedValue1 TEXT NOT NULL,
watchedValue2 TEXT NOT NULL,
watchedValue3 TEXT NOT NULL,
watchedValue4 TEXT NOT NULL,
"status" TEXT NOT NULL,
extra TEXT NOT NULL,
userData TEXT NOT NULL,
foreignKey TEXT NOT NULL,
syncHubNodeName TEXT,
helpVal1 TEXT,
helpVal2 TEXT,
helpVal3 TEXT,
helpVal4 TEXT,
objectGuid TEXT,
PRIMARY KEY("index" AUTOINCREMENT)
); """
sql.execute(sql_Plugins_Objects)
# Plugin execution results
sql_Plugins_Events = """ CREATE TABLE IF NOT EXISTS Plugins_Events(
"Index" INTEGER,
Plugin TEXT NOT NULL,
Object_PrimaryID TEXT NOT NULL,
Object_SecondaryID TEXT NOT NULL,
DateTimeCreated TEXT NOT NULL,
DateTimeChanged TEXT NOT NULL,
Watched_Value1 TEXT NOT NULL,
Watched_Value2 TEXT NOT NULL,
Watched_Value3 TEXT NOT NULL,
Watched_Value4 TEXT NOT NULL,
Status TEXT NOT NULL,
Extra TEXT NOT NULL,
UserData TEXT NOT NULL,
ForeignKey TEXT NOT NULL,
SyncHubNodeName TEXT,
"HelpVal1" TEXT,
"HelpVal2" TEXT,
"HelpVal3" TEXT,
"HelpVal4" TEXT,
PRIMARY KEY("Index" AUTOINCREMENT)
"index" INTEGER,
plugin TEXT NOT NULL,
objectPrimaryId TEXT NOT NULL,
objectSecondaryId TEXT NOT NULL,
dateTimeCreated TEXT NOT NULL,
dateTimeChanged TEXT NOT NULL,
watchedValue1 TEXT NOT NULL,
watchedValue2 TEXT NOT NULL,
watchedValue3 TEXT NOT NULL,
watchedValue4 TEXT NOT NULL,
"status" TEXT NOT NULL,
extra TEXT NOT NULL,
userData TEXT NOT NULL,
foreignKey TEXT NOT NULL,
syncHubNodeName TEXT,
helpVal1 TEXT,
helpVal2 TEXT,
helpVal3 TEXT,
helpVal4 TEXT,
objectGuid TEXT,
PRIMARY KEY("index" AUTOINCREMENT)
); """
sql.execute(sql_Plugins_Events)
# Plugin execution history
sql_Plugins_History = """ CREATE TABLE IF NOT EXISTS Plugins_History(
"Index" INTEGER,
Plugin TEXT NOT NULL,
Object_PrimaryID TEXT NOT NULL,
Object_SecondaryID TEXT NOT NULL,
DateTimeCreated TEXT NOT NULL,
DateTimeChanged TEXT NOT NULL,
Watched_Value1 TEXT NOT NULL,
Watched_Value2 TEXT NOT NULL,
Watched_Value3 TEXT NOT NULL,
Watched_Value4 TEXT NOT NULL,
Status TEXT NOT NULL,
Extra TEXT NOT NULL,
UserData TEXT NOT NULL,
ForeignKey TEXT NOT NULL,
SyncHubNodeName TEXT,
"HelpVal1" TEXT,
"HelpVal2" TEXT,
"HelpVal3" TEXT,
"HelpVal4" TEXT,
PRIMARY KEY("Index" AUTOINCREMENT)
"index" INTEGER,
plugin TEXT NOT NULL,
objectPrimaryId TEXT NOT NULL,
objectSecondaryId TEXT NOT NULL,
dateTimeCreated TEXT NOT NULL,
dateTimeChanged TEXT NOT NULL,
watchedValue1 TEXT NOT NULL,
watchedValue2 TEXT NOT NULL,
watchedValue3 TEXT NOT NULL,
watchedValue4 TEXT NOT NULL,
"status" TEXT NOT NULL,
extra TEXT NOT NULL,
userData TEXT NOT NULL,
foreignKey TEXT NOT NULL,
syncHubNodeName TEXT,
helpVal1 TEXT,
helpVal2 TEXT,
helpVal3 TEXT,
helpVal4 TEXT,
objectGuid TEXT,
PRIMARY KEY("index" AUTOINCREMENT)
); """
sql.execute(sql_Plugins_History)
# Dynamically generated language strings
sql.execute("DROP TABLE IF EXISTS Plugins_Language_Strings;")
sql.execute(""" CREATE TABLE IF NOT EXISTS Plugins_Language_Strings(
"Index" INTEGER,
Language_Code TEXT NOT NULL,
String_Key TEXT NOT NULL,
String_Value TEXT NOT NULL,
Extra TEXT NOT NULL,
PRIMARY KEY("Index" AUTOINCREMENT)
"index" INTEGER,
languageCode TEXT NOT NULL,
stringKey TEXT NOT NULL,
stringValue TEXT NOT NULL,
extra TEXT NOT NULL,
PRIMARY KEY("index" AUTOINCREMENT)
); """)
return True
# ===============================================================================
# CamelCase Column Migration
# ===============================================================================
# Mapping of (table_name, old_column_name) → new_column_name.
# Only entries where the name actually changes are listed.
# Columns like "Index" → "index" are cosmetic case changes handled
# implicitly by SQLite's case-insensitive matching.
_CAMELCASE_COLUMN_MAP = {
"Events": {
"eve_MAC": "eveMac",
"eve_IP": "eveIp",
"eve_DateTime": "eveDateTime",
"eve_EventType": "eveEventType",
"eve_AdditionalInfo": "eveAdditionalInfo",
"eve_PendingAlertEmail": "evePendingAlertEmail",
"eve_PairEventRowid": "evePairEventRowid",
"eve_PairEventRowID": "evePairEventRowid",
},
"Sessions": {
"ses_MAC": "sesMac",
"ses_IP": "sesIp",
"ses_EventTypeConnection": "sesEventTypeConnection",
"ses_DateTimeConnection": "sesDateTimeConnection",
"ses_EventTypeDisconnection": "sesEventTypeDisconnection",
"ses_DateTimeDisconnection": "sesDateTimeDisconnection",
"ses_StillConnected": "sesStillConnected",
"ses_AdditionalInfo": "sesAdditionalInfo",
},
"Online_History": {
"Index": "index",
"Scan_Date": "scanDate",
"Online_Devices": "onlineDevices",
"Down_Devices": "downDevices",
"All_Devices": "allDevices",
"Archived_Devices": "archivedDevices",
"Offline_Devices": "offlineDevices",
},
"Plugins_Objects": {
"Index": "index",
"Plugin": "plugin",
"Object_PrimaryID": "objectPrimaryId",
"Object_SecondaryID": "objectSecondaryId",
"DateTimeCreated": "dateTimeCreated",
"DateTimeChanged": "dateTimeChanged",
"Watched_Value1": "watchedValue1",
"Watched_Value2": "watchedValue2",
"Watched_Value3": "watchedValue3",
"Watched_Value4": "watchedValue4",
"Status": "status",
"Extra": "extra",
"UserData": "userData",
"ForeignKey": "foreignKey",
"SyncHubNodeName": "syncHubNodeName",
"HelpVal1": "helpVal1",
"HelpVal2": "helpVal2",
"HelpVal3": "helpVal3",
"HelpVal4": "helpVal4",
"ObjectGUID": "objectGuid",
},
"Plugins_Events": {
"Index": "index",
"Plugin": "plugin",
"Object_PrimaryID": "objectPrimaryId",
"Object_SecondaryID": "objectSecondaryId",
"DateTimeCreated": "dateTimeCreated",
"DateTimeChanged": "dateTimeChanged",
"Watched_Value1": "watchedValue1",
"Watched_Value2": "watchedValue2",
"Watched_Value3": "watchedValue3",
"Watched_Value4": "watchedValue4",
"Status": "status",
"Extra": "extra",
"UserData": "userData",
"ForeignKey": "foreignKey",
"SyncHubNodeName": "syncHubNodeName",
"HelpVal1": "helpVal1",
"HelpVal2": "helpVal2",
"HelpVal3": "helpVal3",
"HelpVal4": "helpVal4",
"ObjectGUID": "objectGuid",
},
"Plugins_History": {
"Index": "index",
"Plugin": "plugin",
"Object_PrimaryID": "objectPrimaryId",
"Object_SecondaryID": "objectSecondaryId",
"DateTimeCreated": "dateTimeCreated",
"DateTimeChanged": "dateTimeChanged",
"Watched_Value1": "watchedValue1",
"Watched_Value2": "watchedValue2",
"Watched_Value3": "watchedValue3",
"Watched_Value4": "watchedValue4",
"Status": "status",
"Extra": "extra",
"UserData": "userData",
"ForeignKey": "foreignKey",
"SyncHubNodeName": "syncHubNodeName",
"HelpVal1": "helpVal1",
"HelpVal2": "helpVal2",
"HelpVal3": "helpVal3",
"HelpVal4": "helpVal4",
"ObjectGUID": "objectGuid",
},
"Plugins_Language_Strings": {
"Index": "index",
"Language_Code": "languageCode",
"String_Key": "stringKey",
"String_Value": "stringValue",
"Extra": "extra",
},
"AppEvents": {
"Index": "index",
"GUID": "guid",
"AppEventProcessed": "appEventProcessed",
"DateTimeCreated": "dateTimeCreated",
"ObjectType": "objectType",
"ObjectGUID": "objectGuid",
"ObjectPlugin": "objectPlugin",
"ObjectPrimaryID": "objectPrimaryId",
"ObjectSecondaryID": "objectSecondaryId",
"ObjectForeignKey": "objectForeignKey",
"ObjectIndex": "objectIndex",
"ObjectIsNew": "objectIsNew",
"ObjectIsArchived": "objectIsArchived",
"ObjectStatusColumn": "objectStatusColumn",
"ObjectStatus": "objectStatus",
"AppEventType": "appEventType",
"Helper1": "helper1",
"Helper2": "helper2",
"Helper3": "helper3",
"Extra": "extra",
},
"Notifications": {
"Index": "index",
"GUID": "guid",
"DateTimeCreated": "dateTimeCreated",
"DateTimePushed": "dateTimePushed",
"Status": "status",
"JSON": "json",
"Text": "text",
"HTML": "html",
"PublishedVia": "publishedVia",
"Extra": "extra",
},
}
def migrate_to_camelcase(sql) -> bool:
"""
Detects legacy (underscore/PascalCase) column names and renames them
to camelCase using ALTER TABLE … RENAME COLUMN (SQLite ≥ 3.25.0).
Idempotent: columns already matching the new name are silently skipped.
"""
# Quick probe: if Events table has 'eveMac' we're already on the new schema
sql.execute('PRAGMA table_info("Events")')
events_cols = {row[1] for row in sql.fetchall()}
if "eveMac" in events_cols:
mylog("verbose", ["[db_upgrade] Schema already uses camelCase — skipping migration"])
return True
if "eve_MAC" not in events_cols:
# Events table doesn't exist or has unexpected schema — skip silently
mylog("verbose", ["[db_upgrade] Events table missing/unrecognised — skipping camelCase migration"])
return True
mylog("none", ["[db_upgrade] Starting camelCase column migration …"])
# Drop views first — ALTER TABLE RENAME COLUMN will fail if a view
# references the old column name and the view SQL cannot be rewritten.
for view_name in ("Events_Devices", "LatestEventsPerMAC", "Sessions_Devices",
"Convert_Events_to_Sessions", "LatestDeviceScan", "DevicesView"):
sql.execute(f"DROP VIEW IF EXISTS {view_name};")
renamed_count = 0
for table, column_map in _CAMELCASE_COLUMN_MAP.items():
# Check table exists
sql.execute("SELECT name FROM sqlite_master WHERE type='table' AND name=?", (table,))
if not sql.fetchone():
mylog("verbose", [f"[db_upgrade] Table '{table}' does not exist — skipping"])
continue
# Get current column names (case-preserved)
sql.execute(f'PRAGMA table_info("{table}")')
current_cols = {row[1] for row in sql.fetchall()}
for old_name, new_name in column_map.items():
if old_name in current_cols and new_name not in current_cols:
sql.execute(f'ALTER TABLE "{table}" RENAME COLUMN "{old_name}" TO "{new_name}"')
renamed_count += 1
mylog("verbose", [f"[db_upgrade] {table}.{old_name}{new_name}"])
mylog("none", [f"[db_upgrade] ✓ camelCase migration complete — {renamed_count} columns renamed"])
return True
# ===============================================================================
# UTC Timestamp Migration (added 2026-02-10)
# ===============================================================================
@@ -817,17 +1018,18 @@ def migrate_timestamps_to_utc(sql) -> bool:
mylog("verbose", f"[db_upgrade] Starting UTC timestamp migration (offset: {offset_hours} hours)")
# List of tables and their datetime columns
# List of tables and their datetime columns (camelCase names —
# migrate_to_camelcase() runs before this function).
timestamp_columns = {
'Devices': ['devFirstConnection', 'devLastConnection', 'devLastNotification'],
'Events': ['eve_DateTime'],
'Sessions': ['ses_DateTimeConnection', 'ses_DateTimeDisconnection'],
'Notifications': ['DateTimeCreated', 'DateTimePushed'],
'Online_History': ['Scan_Date'],
'Plugins_Objects': ['DateTimeCreated', 'DateTimeChanged'],
'Plugins_Events': ['DateTimeCreated', 'DateTimeChanged'],
'Plugins_History': ['DateTimeCreated', 'DateTimeChanged'],
'AppEvents': ['DateTimeCreated'],
'Events': ['eveDateTime'],
'Sessions': ['sesDateTimeConnection', 'sesDateTimeDisconnection'],
'Notifications': ['dateTimeCreated', 'dateTimePushed'],
'Online_History': ['scanDate'],
'Plugins_Objects': ['dateTimeCreated', 'dateTimeChanged'],
'Plugins_Events': ['dateTimeCreated', 'dateTimeChanged'],
'Plugins_History': ['dateTimeCreated', 'dateTimeChanged'],
'AppEvents': ['dateTimeCreated'],
}
for table, columns in timestamp_columns.items():

View File

@@ -1,14 +1,14 @@
CREATE TABLE Events (eve_MAC STRING (50) NOT NULL COLLATE NOCASE, eve_IP STRING (50) NOT NULL COLLATE NOCASE, eve_DateTime DATETIME NOT NULL, eve_EventType STRING (30) NOT NULL COLLATE NOCASE, eve_AdditionalInfo STRING (250) DEFAULT (''), eve_PendingAlertEmail BOOLEAN NOT NULL CHECK (eve_PendingAlertEmail IN (0, 1)) DEFAULT (1), eve_PairEventRowid INTEGER);
CREATE TABLE Sessions (ses_MAC STRING (50) COLLATE NOCASE, ses_IP STRING (50) COLLATE NOCASE, ses_EventTypeConnection STRING (30) COLLATE NOCASE, ses_DateTimeConnection DATETIME, ses_EventTypeDisconnection STRING (30) COLLATE NOCASE, ses_DateTimeDisconnection DATETIME, ses_StillConnected BOOLEAN, ses_AdditionalInfo STRING (250));
CREATE TABLE IF NOT EXISTS "Online_History" (
"Index" INTEGER,
"Scan_Date" TEXT,
"Online_Devices" INTEGER,
"Down_Devices" INTEGER,
"All_Devices" INTEGER,
"Archived_Devices" INTEGER,
"Offline_Devices" INTEGER,
PRIMARY KEY("Index" AUTOINCREMENT)
CREATE TABLE Events (eveMac STRING (50) NOT NULL COLLATE NOCASE, eveIp STRING (50) NOT NULL COLLATE NOCASE, eveDateTime DATETIME NOT NULL, eveEventType STRING (30) NOT NULL COLLATE NOCASE, eveAdditionalInfo STRING (250) DEFAULT (''), evePendingAlertEmail BOOLEAN NOT NULL CHECK (evePendingAlertEmail IN (0, 1)) DEFAULT (1), evePairEventRowid INTEGER);
CREATE TABLE Sessions (sesMac STRING (50) COLLATE NOCASE, sesIp STRING (50) COLLATE NOCASE, sesEventTypeConnection STRING (30) COLLATE NOCASE, sesDateTimeConnection DATETIME, sesEventTypeDisconnection STRING (30) COLLATE NOCASE, sesDateTimeDisconnection DATETIME, sesStillConnected BOOLEAN, sesAdditionalInfo STRING (250));
CREATE TABLE IF NOT EXISTS Online_History (
"index" INTEGER,
scanDate TEXT,
onlineDevices INTEGER,
downDevices INTEGER,
allDevices INTEGER,
archivedDevices INTEGER,
offlineDevices INTEGER,
PRIMARY KEY("index" AUTOINCREMENT)
);
CREATE TABLE Devices (
devMac STRING (50) PRIMARY KEY NOT NULL COLLATE NOCASE,
@@ -57,96 +57,98 @@ CREATE TABLE Devices (
devParentPortSource TEXT,
devParentRelTypeSource TEXT,
devVlanSource TEXT,
"devCustomProps" TEXT);
CREATE TABLE IF NOT EXISTS "Settings" (
"setKey" TEXT,
"setName" TEXT,
"setDescription" TEXT,
"setType" TEXT,
"setOptions" TEXT,
"setGroup" TEXT,
"setValue" TEXT,
"setEvents" TEXT,
"setOverriddenByEnv" INTEGER
devCustomProps TEXT);
CREATE TABLE IF NOT EXISTS Settings (
setKey TEXT,
setName TEXT,
setDescription TEXT,
setType TEXT,
setOptions TEXT,
setGroup TEXT,
setValue TEXT,
setEvents TEXT,
setOverriddenByEnv INTEGER
);
CREATE TABLE IF NOT EXISTS "Parameters" (
"parID" TEXT PRIMARY KEY,
"parValue" TEXT
CREATE TABLE IF NOT EXISTS Parameters (
parID TEXT PRIMARY KEY,
parValue TEXT
);
CREATE TABLE Plugins_Objects(
"Index" INTEGER,
Plugin TEXT NOT NULL,
Object_PrimaryID TEXT NOT NULL,
Object_SecondaryID TEXT NOT NULL,
DateTimeCreated TEXT NOT NULL,
DateTimeChanged TEXT NOT NULL,
Watched_Value1 TEXT NOT NULL,
Watched_Value2 TEXT NOT NULL,
Watched_Value3 TEXT NOT NULL,
Watched_Value4 TEXT NOT NULL,
Status TEXT NOT NULL,
Extra TEXT NOT NULL,
UserData TEXT NOT NULL,
ForeignKey TEXT NOT NULL,
SyncHubNodeName TEXT,
"HelpVal1" TEXT,
"HelpVal2" TEXT,
"HelpVal3" TEXT,
"HelpVal4" TEXT,
ObjectGUID TEXT,
PRIMARY KEY("Index" AUTOINCREMENT)
"index" INTEGER,
plugin TEXT NOT NULL,
objectPrimaryId TEXT NOT NULL,
objectSecondaryId TEXT NOT NULL,
dateTimeCreated TEXT NOT NULL,
dateTimeChanged TEXT NOT NULL,
watchedValue1 TEXT NOT NULL,
watchedValue2 TEXT NOT NULL,
watchedValue3 TEXT NOT NULL,
watchedValue4 TEXT NOT NULL,
"status" TEXT NOT NULL,
extra TEXT NOT NULL,
userData TEXT NOT NULL,
foreignKey TEXT NOT NULL,
syncHubNodeName TEXT,
helpVal1 TEXT,
helpVal2 TEXT,
helpVal3 TEXT,
helpVal4 TEXT,
objectGuid TEXT,
PRIMARY KEY("index" AUTOINCREMENT)
);
CREATE TABLE Plugins_Events(
"Index" INTEGER,
Plugin TEXT NOT NULL,
Object_PrimaryID TEXT NOT NULL,
Object_SecondaryID TEXT NOT NULL,
DateTimeCreated TEXT NOT NULL,
DateTimeChanged TEXT NOT NULL,
Watched_Value1 TEXT NOT NULL,
Watched_Value2 TEXT NOT NULL,
Watched_Value3 TEXT NOT NULL,
Watched_Value4 TEXT NOT NULL,
Status TEXT NOT NULL,
Extra TEXT NOT NULL,
UserData TEXT NOT NULL,
ForeignKey TEXT NOT NULL,
SyncHubNodeName TEXT,
"HelpVal1" TEXT,
"HelpVal2" TEXT,
"HelpVal3" TEXT,
"HelpVal4" TEXT, "ObjectGUID" TEXT,
PRIMARY KEY("Index" AUTOINCREMENT)
"index" INTEGER,
plugin TEXT NOT NULL,
objectPrimaryId TEXT NOT NULL,
objectSecondaryId TEXT NOT NULL,
dateTimeCreated TEXT NOT NULL,
dateTimeChanged TEXT NOT NULL,
watchedValue1 TEXT NOT NULL,
watchedValue2 TEXT NOT NULL,
watchedValue3 TEXT NOT NULL,
watchedValue4 TEXT NOT NULL,
"status" TEXT NOT NULL,
extra TEXT NOT NULL,
userData TEXT NOT NULL,
foreignKey TEXT NOT NULL,
syncHubNodeName TEXT,
helpVal1 TEXT,
helpVal2 TEXT,
helpVal3 TEXT,
helpVal4 TEXT,
objectGuid TEXT,
PRIMARY KEY("index" AUTOINCREMENT)
);
CREATE TABLE Plugins_History(
"Index" INTEGER,
Plugin TEXT NOT NULL,
Object_PrimaryID TEXT NOT NULL,
Object_SecondaryID TEXT NOT NULL,
DateTimeCreated TEXT NOT NULL,
DateTimeChanged TEXT NOT NULL,
Watched_Value1 TEXT NOT NULL,
Watched_Value2 TEXT NOT NULL,
Watched_Value3 TEXT NOT NULL,
Watched_Value4 TEXT NOT NULL,
Status TEXT NOT NULL,
Extra TEXT NOT NULL,
UserData TEXT NOT NULL,
ForeignKey TEXT NOT NULL,
SyncHubNodeName TEXT,
"HelpVal1" TEXT,
"HelpVal2" TEXT,
"HelpVal3" TEXT,
"HelpVal4" TEXT, "ObjectGUID" TEXT,
PRIMARY KEY("Index" AUTOINCREMENT)
"index" INTEGER,
plugin TEXT NOT NULL,
objectPrimaryId TEXT NOT NULL,
objectSecondaryId TEXT NOT NULL,
dateTimeCreated TEXT NOT NULL,
dateTimeChanged TEXT NOT NULL,
watchedValue1 TEXT NOT NULL,
watchedValue2 TEXT NOT NULL,
watchedValue3 TEXT NOT NULL,
watchedValue4 TEXT NOT NULL,
"status" TEXT NOT NULL,
extra TEXT NOT NULL,
userData TEXT NOT NULL,
foreignKey TEXT NOT NULL,
syncHubNodeName TEXT,
helpVal1 TEXT,
helpVal2 TEXT,
helpVal3 TEXT,
helpVal4 TEXT,
objectGuid TEXT,
PRIMARY KEY("index" AUTOINCREMENT)
);
CREATE TABLE Plugins_Language_Strings(
"Index" INTEGER,
Language_Code TEXT NOT NULL,
String_Key TEXT NOT NULL,
String_Value TEXT NOT NULL,
Extra TEXT NOT NULL,
PRIMARY KEY("Index" AUTOINCREMENT)
"index" INTEGER,
languageCode TEXT NOT NULL,
stringKey TEXT NOT NULL,
stringValue TEXT NOT NULL,
extra TEXT NOT NULL,
PRIMARY KEY("index" AUTOINCREMENT)
);
CREATE TABLE CurrentScan (
scanMac STRING(50) NOT NULL COLLATE NOCASE,
@@ -165,50 +167,50 @@ CREATE TABLE CurrentScan (
scanType STRING(250),
UNIQUE(scanMac)
);
CREATE TABLE IF NOT EXISTS "AppEvents" (
"Index" INTEGER PRIMARY KEY AUTOINCREMENT,
"GUID" TEXT UNIQUE,
"AppEventProcessed" BOOLEAN,
"DateTimeCreated" TEXT,
"ObjectType" TEXT,
"ObjectGUID" TEXT,
"ObjectPlugin" TEXT,
"ObjectPrimaryID" TEXT,
"ObjectSecondaryID" TEXT,
"ObjectForeignKey" TEXT,
"ObjectIndex" TEXT,
"ObjectIsNew" BOOLEAN,
"ObjectIsArchived" BOOLEAN,
"ObjectStatusColumn" TEXT,
"ObjectStatus" TEXT,
"AppEventType" TEXT,
"Helper1" TEXT,
"Helper2" TEXT,
"Helper3" TEXT,
"Extra" TEXT
CREATE TABLE IF NOT EXISTS AppEvents (
"index" INTEGER PRIMARY KEY AUTOINCREMENT,
guid TEXT UNIQUE,
appEventProcessed BOOLEAN,
dateTimeCreated TEXT,
objectType TEXT,
objectGuid TEXT,
objectPlugin TEXT,
objectPrimaryId TEXT,
objectSecondaryId TEXT,
objectForeignKey TEXT,
objectIndex TEXT,
objectIsNew BOOLEAN,
objectIsArchived BOOLEAN,
objectStatusColumn TEXT,
objectStatus TEXT,
appEventType TEXT,
helper1 TEXT,
helper2 TEXT,
helper3 TEXT,
extra TEXT
);
CREATE TABLE IF NOT EXISTS "Notifications" (
"Index" INTEGER,
"GUID" TEXT UNIQUE,
"DateTimeCreated" TEXT,
"DateTimePushed" TEXT,
"Status" TEXT,
"JSON" TEXT,
"Text" TEXT,
"HTML" TEXT,
"PublishedVia" TEXT,
"Extra" TEXT,
PRIMARY KEY("Index" AUTOINCREMENT)
CREATE TABLE IF NOT EXISTS Notifications (
"index" INTEGER,
guid TEXT UNIQUE,
dateTimeCreated TEXT,
dateTimePushed TEXT,
"status" TEXT,
"json" TEXT,
"text" TEXT,
html TEXT,
publishedVia TEXT,
extra TEXT,
PRIMARY KEY("index" AUTOINCREMENT)
);
CREATE INDEX IDX_eve_DateTime ON Events (eve_DateTime);
CREATE INDEX IDX_eve_EventType ON Events (eve_EventType COLLATE NOCASE);
CREATE INDEX IDX_eve_MAC ON Events (eve_MAC COLLATE NOCASE);
CREATE INDEX IDX_eve_PairEventRowid ON Events (eve_PairEventRowid);
CREATE INDEX IDX_ses_EventTypeDisconnection ON Sessions (ses_EventTypeDisconnection COLLATE NOCASE);
CREATE INDEX IDX_ses_EventTypeConnection ON Sessions (ses_EventTypeConnection COLLATE NOCASE);
CREATE INDEX IDX_ses_DateTimeDisconnection ON Sessions (ses_DateTimeDisconnection);
CREATE INDEX IDX_ses_MAC ON Sessions (ses_MAC COLLATE NOCASE);
CREATE INDEX IDX_ses_DateTimeConnection ON Sessions (ses_DateTimeConnection);
CREATE INDEX IDX_eve_DateTime ON Events (eveDateTime);
CREATE INDEX IDX_eve_EventType ON Events (eveEventType COLLATE NOCASE);
CREATE INDEX IDX_eve_MAC ON Events (eveMac COLLATE NOCASE);
CREATE INDEX IDX_eve_PairEventRowid ON Events (evePairEventRowid);
CREATE INDEX IDX_ses_EventTypeDisconnection ON Sessions (sesEventTypeDisconnection COLLATE NOCASE);
CREATE INDEX IDX_ses_EventTypeConnection ON Sessions (sesEventTypeConnection COLLATE NOCASE);
CREATE INDEX IDX_ses_DateTimeDisconnection ON Sessions (sesDateTimeDisconnection);
CREATE INDEX IDX_ses_MAC ON Sessions (sesMac COLLATE NOCASE);
CREATE INDEX IDX_ses_DateTimeConnection ON Sessions (sesDateTimeConnection);
CREATE INDEX IDX_dev_PresentLastScan ON Devices (devPresentLastScan);
CREATE INDEX IDX_dev_FirstConnection ON Devices (devFirstConnection);
CREATE INDEX IDX_dev_AlertDeviceDown ON Devices (devAlertDown);
@@ -220,21 +222,20 @@ CREATE INDEX IDX_dev_NewDevice ON Devices (devIsNew);
CREATE INDEX IDX_dev_Archived ON Devices (devIsArchived);
CREATE UNIQUE INDEX IF NOT EXISTS idx_events_unique
ON Events (
eve_MAC,
eve_IP,
eve_EventType,
eve_DateTime
eveMac,
eveIp,
eveEventType,
eveDateTime
);
CREATE VIEW Events_Devices AS
SELECT *
FROM Events
LEFT JOIN Devices ON eve_MAC = devMac
/* Events_Devices(eve_MAC,eve_IP,eve_DateTime,eve_EventType,eve_AdditionalInfo,eve_PendingAlertEmail,eve_PairEventRowid,devMac,devName,devOwner,devType,devVendor,devFavorite,devGroup,devComments,devFirstConnection,devLastConnection,devLastIP,devStaticIP,devScan,devLogEvents,devAlertEvents,devAlertDown,devSkipRepeated,devLastNotification,devPresentLastScan,devIsNew,devLocation,devIsArchived,devParentMAC,devParentPort,devIcon,devGUID,devSite,devSSID,devSyncHubNode,devSourcePlugin,devCustomProps) */;
LEFT JOIN Devices ON eveMac = devMac;
CREATE VIEW LatestEventsPerMAC AS
WITH RankedEvents AS (
SELECT
e.*,
ROW_NUMBER() OVER (PARTITION BY e.eve_MAC ORDER BY e.eve_DateTime DESC) AS row_num
ROW_NUMBER() OVER (PARTITION BY e.eveMac ORDER BY e.eveDateTime DESC) AS row_num
FROM Events AS e
)
SELECT
@@ -242,192 +243,33 @@ CREATE VIEW LatestEventsPerMAC AS
d.*,
c.*
FROM RankedEvents AS e
LEFT JOIN Devices AS d ON e.eve_MAC = d.devMac
INNER JOIN CurrentScan AS c ON e.eve_MAC = c.scanMac
WHERE e.row_num = 1
/* LatestEventsPerMAC(eve_MAC,eve_IP,eve_DateTime,eve_EventType,eve_AdditionalInfo,eve_PendingAlertEmail,eve_PairEventRowid,row_num,devMac,devName,devOwner,devType,devVendor,devFavorite,devGroup,devComments,devFirstConnection,devLastConnection,devLastIP,devStaticIP,devScan,devLogEvents,devAlertEvents,devAlertDown,devSkipRepeated,devLastNotification,devPresentLastScan,devIsNew,devLocation,devIsArchived,devParentMAC,devParentPort,devIcon,devGUID,devSite,devSSID,devSyncHubNode,devSourcePlugin,devCustomProps,scanMac,scanLastIP,scanVendor,scanSourcePlugin,scanName,scanLastQuery,scanLastConnection,scanSyncHubNode,scanSite,scanSSID,scanParentMAC,scanParentPort,scanType) */;
CREATE VIEW Sessions_Devices AS SELECT * FROM Sessions LEFT JOIN "Devices" ON ses_MAC = devMac
/* Sessions_Devices(ses_MAC,ses_IP,ses_EventTypeConnection,ses_DateTimeConnection,ses_EventTypeDisconnection,ses_DateTimeDisconnection,ses_StillConnected,ses_AdditionalInfo,devMac,devName,devOwner,devType,devVendor,devFavorite,devGroup,devComments,devFirstConnection,devLastConnection,devLastIP,devStaticIP,devScan,devLogEvents,devAlertEvents,devAlertDown,devSkipRepeated,devLastNotification,devPresentLastScan,devIsNew,devLocation,devIsArchived,devParentMAC,devParentPort,devIcon,devGUID,devSite,devSSID,devSyncHubNode,devSourcePlugin,devCustomProps) */;
CREATE VIEW Convert_Events_to_Sessions AS SELECT EVE1.eve_MAC,
EVE1.eve_IP,
EVE1.eve_EventType AS eve_EventTypeConnection,
EVE1.eve_DateTime AS eve_DateTimeConnection,
CASE WHEN EVE2.eve_EventType IN ('Disconnected', 'Device Down') OR
EVE2.eve_EventType IS NULL THEN EVE2.eve_EventType ELSE '<missing event>' END AS eve_EventTypeDisconnection,
CASE WHEN EVE2.eve_EventType IN ('Disconnected', 'Device Down') THEN EVE2.eve_DateTime ELSE NULL END AS eve_DateTimeDisconnection,
CASE WHEN EVE2.eve_EventType IS NULL THEN 1 ELSE 0 END AS eve_StillConnected,
EVE1.eve_AdditionalInfo
LEFT JOIN Devices AS d ON e.eveMac = d.devMac
INNER JOIN CurrentScan AS c ON e.eveMac = c.scanMac
WHERE e.row_num = 1;
CREATE VIEW Sessions_Devices AS SELECT * FROM Sessions LEFT JOIN Devices ON sesMac = devMac;
CREATE VIEW Convert_Events_to_Sessions AS SELECT EVE1.eveMac,
EVE1.eveIp,
EVE1.eveEventType AS eveEventTypeConnection,
EVE1.eveDateTime AS eveDateTimeConnection,
CASE WHEN EVE2.eveEventType IN ('Disconnected', 'Device Down') OR
EVE2.eveEventType IS NULL THEN EVE2.eveEventType ELSE '<missing event>' END AS eveEventTypeDisconnection,
CASE WHEN EVE2.eveEventType IN ('Disconnected', 'Device Down') THEN EVE2.eveDateTime ELSE NULL END AS eveDateTimeDisconnection,
CASE WHEN EVE2.eveEventType IS NULL THEN 1 ELSE 0 END AS eveStillConnected,
EVE1.eveAdditionalInfo
FROM Events AS EVE1
LEFT JOIN
Events AS EVE2 ON EVE1.eve_PairEventRowID = EVE2.RowID
WHERE EVE1.eve_EventType IN ('New Device', 'Connected','Down Reconnected')
Events AS EVE2 ON EVE1.evePairEventRowid = EVE2.RowID
WHERE EVE1.eveEventType IN ('New Device', 'Connected','Down Reconnected')
UNION
SELECT eve_MAC,
eve_IP,
'<missing event>' AS eve_EventTypeConnection,
NULL AS eve_DateTimeConnection,
eve_EventType AS eve_EventTypeDisconnection,
eve_DateTime AS eve_DateTimeDisconnection,
0 AS eve_StillConnected,
eve_AdditionalInfo
SELECT eveMac,
eveIp,
'<missing event>' AS eveEventTypeConnection,
NULL AS eveDateTimeConnection,
eveEventType AS eveEventTypeDisconnection,
eveDateTime AS eveDateTimeDisconnection,
0 AS eveStillConnected,
eveAdditionalInfo
FROM Events AS EVE1
WHERE (eve_EventType = 'Device Down' OR
eve_EventType = 'Disconnected') AND
EVE1.eve_PairEventRowID IS NULL
/* Convert_Events_to_Sessions(eve_MAC,eve_IP,eve_EventTypeConnection,eve_DateTimeConnection,eve_EventTypeDisconnection,eve_DateTimeDisconnection,eve_StillConnected,eve_AdditionalInfo) */;
CREATE TRIGGER "trg_insert_devices"
AFTER INSERT ON "Devices"
WHEN NOT EXISTS (
SELECT 1 FROM AppEvents
WHERE AppEventProcessed = 0
AND ObjectType = 'Devices'
AND ObjectGUID = NEW.devGUID
AND ObjectStatus = CASE WHEN NEW.devPresentLastScan = 1 THEN 'online' ELSE 'offline' END
AND AppEventType = 'insert'
)
BEGIN
INSERT INTO "AppEvents" (
"GUID",
"DateTimeCreated",
"AppEventProcessed",
"ObjectType",
"ObjectGUID",
"ObjectPrimaryID",
"ObjectSecondaryID",
"ObjectStatus",
"ObjectStatusColumn",
"ObjectIsNew",
"ObjectIsArchived",
"ObjectForeignKey",
"ObjectPlugin",
"AppEventType"
)
VALUES (
lower(
hex(randomblob(4)) || '-' || hex(randomblob(2)) || '-' || '4' ||
substr(hex( randomblob(2)), 2) || '-' ||
substr('AB89', 1 + (abs(random()) % 4) , 1) ||
substr(hex(randomblob(2)), 2) || '-' ||
hex(randomblob(6))
)
,
DATETIME('now'),
FALSE,
'Devices',
NEW.devGUID, -- ObjectGUID
NEW.devMac, -- ObjectPrimaryID
NEW.devLastIP, -- ObjectSecondaryID
CASE WHEN NEW.devPresentLastScan = 1 THEN 'online' ELSE 'offline' END, -- ObjectStatus
'devPresentLastScan', -- ObjectStatusColumn
NEW.devIsNew, -- ObjectIsNew
NEW.devIsArchived, -- ObjectIsArchived
NEW.devGUID, -- ObjectForeignKey
'DEVICES', -- ObjectForeignKey
'insert'
);
END;
CREATE TRIGGER "trg_update_devices"
AFTER UPDATE ON "Devices"
WHEN NOT EXISTS (
SELECT 1 FROM AppEvents
WHERE AppEventProcessed = 0
AND ObjectType = 'Devices'
AND ObjectGUID = NEW.devGUID
AND ObjectStatus = CASE WHEN NEW.devPresentLastScan = 1 THEN 'online' ELSE 'offline' END
AND AppEventType = 'update'
)
BEGIN
INSERT INTO "AppEvents" (
"GUID",
"DateTimeCreated",
"AppEventProcessed",
"ObjectType",
"ObjectGUID",
"ObjectPrimaryID",
"ObjectSecondaryID",
"ObjectStatus",
"ObjectStatusColumn",
"ObjectIsNew",
"ObjectIsArchived",
"ObjectForeignKey",
"ObjectPlugin",
"AppEventType"
)
VALUES (
lower(
hex(randomblob(4)) || '-' || hex(randomblob(2)) || '-' || '4' ||
substr(hex( randomblob(2)), 2) || '-' ||
substr('AB89', 1 + (abs(random()) % 4) , 1) ||
substr(hex(randomblob(2)), 2) || '-' ||
hex(randomblob(6))
)
,
DATETIME('now'),
FALSE,
'Devices',
NEW.devGUID, -- ObjectGUID
NEW.devMac, -- ObjectPrimaryID
NEW.devLastIP, -- ObjectSecondaryID
CASE WHEN NEW.devPresentLastScan = 1 THEN 'online' ELSE 'offline' END, -- ObjectStatus
'devPresentLastScan', -- ObjectStatusColumn
NEW.devIsNew, -- ObjectIsNew
NEW.devIsArchived, -- ObjectIsArchived
NEW.devGUID, -- ObjectForeignKey
'DEVICES', -- ObjectForeignKey
'update'
);
END;
CREATE TRIGGER "trg_delete_devices"
AFTER DELETE ON "Devices"
WHEN NOT EXISTS (
SELECT 1 FROM AppEvents
WHERE AppEventProcessed = 0
AND ObjectType = 'Devices'
AND ObjectGUID = OLD.devGUID
AND ObjectStatus = CASE WHEN OLD.devPresentLastScan = 1 THEN 'online' ELSE 'offline' END
AND AppEventType = 'delete'
)
BEGIN
INSERT INTO "AppEvents" (
"GUID",
"DateTimeCreated",
"AppEventProcessed",
"ObjectType",
"ObjectGUID",
"ObjectPrimaryID",
"ObjectSecondaryID",
"ObjectStatus",
"ObjectStatusColumn",
"ObjectIsNew",
"ObjectIsArchived",
"ObjectForeignKey",
"ObjectPlugin",
"AppEventType"
)
VALUES (
lower(
hex(randomblob(4)) || '-' || hex(randomblob(2)) || '-' || '4' ||
substr(hex( randomblob(2)), 2) || '-' ||
substr('AB89', 1 + (abs(random()) % 4) , 1) ||
substr(hex(randomblob(2)), 2) || '-' ||
hex(randomblob(6))
)
,
DATETIME('now'),
FALSE,
'Devices',
OLD.devGUID, -- ObjectGUID
OLD.devMac, -- ObjectPrimaryID
OLD.devLastIP, -- ObjectSecondaryID
CASE WHEN OLD.devPresentLastScan = 1 THEN 'online' ELSE 'offline' END, -- ObjectStatus
'devPresentLastScan', -- ObjectStatusColumn
OLD.devIsNew, -- ObjectIsNew
OLD.devIsArchived, -- ObjectIsArchived
OLD.devGUID, -- ObjectForeignKey
'DEVICES', -- ObjectForeignKey
'delete'
);
END;
WHERE (eveEventType = 'Device Down' OR
eveEventType = 'Disconnected') AND
EVE1.evePairEventRowid IS NULL;

View File

@@ -29,10 +29,10 @@ class SafeConditionBuilder:
# Whitelist of allowed column names for filtering
ALLOWED_COLUMNS = {
"eve_MAC",
"eve_DateTime",
"eve_IP",
"eve_EventType",
"eveMac",
"eveDateTime",
"eveIp",
"eveEventType",
"devName",
"devComments",
"devLastIP",
@@ -43,15 +43,15 @@ class SafeConditionBuilder:
"devPresentLastScan",
"devFavorite",
"devIsNew",
"Plugin",
"Object_PrimaryId",
"Object_SecondaryId",
"DateTimeChanged",
"Watched_Value1",
"Watched_Value2",
"Watched_Value3",
"Watched_Value4",
"Status",
"plugin",
"objectPrimaryId",
"objectSecondaryId",
"dateTimeChanged",
"watchedValue1",
"watchedValue2",
"watchedValue3",
"watchedValue4",
"status",
}
# Whitelist of allowed comparison operators
@@ -413,7 +413,7 @@ class SafeConditionBuilder:
This method handles basic patterns like:
- devName = 'value' (with optional AND/OR prefix)
- devComments LIKE '%value%'
- eve_EventType IN ('type1', 'type2')
- eveEventType IN ('type1', 'type2')
Args:
condition: Single condition string to parse
@@ -648,7 +648,7 @@ class SafeConditionBuilder:
self.parameters[param_name] = event_type
param_names.append(f":{param_name}")
sql_snippet = f"AND eve_EventType IN ({', '.join(param_names)})"
sql_snippet = f"AND eveEventType IN ({', '.join(param_names)})"
return sql_snippet, self.parameters
def get_safe_condition_legacy(

View File

@@ -27,7 +27,7 @@ from messaging.in_app import write_notification
# ===============================================================================
_LANGUAGES_JSON = os.path.join(
applicationPath, "front", "php", "templates", "language", "language_definitions" ,"languages.json"
applicationPath, "front", "php", "templates", "language", "language_definitions", "languages.json"
)
@@ -204,6 +204,9 @@ def importConfigs(pm, db, all_plugins):
# rename settings that have changed names due to code cleanup and migration to plugins
# renameSettings(config_file)
# rename legacy DB column references in user config values (e.g. templates, WATCH lists)
renameColumnReferences(config_file)
fileModifiedTime = os.path.getmtime(config_file)
mylog("debug", ["[Import Config] checking config file "])
@@ -582,7 +585,7 @@ def importConfigs(pm, db, all_plugins):
# bulk-import language strings
sql.executemany(
"""INSERT INTO Plugins_Language_Strings ("Language_Code", "String_Key", "String_Value", "Extra") VALUES (?, ?, ?, ?)""",
"""INSERT INTO Plugins_Language_Strings (languageCode, stringKey, stringValue, extra) VALUES (?, ?, ?, ?)""",
stringSqlParams,
)
@@ -845,3 +848,85 @@ def renameSettings(config_file):
else:
mylog("debug", "[Config] No old setting names found in the file. No changes made.")
# -------------------------------------------------------------------------------
# Rename legacy DB column names in user-persisted config values (templates, WATCH lists, etc.)
# Follows the same backup-and-replace pattern as renameSettings().
_column_replacements = {
# Event columns
r"\beve_MAC\b": "eveMac",
r"\beve_IP\b": "eveIp",
r"\beve_DateTime\b": "eveDateTime",
r"\beve_EventType\b": "eveEventType",
r"\beve_AdditionalInfo\b": "eveAdditionalInfo",
r"\beve_PendingAlertEmail\b": "evePendingAlertEmail",
r"\beve_PairEventRowid\b": "evePairEventRowid",
r"\beve_PairEventRowID\b": "evePairEventRowid",
# Session columns
r"\bses_MAC\b": "sesMac",
r"\bses_IP\b": "sesIp",
r"\bses_DateTimeConnection\b": "sesDateTimeConnection",
r"\bses_DateTimeDisconnection\b": "sesDateTimeDisconnection",
r"\bses_EventTypeConnection\b": "sesEventTypeConnection",
r"\bses_EventTypeDisconnection\b": "sesEventTypeDisconnection",
r"\bses_StillConnected\b": "sesStillConnected",
r"\bses_AdditionalInfo\b": "sesAdditionalInfo",
# Plugin columns (templates + WATCH values)
r"\bObject_PrimaryID\b": "objectPrimaryId",
r"\bObject_PrimaryId\b": "objectPrimaryId",
r"\bObjectPrimaryID\b": "objectPrimaryId",
r"\bObject_SecondaryID\b": "objectSecondaryId",
r"\bObject_SecondaryId\b": "objectSecondaryId",
r"\bObjectSecondaryID\b": "objectSecondaryId",
r"\bWatched_Value1\b": "watchedValue1",
r"\bWatched_Value2\b": "watchedValue2",
r"\bWatched_Value3\b": "watchedValue3",
r"\bWatched_Value4\b": "watchedValue4",
r"\bDateTimeChanged\b": "dateTimeChanged",
r"\bDateTimeCreated\b": "dateTimeCreated",
r"\bSyncHubNodeName\b": "syncHubNodeName",
# Online_History (in case of API_CUSTOM_SQL)
r"\bScan_Date\b": "scanDate",
r"\bOnline_Devices\b": "onlineDevices",
r"\bDown_Devices\b": "downDevices",
r"\bAll_Devices\b": "allDevices",
r"\bArchived_Devices\b": "archivedDevices",
r"\bOffline_Devices\b": "offlineDevices",
# Language strings (unlikely in user config but thorough)
r"\bLanguage_Code\b": "languageCode",
r"\bString_Key\b": "stringKey",
r"\bString_Value\b": "stringValue",
}
def renameColumnReferences(config_file):
"""Rename legacy DB column references in the user's app.conf file."""
contains_old_refs = False
with open(str(config_file), "r") as f:
for line in f:
if any(re.search(key, line) for key in _column_replacements):
mylog("debug", f"[Config] Old column reference found: ({line.strip()})")
contains_old_refs = True
break
if not contains_old_refs:
mylog("debug", "[Config] No old column references found in config. No changes made.")
return
timestamp = timeNowUTC(as_string=False).strftime("%Y%m%d%H%M%S")
backup_file = f"{config_file}_old_column_names_{timestamp}.bak"
mylog("none", f"[Config] Renaming legacy column references — backup: {backup_file}")
shutil.copy(str(config_file), backup_file)
with (
open(str(config_file), "r") as original,
open(str(config_file) + "_temp", "w") as temp,
):
for line in original:
for pattern, replacement in _column_replacements.items():
line = re.sub(pattern, replacement, line)
temp.write(line)
shutil.move(str(config_file) + "_temp", str(config_file))

View File

@@ -0,0 +1,140 @@
# -------------------------------------------------------------------------------
# notification_sections.py — Single source of truth for notification section
# metadata: titles, SQL templates, datetime fields, and section ordering.
#
# Both reporting.py and notification_instance.py import from here.
# -------------------------------------------------------------------------------
# Canonical processing order
SECTION_ORDER = [
"new_devices",
"down_devices",
"down_reconnected",
"events",
"plugins",
]
# Section display titles (used in text + HTML notifications)
SECTION_TITLES = {
"new_devices": "🆕 New devices",
"down_devices": "🔴 Down devices",
"down_reconnected": "🔁 Reconnected down devices",
"events": "⚡ Events",
"plugins": "🔌 Plugins",
}
# Which column(s) contain datetime values per section (for timezone conversion)
DATETIME_FIELDS = {
"new_devices": ["eveDateTime"],
"down_devices": ["eveDateTime"],
"down_reconnected": ["eveDateTime"],
"events": ["eveDateTime"],
"plugins": ["dateTimeChanged"],
}
# ---------------------------------------------------------------------------
# SQL templates
#
# All device sections use unified DB column names so the JSON output
# has consistent field names across new_devices, down_devices,
# down_reconnected, and events.
#
# Placeholders:
# {condition} — optional WHERE clause appended by condition builder
# {alert_down_minutes} — runtime value, only used by down_devices
# ---------------------------------------------------------------------------
SQL_TEMPLATES = {
"new_devices": """
SELECT
devName,
eveMac,
devVendor,
devLastIP as eveIp,
eveDateTime,
eveEventType,
devComments
FROM Events_Devices
WHERE evePendingAlertEmail = 1
AND eveEventType = 'New Device' {condition}
ORDER BY eveDateTime
""",
"down_devices": """
SELECT
devName,
eveMac,
devVendor,
eveIp,
eveDateTime,
eveEventType,
devComments
FROM Events_Devices AS down_events
WHERE evePendingAlertEmail = 1
AND down_events.eveEventType = 'Device Down'
AND eveDateTime < datetime('now', '-{alert_down_minutes} minutes')
AND NOT EXISTS (
SELECT 1
FROM Events AS connected_events
WHERE connected_events.eveMac = down_events.eveMac
AND connected_events.eveEventType = 'Connected'
AND connected_events.eveDateTime > down_events.eveDateTime
)
ORDER BY down_events.eveDateTime
""",
"down_reconnected": """
SELECT
devName,
reconnected_devices.eveMac,
devVendor,
reconnected_devices.eveIp,
reconnected_devices.eveDateTime,
reconnected_devices.eveEventType,
devComments
FROM Events_Devices AS reconnected_devices
WHERE reconnected_devices.eveEventType = 'Down Reconnected'
AND reconnected_devices.evePendingAlertEmail = 1
AND NOT EXISTS (
SELECT 1 FROM Events AS newer
WHERE newer.eveMac = reconnected_devices.eveMac
AND newer.eveEventType = 'Down Reconnected'
AND newer.evePendingAlertEmail = 1
AND newer.eveDateTime > reconnected_devices.eveDateTime
)
ORDER BY reconnected_devices.eveDateTime
""",
"events": """
SELECT
devName,
eveMac,
devVendor,
devLastIP as eveIp,
eveDateTime,
eveEventType,
devComments
FROM Events_Devices
WHERE evePendingAlertEmail = 1
AND eveEventType IN ({event_types}) {condition}
ORDER BY eveDateTime
""",
"plugins": """
SELECT
plugin,
objectPrimaryId,
objectSecondaryId,
dateTimeChanged,
watchedValue1,
watchedValue2,
watchedValue3,
watchedValue4,
status
FROM Plugins_Events
""",
}
# Sections that support user-defined condition filters
SECTIONS_WITH_CONDITIONS = {"new_devices", "events"}
# Legacy setting key mapping for condition filters
SECTION_CONDITION_MAP = {
"new_devices": "NTFPRCS_new_dev_condition",
"events": "NTFPRCS_event_condition",
}

View File

@@ -25,20 +25,20 @@ from helper import ( # noqa: E402 [flake8 lint suppression]
from logger import mylog # noqa: E402 [flake8 lint suppression]
from db.sql_safe_builder import create_safe_condition_builder # noqa: E402 [flake8 lint suppression]
from utils.datetime_utils import format_date_iso # noqa: E402 [flake8 lint suppression]
from messaging.notification_sections import ( # noqa: E402 [flake8 lint suppression]
SECTION_ORDER,
SECTION_TITLES,
DATETIME_FIELDS,
SQL_TEMPLATES,
SECTIONS_WITH_CONDITIONS,
SECTION_CONDITION_MAP,
)
import conf # noqa: E402 [flake8 lint suppression]
# ===============================================================================
# Timezone conversion
# ===============================================================================
DATETIME_FIELDS = {
"new_devices": ["Datetime"],
"down_devices": ["eve_DateTime"],
"down_reconnected": ["eve_DateTime"],
"events": ["Datetime"],
"plugins": ["DateTimeChanged"],
}
def get_datetime_fields_from_columns(column_names):
return [
@@ -114,16 +114,16 @@ def get_notifications(db):
# Disable events where reporting is disabled
sql.execute("""
UPDATE Events SET eve_PendingAlertEmail = 0
WHERE eve_PendingAlertEmail = 1
AND eve_EventType NOT IN ('Device Down', 'Down Reconnected', 'New Device')
AND eve_MAC IN (SELECT devMac FROM Devices WHERE devAlertEvents = 0)
UPDATE Events SET evePendingAlertEmail = 0
WHERE evePendingAlertEmail = 1
AND eveEventType NOT IN ('Device Down', 'Down Reconnected', 'New Device')
AND eveMac IN (SELECT devMac FROM Devices WHERE devAlertEvents = 0)
""")
sql.execute("""
UPDATE Events SET eve_PendingAlertEmail = 0
WHERE eve_PendingAlertEmail = 1
AND eve_EventType IN ('Device Down', 'Down Reconnected')
AND eve_MAC IN (SELECT devMac FROM Devices WHERE devAlertDown = 0)
UPDATE Events SET evePendingAlertEmail = 0
WHERE evePendingAlertEmail = 1
AND eveEventType IN ('Device Down', 'Down Reconnected')
AND eveMac IN (SELECT devMac FROM Devices WHERE devAlertDown = 0)
""")
alert_down_minutes = int(get_setting_value("NTFPRCS_alert_down_time") or 0)
@@ -157,103 +157,15 @@ def get_notifications(db):
return ""
# -------------------------
# SQL templates
# -------------------------
sql_templates = {
"new_devices": """
SELECT
eve_MAC as MAC,
eve_DateTime as Datetime,
devLastIP as IP,
eve_EventType as "Event Type",
devName as "Device name",
devComments as Comments
FROM Events_Devices
WHERE eve_PendingAlertEmail = 1
AND eve_EventType = 'New Device' {condition}
ORDER BY eve_DateTime
""",
"down_devices": f"""
SELECT
devName,
eve_MAC,
devVendor,
eve_IP,
eve_DateTime,
eve_EventType
FROM Events_Devices AS down_events
WHERE eve_PendingAlertEmail = 1
AND down_events.eve_EventType = 'Device Down'
AND eve_DateTime < datetime('now', '-{alert_down_minutes} minutes')
AND NOT EXISTS (
SELECT 1
FROM Events AS connected_events
WHERE connected_events.eve_MAC = down_events.eve_MAC
AND connected_events.eve_EventType = 'Connected'
AND connected_events.eve_DateTime > down_events.eve_DateTime
)
ORDER BY down_events.eve_DateTime
""",
"down_reconnected": """
SELECT
devName,
eve_MAC,
devVendor,
eve_IP,
eve_DateTime,
eve_EventType
FROM Events_Devices AS reconnected_devices
WHERE reconnected_devices.eve_EventType = 'Down Reconnected'
AND reconnected_devices.eve_PendingAlertEmail = 1
ORDER BY reconnected_devices.eve_DateTime
""",
"events": """
SELECT
eve_MAC as MAC,
eve_DateTime as Datetime,
devLastIP as IP,
eve_EventType as "Event Type",
devName as "Device name",
devComments as Comments
FROM Events_Devices
WHERE eve_PendingAlertEmail = 1
AND eve_EventType IN ('Connected', 'Down Reconnected', 'Disconnected','IP Changed') {condition}
ORDER BY eve_DateTime
""",
"plugins": """
SELECT
Plugin,
Object_PrimaryId,
Object_SecondaryId,
DateTimeChanged,
Watched_Value1,
Watched_Value2,
Watched_Value3,
Watched_Value4,
Status
FROM Plugins_Events
"""
}
# Titles for metadata
section_titles = {
"new_devices": "🆕 New devices",
"down_devices": "🔴 Down devices",
"down_reconnected": "🔁 Reconnected down devices",
"events": "⚡ Events",
"plugins": "🔌 Plugins"
}
# Sections that support dynamic conditions
sections_with_conditions = {"new_devices", "events"}
# SQL templates with placeholders for runtime values
# {condition} and {alert_down_minutes} are formatted at query time
# Initialize final structure
final_json = {}
for section in ["new_devices", "down_devices", "down_reconnected", "events", "plugins"]:
for section in SECTION_ORDER:
final_json[section] = []
final_json[f"{section}_meta"] = {
"title": section_titles.get(section, section),
"title": SECTION_TITLES.get(section, section),
"columnNames": []
}
@@ -262,17 +174,8 @@ def get_notifications(db):
# -------------------------
# Main loop
# -------------------------
condition_builder = create_safe_condition_builder()
SECTION_CONDITION_MAP = {
"new_devices": "NTFPRCS_new_dev_condition",
"events": "NTFPRCS_event_condition",
}
sections_with_conditions = set(SECTION_CONDITION_MAP.keys())
for section in sections:
template = sql_templates.get(section)
template = SQL_TEMPLATES.get(section)
if not template:
mylog("verbose", ["[Notification] Unknown section: ", section])
@@ -282,7 +185,7 @@ def get_notifications(db):
parameters = {}
try:
if section in sections_with_conditions:
if section in SECTIONS_WITH_CONDITIONS:
condition_key = SECTION_CONDITION_MAP.get(section)
condition_setting = get_setting_value(condition_key)
@@ -291,11 +194,31 @@ def get_notifications(db):
condition_setting
)
sqlQuery = template.format(condition=safe_condition)
# Format template with runtime placeholders
format_vars = {"condition": safe_condition}
if section == "down_devices":
format_vars["alert_down_minutes"] = alert_down_minutes
if section == "events":
# 'Down Reconnected' has its own dedicated section; exclude it
# from events when that section is also active to prevent the
# same device appearing twice with different IP sources.
if "down_reconnected" in sections:
format_vars["event_types"] = "'Connected', 'Disconnected','IP Changed'"
else:
format_vars["event_types"] = "'Connected', 'Down Reconnected', 'Disconnected','IP Changed'"
sqlQuery = template.format(**format_vars)
except Exception as e:
mylog("verbose", [f"[Notification] Error building condition for {section}: ", e])
sqlQuery = template.format(condition="")
fallback_vars = {"condition": ""}
if section == "down_devices":
fallback_vars["alert_down_minutes"] = alert_down_minutes
if section == "events":
if "down_reconnected" in sections:
fallback_vars["event_types"] = "'Connected', 'Disconnected','IP Changed'"
else:
fallback_vars["event_types"] = "'Connected', 'Down Reconnected', 'Disconnected','IP Changed'"
sqlQuery = template.format(**fallback_vars)
parameters = {}
mylog("debug", [f"[Notification] {section} SQL query: ", sqlQuery])
@@ -309,7 +232,7 @@ def get_notifications(db):
final_json[section] = json_obj.json.get("data", [])
final_json[f"{section}_meta"] = {
"title": section_titles.get(section, section),
"title": SECTION_TITLES.get(section, section),
"columnNames": getattr(json_obj, "columnNames", [])
}
@@ -323,7 +246,7 @@ def skip_repeated_notifications(db):
"""
Skips sending alerts for devices recently notified.
Clears `eve_PendingAlertEmail` for events linked to devices whose last
Clears `evePendingAlertEmail` for events linked to devices whose last
notification time is within their `devSkipRepeated` interval.
Args:
@@ -334,8 +257,8 @@ def skip_repeated_notifications(db):
# due strfime : Overflow --> use "strftime / 60"
mylog("verbose", "[Skip Repeated Notifications] Skip Repeated")
db.sql.execute("""UPDATE Events SET eve_PendingAlertEmail = 0
WHERE eve_PendingAlertEmail = 1 AND eve_MAC IN
db.sql.execute("""UPDATE Events SET evePendingAlertEmail = 0
WHERE evePendingAlertEmail = 1 AND eveMac IN
(
SELECT devMac FROM Devices
WHERE devLastNotification IS NOT NULL

View File

@@ -142,17 +142,17 @@ class DeviceInstance:
objs = PluginObjectInstance().getByField(
plugPrefix='NMAP',
matchedColumn='Object_PrimaryID',
matchedColumn='objectPrimaryId',
matchedKey=primary,
returnFields=['Object_SecondaryID', 'Watched_Value2']
returnFields=['objectSecondaryId', 'watchedValue2']
)
ports = []
for o in objs:
port = int(o.get('Object_SecondaryID') or 0)
port = int(o.get('objectSecondaryId') or 0)
ports.append({"port": port, "service": o.get('Watched_Value2', '')})
ports.append({"port": port, "service": o.get('watchedValue2', '')})
return ports
@@ -327,20 +327,30 @@ class DeviceInstance:
return {"success": True, "inserted": row_count, "skipped_lines": skipped}
def getTotals(self):
"""Get device totals by status."""
"""Get device totals by status.
Returns a list of 6 counts in the documented positional order:
[all, connected, favorites, new, down, archived]
IMPORTANT: This order is a public API contract consumed by:
- presence.php (reads indices 0-5)
- /devices/totals/named (maps indices 0-5 to named fields)
- homepage widget datav2 (reads /devices/totals indices)
DO NOT change the order or add/remove fields without a breaking-change release.
"""
conn = get_temp_db_connection()
sql = conn.cursor()
conditions = get_device_conditions()
all_conditions = get_device_conditions()
# Build sub-selects dynamically for all dictionary entries
sub_queries = []
for key, condition in conditions.items():
# Make sure the alias is SQL-safe (no spaces or special chars)
alias = key.replace(" ", "_").lower()
sub_queries.append(f'(SELECT COUNT(*) FROM DevicesView {condition}) AS "{alias}"')
# Only the 6 public fields, in documented positional order.
# DO NOT change this order — it is a stable API contract.
keys = ["all", "connected", "favorites", "new", "down", "archived"]
sub_queries = [
f'(SELECT COUNT(*) FROM DevicesView {all_conditions[key]}) AS "{key}"'
for key in keys
]
# Join all sub-selects with commas
query = "SELECT\n " + ",\n ".join(sub_queries)
sql.execute(query)
row = sql.fetchone()
@@ -471,31 +481,31 @@ class DeviceInstance:
LOWER(d.devParentMAC) AS devParentMAC,
(SELECT COUNT(*) FROM Sessions
WHERE LOWER(ses_MAC) = LOWER(d.devMac) AND (
ses_DateTimeConnection >= {period_date_sql} OR
ses_DateTimeDisconnection >= {period_date_sql} OR
ses_StillConnected = 1
WHERE LOWER(sesMac) = LOWER(d.devMac) AND (
sesDateTimeConnection >= {period_date_sql} OR
sesDateTimeDisconnection >= {period_date_sql} OR
sesStillConnected = 1
)) AS devSessions,
(SELECT COUNT(*) FROM Events
WHERE LOWER(eve_MAC) = LOWER(d.devMac) AND eve_DateTime >= {period_date_sql}
AND eve_EventType NOT IN ('Connected','Disconnected')) AS devEvents,
WHERE LOWER(eveMac) = LOWER(d.devMac) AND eveDateTime >= {period_date_sql}
AND eveEventType NOT IN ('Connected','Disconnected')) AS devEvents,
(SELECT COUNT(*) FROM Events
WHERE LOWER(eve_MAC) = LOWER(d.devMac) AND eve_DateTime >= {period_date_sql}
AND eve_EventType = 'Device Down') AS devDownAlerts,
WHERE LOWER(eveMac) = LOWER(d.devMac) AND eveDateTime >= {period_date_sql}
AND eveEventType = 'Device Down') AS devDownAlerts,
(SELECT CAST(MAX(0, SUM(
julianday(IFNULL(ses_DateTimeDisconnection,'{now}')) -
julianday(CASE WHEN ses_DateTimeConnection < {period_date_sql}
THEN {period_date_sql} ELSE ses_DateTimeConnection END)
julianday(IFNULL(sesDateTimeDisconnection,'{now}')) -
julianday(CASE WHEN sesDateTimeConnection < {period_date_sql}
THEN {period_date_sql} ELSE sesDateTimeConnection END)
) * 24) AS INT)
FROM Sessions
WHERE LOWER(ses_MAC) = LOWER(d.devMac)
AND ses_DateTimeConnection IS NOT NULL
AND (ses_DateTimeDisconnection IS NOT NULL OR ses_StillConnected = 1)
AND (ses_DateTimeConnection >= {period_date_sql}
OR ses_DateTimeDisconnection >= {period_date_sql} OR ses_StillConnected = 1)
WHERE LOWER(sesMac) = LOWER(d.devMac)
AND sesDateTimeConnection IS NOT NULL
AND (sesDateTimeDisconnection IS NOT NULL OR sesStillConnected = 1)
AND (sesDateTimeConnection >= {period_date_sql}
OR sesDateTimeDisconnection >= {period_date_sql} OR sesStillConnected = 1)
) AS devPresenceHours
FROM DevicesView d
@@ -797,7 +807,7 @@ class DeviceInstance:
"""Delete all events for a device."""
conn = get_temp_db_connection()
cur = conn.cursor()
cur.execute("DELETE FROM Events WHERE eve_MAC=?", (mac,))
cur.execute("DELETE FROM Events WHERE eveMac=?", (mac,))
conn.commit()
conn.close()
return {"success": True}

View File

@@ -21,7 +21,7 @@ class EventInstance:
def get_all(self):
conn = self._conn()
rows = conn.execute(
"SELECT * FROM Events ORDER BY eve_DateTime DESC"
"SELECT * FROM Events ORDER BY eveDateTime DESC"
).fetchall()
conn.close()
return self._rows_to_list(rows)
@@ -31,7 +31,7 @@ class EventInstance:
conn = self._conn()
rows = conn.execute("""
SELECT * FROM Events
ORDER BY eve_DateTime DESC
ORDER BY eveDateTime DESC
LIMIT ?
""", (n,)).fetchall()
conn.close()
@@ -47,8 +47,8 @@ class EventInstance:
conn = self._conn()
rows = conn.execute("""
SELECT * FROM Events
WHERE eve_DateTime >= ?
ORDER BY eve_DateTime DESC
WHERE eveDateTime >= ?
ORDER BY eveDateTime DESC
""", (since,)).fetchall()
conn.close()
return self._rows_to_list(rows)
@@ -63,8 +63,8 @@ class EventInstance:
conn = self._conn()
rows = conn.execute("""
SELECT * FROM Events
WHERE eve_DateTime >= ?
ORDER BY eve_DateTime DESC
WHERE eveDateTime >= ?
ORDER BY eveDateTime DESC
""", (since,)).fetchall()
conn.close()
return self._rows_to_list(rows)
@@ -78,8 +78,8 @@ class EventInstance:
conn = self._conn()
rows = conn.execute("""
SELECT * FROM Events
WHERE eve_DateTime BETWEEN ? AND ?
ORDER BY eve_DateTime DESC
WHERE eveDateTime BETWEEN ? AND ?
ORDER BY eveDateTime DESC
""", (start, end)).fetchall()
conn.close()
return self._rows_to_list(rows)
@@ -89,9 +89,9 @@ class EventInstance:
conn = self._conn()
conn.execute("""
INSERT OR IGNORE INTO Events (
eve_MAC, eve_IP, eve_DateTime,
eve_EventType, eve_AdditionalInfo,
eve_PendingAlertEmail, eve_PairEventRowid
eveMac, eveIp, eveDateTime,
eveEventType, eveAdditionalInfo,
evePendingAlertEmail, evePairEventRowid
) VALUES (?,?,?,?,?,?,?)
""", (mac, ip, timeNowUTC(), eventType, info,
1 if pendingAlert else 0, pairRow))
@@ -102,7 +102,7 @@ class EventInstance:
def delete_older_than(self, days: int):
cutoff = timeNowUTC(as_string=False) - timedelta(days=days)
conn = self._conn()
result = conn.execute("DELETE FROM Events WHERE eve_DateTime < ?", (cutoff,))
result = conn.execute("DELETE FROM Events WHERE eveDateTime < ?", (cutoff,))
conn.commit()
deleted_count = result.rowcount
conn.close()
@@ -124,7 +124,7 @@ class EventInstance:
cur = conn.cursor()
cur.execute(
"""
INSERT OR IGNORE INTO Events (eve_MAC, eve_IP, eve_DateTime, eve_EventType, eve_AdditionalInfo, eve_PendingAlertEmail)
INSERT OR IGNORE INTO Events (eveMac, eveIp, eveDateTime, eveEventType, eveAdditionalInfo, evePendingAlertEmail)
VALUES (?, ?, ?, ?, ?, ?)
""",
(mac, ip, start_time, event_type, additional_info, pending_alert),
@@ -145,10 +145,10 @@ class EventInstance:
cur = conn.cursor()
if mac:
sql = "SELECT * FROM Events WHERE eve_MAC=? ORDER BY eve_DateTime DESC"
sql = "SELECT * FROM Events WHERE eveMac=? ORDER BY eveDateTime DESC"
cur.execute(sql, (mac,))
else:
sql = "SELECT * FROM Events ORDER BY eve_DateTime DESC"
sql = "SELECT * FROM Events ORDER BY eveDateTime DESC"
cur.execute(sql)
rows = cur.fetchall()
@@ -163,7 +163,7 @@ class EventInstance:
cur = conn.cursor()
# Use a parameterized query with sqlite date function
sql = "DELETE FROM Events WHERE eve_DateTime <= date('now', ?)"
sql = "DELETE FROM Events WHERE eveDateTime <= date('now', ?)"
cur.execute(sql, [f"-{days} days"])
conn.commit()
@@ -197,19 +197,19 @@ class EventInstance:
sql = f"""
SELECT
(SELECT COUNT(*) FROM Events WHERE eve_DateTime >= {period_date_sql}) AS all_events,
(SELECT COUNT(*) FROM Events WHERE eveDateTime >= {period_date_sql}) AS all_events,
(SELECT COUNT(*) FROM Sessions WHERE
ses_DateTimeConnection >= {period_date_sql}
OR ses_DateTimeDisconnection >= {period_date_sql}
OR ses_StillConnected = 1
sesDateTimeConnection >= {period_date_sql}
OR sesDateTimeDisconnection >= {period_date_sql}
OR sesStillConnected = 1
) AS sessions,
(SELECT COUNT(*) FROM Sessions WHERE
(ses_DateTimeConnection IS NULL AND ses_DateTimeDisconnection >= {period_date_sql})
OR (ses_DateTimeDisconnection IS NULL AND ses_StillConnected = 0 AND ses_DateTimeConnection >= {period_date_sql})
(sesDateTimeConnection IS NULL AND sesDateTimeDisconnection >= {period_date_sql})
OR (sesDateTimeDisconnection IS NULL AND sesStillConnected = 0 AND sesDateTimeConnection >= {period_date_sql})
) AS missing,
(SELECT COUNT(*) FROM Events WHERE eve_DateTime >= {period_date_sql} AND eve_EventType LIKE 'VOIDED%') AS voided,
(SELECT COUNT(*) FROM Events WHERE eve_DateTime >= {period_date_sql} AND eve_EventType LIKE 'New Device') AS new,
(SELECT COUNT(*) FROM Events WHERE eve_DateTime >= {period_date_sql} AND eve_EventType LIKE 'Device Down') AS down
(SELECT COUNT(*) FROM Events WHERE eveDateTime >= {period_date_sql} AND eveEventType LIKE 'VOIDED%') AS voided,
(SELECT COUNT(*) FROM Events WHERE eveDateTime >= {period_date_sql} AND eveEventType LIKE 'New Device') AS new,
(SELECT COUNT(*) FROM Events WHERE eveDateTime >= {period_date_sql} AND eveEventType LIKE 'Device Down') AS down
"""
cur.execute(sql)
@@ -247,11 +247,11 @@ class EventInstance:
conn = self._conn()
sql = """
SELECT eve_MAC, COUNT(*) as event_count
SELECT eveMac, COUNT(*) as event_count
FROM Events
WHERE eve_EventType IN ('Connected','Disconnected','Device Down','Down Reconnected')
AND eve_DateTime >= datetime('now', ?)
GROUP BY eve_MAC
WHERE eveEventType IN ('Connected','Disconnected','Device Down','Down Reconnected')
AND eveDateTime >= datetime('now', ?)
GROUP BY eveMac
HAVING COUNT(*) >= ?
"""
@@ -262,6 +262,6 @@ class EventInstance:
conn.close()
if macs_only:
return {row["eve_MAC"] for row in rows}
return {row["eveMac"] for row in rows}
return [dict(row) for row in rows]

View File

@@ -1,4 +1,5 @@
import json
import re
import uuid
import socket
from yattag import indent
@@ -16,6 +17,7 @@ from helper import (
getBuildTimeStampAndVersion,
)
from messaging.in_app import write_notification
from messaging.notification_sections import SECTION_ORDER
from utils.datetime_utils import timeNowUTC, timeNowTZ, get_timezone_offset
@@ -29,17 +31,17 @@ class NotificationInstance:
# Create Notifications table if missing
self.db.sql.execute("""CREATE TABLE IF NOT EXISTS "Notifications" (
"Index" INTEGER,
"GUID" TEXT UNIQUE,
"DateTimeCreated" TEXT,
"DateTimePushed" TEXT,
"Status" TEXT,
"JSON" TEXT,
"Text" TEXT,
"HTML" TEXT,
"PublishedVia" TEXT,
"Extra" TEXT,
PRIMARY KEY("Index" AUTOINCREMENT)
"index" INTEGER,
"guid" TEXT UNIQUE,
"dateTimeCreated" TEXT,
"dateTimePushed" TEXT,
"status" TEXT,
"json" TEXT,
"text" TEXT,
"html" TEXT,
"publishedVia" TEXT,
"extra" TEXT,
PRIMARY KEY("index" AUTOINCREMENT)
);
""")
@@ -60,12 +62,7 @@ class NotificationInstance:
write_file(logPath + "/report_output.json", json.dumps(JSON))
# Check if nothing to report, end
if (
JSON["new_devices"] == [] and JSON["down_devices"] == [] and JSON["events"] == [] and JSON["plugins"] == [] and JSON["down_reconnected"] == []
):
self.HasNotifications = False
else:
self.HasNotifications = True
self.HasNotifications = any(JSON.get(s, []) for s in SECTION_ORDER)
self.GUID = str(uuid.uuid4())
self.DateTimeCreated = timeNowUTC()
@@ -129,47 +126,13 @@ class NotificationInstance:
mail_text = mail_text.replace("REPORT_DASHBOARD_URL", self.serverUrl)
mail_html = mail_html.replace("REPORT_DASHBOARD_URL", self.serverUrl)
# Start generating the TEXT & HTML notification messages
# new_devices
# ---
html, text = construct_notifications(self.JSON, "new_devices")
mail_text = mail_text.replace("NEW_DEVICES_TABLE", text + "\n")
mail_html = mail_html.replace("NEW_DEVICES_TABLE", html)
mylog("verbose", ["[Notification] New Devices sections done."])
# down_devices
# ---
html, text = construct_notifications(self.JSON, "down_devices")
mail_text = mail_text.replace("DOWN_DEVICES_TABLE", text + "\n")
mail_html = mail_html.replace("DOWN_DEVICES_TABLE", html)
mylog("verbose", ["[Notification] Down Devices sections done."])
# down_reconnected
# ---
html, text = construct_notifications(self.JSON, "down_reconnected")
mail_text = mail_text.replace("DOWN_RECONNECTED_TABLE", text + "\n")
mail_html = mail_html.replace("DOWN_RECONNECTED_TABLE", html)
mylog("verbose", ["[Notification] Reconnected Down Devices sections done."])
# events
# ---
html, text = construct_notifications(self.JSON, "events")
mail_text = mail_text.replace("EVENTS_TABLE", text + "\n")
mail_html = mail_html.replace("EVENTS_TABLE", html)
mylog("verbose", ["[Notification] Events sections done."])
# plugins
# ---
html, text = construct_notifications(self.JSON, "plugins")
mail_text = mail_text.replace("PLUGINS_TABLE", text + "\n")
mail_html = mail_html.replace("PLUGINS_TABLE", html)
mylog("verbose", ["[Notification] Plugins sections done."])
# Generate TEXT & HTML for each notification section
for section in SECTION_ORDER:
html, text = construct_notifications(self.JSON, section)
placeholder = f"{section.upper()}_TABLE"
mail_text = mail_text.replace(placeholder, text + "\n")
mail_html = mail_html.replace(placeholder, html)
mylog("verbose", [f"[Notification] {section} section done."])
final_text = removeDuplicateNewLines(mail_text)
@@ -215,7 +178,7 @@ class NotificationInstance:
def upsert(self):
self.db.sql.execute(
"""
INSERT OR REPLACE INTO Notifications (GUID, DateTimeCreated, DateTimePushed, Status, JSON, Text, HTML, PublishedVia, Extra)
INSERT OR REPLACE INTO Notifications (guid, dateTimeCreated, dateTimePushed, "status", "json", "text", html, publishedVia, extra)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
@@ -239,7 +202,7 @@ class NotificationInstance:
self.db.sql.execute(
"""
DELETE FROM Notifications
WHERE GUID = ?
WHERE guid = ?
""",
(GUID,),
)
@@ -249,7 +212,7 @@ class NotificationInstance:
def getNew(self):
self.db.sql.execute("""
SELECT * FROM Notifications
WHERE Status = "new"
WHERE "status" = 'new'
""")
return self.db.sql.fetchall()
@@ -258,8 +221,8 @@ class NotificationInstance:
# Execute an SQL query to update the status of all notifications
self.db.sql.execute("""
UPDATE Notifications
SET Status = "processed"
WHERE Status = "new"
SET "status" = 'processed'
WHERE "status" = 'new'
""")
self.save()
@@ -271,15 +234,15 @@ class NotificationInstance:
self.db.sql.execute("""
UPDATE Devices SET devLastNotification = ?
WHERE devMac IN (
SELECT eve_MAC FROM Events
WHERE eve_PendingAlertEmail = 1
SELECT eveMac FROM Events
WHERE evePendingAlertEmail = 1
)
""", (timeNowUTC(),))
self.db.sql.execute("""
UPDATE Events SET eve_PendingAlertEmail = 0
WHERE eve_PendingAlertEmail = 1
AND eve_EventType !='Device Down' """)
UPDATE Events SET evePendingAlertEmail = 0
WHERE evePendingAlertEmail = 1
AND eveEventType !='Device Down' """)
# Clear down events flag after the reporting window passed
minutes = int(get_setting_value("NTFPRCS_alert_down_time") or 0)
@@ -287,10 +250,10 @@ class NotificationInstance:
self.db.sql.execute(
"""
UPDATE Events
SET eve_PendingAlertEmail = 0
WHERE eve_PendingAlertEmail = 1
AND eve_EventType = 'Device Down'
AND eve_DateTime < datetime('now', ?, ?)
SET evePendingAlertEmail = 0
WHERE evePendingAlertEmail = 1
AND eveEventType = 'Device Down'
AND eveDateTime < datetime('now', ?, ?)
""",
(f"-{minutes} minutes", tz_offset),
)
@@ -345,8 +308,16 @@ def construct_notifications(JSON, section):
build_direction = "TOP_TO_BOTTOM"
text_line = "{}\t{}\n"
# Read template settings
show_headers = get_setting_value("NTFPRCS_TEXT_SECTION_HEADERS")
if show_headers is None or show_headers == "":
show_headers = True
text_template = get_setting_value(f"NTFPRCS_TEXT_TEMPLATE_{section}") or ""
if len(jsn) > 0:
text = tableTitle + "\n---------\n"
# Section header (text)
if show_headers:
text = tableTitle + "\n---------\n"
# Convert a JSON into an HTML table
html = convert(
@@ -363,13 +334,24 @@ def construct_notifications(JSON, section):
)
# prepare text-only message
for device in jsn:
for header in headers:
padding = ""
if len(header) < 4:
padding = "\t"
text += text_line.format(header + ": " + padding, device[header])
text += "\n"
if text_template:
# Custom template: replace {FieldName} placeholders per device
for device in jsn:
line = re.sub(
r'\{(.+?)\}',
lambda m: str(device.get(m.group(1), m.group(0))),
text_template,
)
text += line + "\n"
else:
# Legacy fallback: vertical Header: Value list
for device in jsn:
for header in headers:
padding = ""
if len(header) < 4:
padding = "\t"
text += text_line.format(header + ": " + padding, device[header])
text += "\n"
# Format HTML table headers
for header in headers:

View File

@@ -35,18 +35,18 @@ class PluginObjectInstance:
def getByGUID(self, ObjectGUID):
return self._fetchone(
"SELECT * FROM Plugins_Objects WHERE ObjectGUID = ?", (ObjectGUID,)
"SELECT * FROM Plugins_Objects WHERE objectGuid = ?", (ObjectGUID,)
)
def exists(self, ObjectGUID):
row = self._fetchone("""
SELECT COUNT(*) AS count FROM Plugins_Objects WHERE ObjectGUID = ?
SELECT COUNT(*) AS count FROM Plugins_Objects WHERE objectGuid = ?
""", (ObjectGUID,))
return row["count"] > 0 if row else False
def getByPlugin(self, plugin):
return self._fetchall(
"SELECT * FROM Plugins_Objects WHERE Plugin = ?", (plugin,)
"SELECT * FROM Plugins_Objects WHERE plugin = ?", (plugin,)
)
def getLastNCreatedPerPlugin(self, plugin, entries=1):
@@ -54,8 +54,8 @@ class PluginObjectInstance:
"""
SELECT *
FROM Plugins_Objects
WHERE Plugin = ?
ORDER BY DateTimeCreated DESC
WHERE plugin = ?
ORDER BY dateTimeCreated DESC
LIMIT ?
""",
(plugin, entries),
@@ -63,7 +63,7 @@ class PluginObjectInstance:
def getByField(self, plugPrefix, matchedColumn, matchedKey, returnFields=None):
rows = self._fetchall(
f"SELECT * FROM Plugins_Objects WHERE Plugin = ? AND {matchedColumn} = ?",
f"SELECT * FROM Plugins_Objects WHERE plugin = ? AND {matchedColumn} = ?",
(plugPrefix, matchedKey.lower())
)
@@ -75,12 +75,12 @@ class PluginObjectInstance:
def getByPrimary(self, plugin, primary_id):
return self._fetchall("""
SELECT * FROM Plugins_Objects
WHERE Plugin = ? AND Object_PrimaryID = ?
WHERE plugin = ? AND objectPrimaryId = ?
""", (plugin, primary_id))
def getByStatus(self, status):
return self._fetchall("""
SELECT * FROM Plugins_Objects WHERE Status = ?
SELECT * FROM Plugins_Objects WHERE "status" = ?
""", (status,))
def updateField(self, ObjectGUID, field, value):
@@ -90,7 +90,7 @@ class PluginObjectInstance:
raise ValueError(msg)
self._execute(
f"UPDATE Plugins_Objects SET {field}=? WHERE ObjectGUID=?",
f"UPDATE Plugins_Objects SET {field}=? WHERE objectGuid=?",
(value, ObjectGUID)
)
@@ -100,4 +100,32 @@ class PluginObjectInstance:
mylog("none", msg)
raise ValueError(msg)
self._execute("DELETE FROM Plugins_Objects WHERE ObjectGUID=?", (ObjectGUID,))
self._execute("DELETE FROM Plugins_Objects WHERE objectGuid=?", (ObjectGUID,))
def getStats(self, foreign_key=None):
"""Per-plugin row counts across Objects, Events, and History tables.
Optionally scoped to a specific foreignKey (e.g. MAC address)."""
if foreign_key:
sql = """
SELECT 'objects' AS tableName, plugin, COUNT(*) AS cnt
FROM Plugins_Objects WHERE foreignKey = ? GROUP BY plugin
UNION ALL
SELECT 'events', plugin, COUNT(*)
FROM Plugins_Events WHERE foreignKey = ? GROUP BY plugin
UNION ALL
SELECT 'history', plugin, COUNT(*)
FROM Plugins_History WHERE foreignKey = ? GROUP BY plugin
"""
return self._fetchall(sql, (foreign_key, foreign_key, foreign_key))
else:
sql = """
SELECT 'objects' AS tableName, plugin, COUNT(*) AS cnt
FROM Plugins_Objects GROUP BY plugin
UNION ALL
SELECT 'events', plugin, COUNT(*)
FROM Plugins_Events GROUP BY plugin
UNION ALL
SELECT 'history', plugin, COUNT(*)
FROM Plugins_History GROUP BY plugin
"""
return self._fetchall(sql)

View File

@@ -238,11 +238,11 @@ class plugin_manager:
if plugin_name: # Only compute for single plugin
sql.execute(
"""
SELECT MAX(DateTimeChanged) AS last_changed,
SELECT MAX(dateTimeChanged) AS last_changed,
COUNT(*) AS total_objects,
SUM(CASE WHEN DateTimeCreated = DateTimeChanged THEN 1 ELSE 0 END) AS new_objects
SUM(CASE WHEN dateTimeCreated = dateTimeChanged THEN 1 ELSE 0 END) AS new_objects
FROM Plugins_Objects
WHERE Plugin = ?
WHERE plugin = ?
""",
(plugin_name,),
)
@@ -264,12 +264,12 @@ class plugin_manager:
else: # Compute for all plugins (full refresh)
sql.execute("""
SELECT Plugin,
MAX(DateTimeChanged) AS last_changed,
SELECT plugin,
MAX(dateTimeChanged) AS last_changed,
COUNT(*) AS total_objects,
SUM(CASE WHEN DateTimeCreated = DateTimeChanged THEN 1 ELSE 0 END) AS new_objects
SUM(CASE WHEN dateTimeCreated = dateTimeChanged THEN 1 ELSE 0 END) AS new_objects
FROM Plugins_Objects
GROUP BY Plugin
GROUP BY plugin
""")
for plugin, last_changed, total_objects, new_objects in sql.fetchall():
new_objects = new_objects or 0 # ensure it's int
@@ -496,22 +496,22 @@ def execute_plugin(db, all_plugins, plugin):
# Common part of the SQL parameters
base_params = [
0, # "Index" placeholder
0, # "index" placeholder
plugin[
"unique_prefix"
], # "Plugin" column value from the plugin dictionary
columns[0], # "Object_PrimaryID" value from columns list
columns[1], # "Object_SecondaryID" value from columns list
"null", # Placeholder for "DateTimeCreated" column
columns[2], # "DateTimeChanged" value from columns list
columns[3], # "Watched_Value1" value from columns list
columns[4], # "Watched_Value2" value from columns list
columns[5], # "Watched_Value3" value from columns list
columns[6], # "Watched_Value4" value from columns list
"not-processed", # "Status" column (placeholder)
columns[7], # "Extra" value from columns list
"null", # Placeholder for "UserData" column
columns[8], # "ForeignKey" value from columns list
], # "plugin" column value from the plugin dictionary
columns[0], # "objectPrimaryId" value from columns list
columns[1], # "objectSecondaryId" value from columns list
"null", # Placeholder for "dateTimeCreated" column
columns[2], # "dateTimeChanged" value from columns list
columns[3], # "watchedValue1" value from columns list
columns[4], # "watchedValue2" value from columns list
columns[5], # "watchedValue3" value from columns list
columns[6], # "watchedValue4" value from columns list
"not-processed", # "status" column (placeholder)
columns[7], # "extra" value from columns list
"null", # Placeholder for "userData" column
columns[8], # "foreignKey" value from columns list
tmp_SyncHubNodeName, # Sync Hub Node name
]
@@ -566,26 +566,26 @@ def execute_plugin(db, all_plugins, plugin):
# Each value corresponds to a column in the table in the order of the columns.
# Must match the Plugins_Objects and Plugins_Events database tables and can be used as input for the plugin_object_class.
base_params = [
0, # "Index" placeholder
plugin["unique_prefix"], # "Plugin" plugin dictionary
row[0], # "Object_PrimaryID" row
0, # "index" placeholder
plugin["unique_prefix"], # "plugin" plugin dictionary
row[0], # "objectPrimaryId" row
handle_empty(
row[1]
), # "Object_SecondaryID" column after handling empty values
"null", # Placeholder "DateTimeCreated" column
row[2], # "DateTimeChanged" row
row[3], # "Watched_Value1" row
row[4], # "Watched_Value2" row
), # "objectSecondaryId" column after handling empty values
"null", # Placeholder "dateTimeCreated" column
row[2], # "dateTimeChanged" row
row[3], # "watchedValue1" row
row[4], # "watchedValue2" row
handle_empty(
row[5]
), # "Watched_Value3" column after handling empty values
), # "watchedValue3" column after handling empty values
handle_empty(
row[6]
), # "Watched_Value4" column after handling empty values
"not-processed", # "Status" column (placeholder)
row[7], # "Extra" row
"null", # Placeholder "UserData" column
row[8], # "ForeignKey" row
), # "watchedValue4" column after handling empty values
"not-processed", # "status" column (placeholder)
row[7], # "extra" row
"null", # Placeholder "userData" column
row[8], # "foreignKey" row
"null", # Sync Hub Node name - Only supported with scripts
]
@@ -654,41 +654,41 @@ def execute_plugin(db, all_plugins, plugin):
# Each value corresponds to a column in the table in the order of the columns.
# Must match the Plugins_Objects and Plugins_Events database tables and can be used as input for the plugin_object_class.
base_params = [
0, # "Index" placeholder
plugin["unique_prefix"], # "Plugin"
row[0], # "Object_PrimaryID"
handle_empty(row[1]), # "Object_SecondaryID"
"null", # "DateTimeCreated" column (null placeholder)
row[2], # "DateTimeChanged"
row[3], # "Watched_Value1"
row[4], # "Watched_Value2"
handle_empty(row[5]), # "Watched_Value3"
handle_empty(row[6]), # "Watched_Value4"
"not-processed", # "Status" column (placeholder)
row[7], # "Extra"
"null", # "UserData" column (null placeholder)
row[8], # "ForeignKey"
"null", # Sync Hub Node name - Only supported with scripts
0, # "index" placeholder
plugin["unique_prefix"], # "plugin"
row[0], # "objectPrimaryId"
handle_empty(row[1]), # "objectSecondaryId"
"null", # "dateTimeCreated" column (null placeholder)
row[2], # "dateTimeChanged"
row[3], # "watchedValue1"
row[4], # "watchedValue2"
handle_empty(row[5]), # "watchedValue3"
handle_empty(row[6]), # "watchedValue4"
"not-processed", # "status" column (placeholder)
row[7], # "extra"
"null", # "userData" column (null placeholder)
row[8], # "foreignKey"
"null", # syncHubNodeName - Only supported with scripts
]
# Extend the base tuple with additional values if there are 13 columns
if len(row) == 13:
base_params.extend(
[
row[9], # "HelpVal1"
row[10], # "HelpVal2"
row[11], # "HelpVal3"
row[12], # "HelpVal4"
row[9], # "helpVal1"
row[10], # "helpVal2"
row[11], # "helpVal3"
row[12], # "helpVal4"
]
)
else:
# add padding
base_params.extend(
[
"null", # "HelpVal1"
"null", # "HelpVal2"
"null", # "HelpVal3"
"null", # "HelpVal4"
"null", # "helpVal1"
"null", # "helpVal2"
"null", # "helpVal3"
"null", # "helpVal4"
]
)
@@ -749,7 +749,7 @@ def process_plugin_events(db, plugin, plugEventsArr):
# Create plugin objects from existing database entries
plugObjectsArr = db.get_sql_array(
"SELECT * FROM Plugins_Objects where Plugin = '" + str(pluginPref) + "'"
"SELECT * FROM Plugins_Objects where plugin = '" + str(pluginPref) + "'"
)
for obj in plugObjectsArr:
@@ -894,11 +894,11 @@ def process_plugin_events(db, plugin, plugEventsArr):
sql.executemany(
"""
INSERT INTO Plugins_Objects
("Plugin", "Object_PrimaryID", "Object_SecondaryID", "DateTimeCreated",
"DateTimeChanged", "Watched_Value1", "Watched_Value2", "Watched_Value3",
"Watched_Value4", "Status", "Extra", "UserData", "ForeignKey", "SyncHubNodeName",
"HelpVal1", "HelpVal2", "HelpVal3", "HelpVal4",
"ObjectGUID")
("plugin", "objectPrimaryId", "objectSecondaryId", "dateTimeCreated",
"dateTimeChanged", "watchedValue1", "watchedValue2", "watchedValue3",
"watchedValue4", "status", "extra", "userData", "foreignKey", "syncHubNodeName",
"helpVal1", "helpVal2", "helpVal3", "helpVal4",
"objectGuid")
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
objects_to_insert,
@@ -909,12 +909,12 @@ def process_plugin_events(db, plugin, plugEventsArr):
sql.executemany(
"""
UPDATE Plugins_Objects
SET "Plugin" = ?, "Object_PrimaryID" = ?, "Object_SecondaryID" = ?, "DateTimeCreated" = ?,
"DateTimeChanged" = ?, "Watched_Value1" = ?, "Watched_Value2" = ?, "Watched_Value3" = ?,
"Watched_Value4" = ?, "Status" = ?, "Extra" = ?, "UserData" = ?, "ForeignKey" = ?, "SyncHubNodeName" = ?,
"HelpVal1" = ?, "HelpVal2" = ?, "HelpVal3" = ?, "HelpVal4" = ?,
"ObjectGUID" = ?
WHERE "Index" = ?
SET "plugin" = ?, "objectPrimaryId" = ?, "objectSecondaryId" = ?, "dateTimeCreated" = ?,
"dateTimeChanged" = ?, "watchedValue1" = ?, "watchedValue2" = ?, "watchedValue3" = ?,
"watchedValue4" = ?, "status" = ?, "extra" = ?, "userData" = ?, "foreignKey" = ?, "syncHubNodeName" = ?,
"helpVal1" = ?, "helpVal2" = ?, "helpVal3" = ?, "helpVal4" = ?,
"objectGuid" = ?
WHERE "index" = ?
""",
objects_to_update,
)
@@ -924,11 +924,11 @@ def process_plugin_events(db, plugin, plugEventsArr):
sql.executemany(
"""
INSERT INTO Plugins_Events
("Plugin", "Object_PrimaryID", "Object_SecondaryID", "DateTimeCreated",
"DateTimeChanged", "Watched_Value1", "Watched_Value2", "Watched_Value3",
"Watched_Value4", "Status", "Extra", "UserData", "ForeignKey", "SyncHubNodeName",
"HelpVal1", "HelpVal2", "HelpVal3", "HelpVal4",
"ObjectGUID")
("plugin", "objectPrimaryId", "objectSecondaryId", "dateTimeCreated",
"dateTimeChanged", "watchedValue1", "watchedValue2", "watchedValue3",
"watchedValue4", "status", "extra", "userData", "foreignKey", "syncHubNodeName",
"helpVal1", "helpVal2", "helpVal3", "helpVal4",
"objectGuid")
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
events_to_insert,
@@ -939,11 +939,11 @@ def process_plugin_events(db, plugin, plugEventsArr):
sql.executemany(
"""
INSERT INTO Plugins_History
("Plugin", "Object_PrimaryID", "Object_SecondaryID", "DateTimeCreated",
"DateTimeChanged", "Watched_Value1", "Watched_Value2", "Watched_Value3",
"Watched_Value4", "Status", "Extra", "UserData", "ForeignKey", "SyncHubNodeName",
"HelpVal1", "HelpVal2", "HelpVal3", "HelpVal4",
"ObjectGUID")
("plugin", "objectPrimaryId", "objectSecondaryId", "dateTimeCreated",
"dateTimeChanged", "watchedValue1", "watchedValue2", "watchedValue3",
"watchedValue4", "status", "extra", "userData", "foreignKey", "syncHubNodeName",
"helpVal1", "helpVal2", "helpVal3", "helpVal4",
"objectGuid")
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
history_to_insert,
@@ -993,41 +993,41 @@ def process_plugin_events(db, plugin, plugEventsArr):
tmpList = []
for col in mappedCols:
if col["column"] == "Index":
if col["column"] == "index":
tmpList.append(plgEv.index)
elif col["column"] == "Plugin":
elif col["column"] == "plugin":
tmpList.append(plgEv.pluginPref)
elif col["column"] == "Object_PrimaryID":
elif col["column"] == "objectPrimaryId":
tmpList.append(plgEv.primaryId)
elif col["column"] == "Object_SecondaryID":
elif col["column"] == "objectSecondaryId":
tmpList.append(plgEv.secondaryId)
elif col["column"] == "DateTimeCreated":
elif col["column"] == "dateTimeCreated":
tmpList.append(plgEv.created)
elif col["column"] == "DateTimeChanged":
elif col["column"] == "dateTimeChanged":
tmpList.append(plgEv.changed)
elif col["column"] == "Watched_Value1":
elif col["column"] == "watchedValue1":
tmpList.append(plgEv.watched1)
elif col["column"] == "Watched_Value2":
elif col["column"] == "watchedValue2":
tmpList.append(plgEv.watched2)
elif col["column"] == "Watched_Value3":
elif col["column"] == "watchedValue3":
tmpList.append(plgEv.watched3)
elif col["column"] == "Watched_Value4":
elif col["column"] == "watchedValue4":
tmpList.append(plgEv.watched4)
elif col["column"] == "UserData":
elif col["column"] == "userData":
tmpList.append(plgEv.userData)
elif col["column"] == "Extra":
elif col["column"] == "extra":
tmpList.append(plgEv.extra)
elif col["column"] == "Status":
elif col["column"] == "status":
tmpList.append(plgEv.status)
elif col["column"] == "SyncHubNodeName":
elif col["column"] == "syncHubNodeName":
tmpList.append(plgEv.syncHubNodeName)
elif col["column"] == "HelpVal1":
elif col["column"] == "helpVal1":
tmpList.append(plgEv.helpVal1)
elif col["column"] == "HelpVal2":
elif col["column"] == "helpVal2":
tmpList.append(plgEv.helpVal2)
elif col["column"] == "HelpVal3":
elif col["column"] == "helpVal3":
tmpList.append(plgEv.helpVal3)
elif col["column"] == "HelpVal4":
elif col["column"] == "helpVal4":
tmpList.append(plgEv.helpVal4)
# Check if there's a default value specified for this column in the JSON.
@@ -1113,10 +1113,10 @@ class plugin_object_class:
# hash for comapring watched value changes
indexNameColumnMapping = [
(6, "Watched_Value1"),
(7, "Watched_Value2"),
(8, "Watched_Value3"),
(9, "Watched_Value4"),
(6, "watchedValue1"),
(7, "watchedValue2"),
(8, "watchedValue3"),
(9, "watchedValue4"),
]
if setObj is not None:

View File

@@ -581,8 +581,8 @@ def print_scan_stats(db):
row_dict = dict(row)
mylog("trace", f" {row_dict}")
mylog("trace", " ================ Events table content where eve_PendingAlertEmail = 1 ================",)
sql.execute("select * from Events where eve_PendingAlertEmail = 1")
mylog("trace", " ================ Events table content where evePendingAlertEmail = 1 ================",)
sql.execute("select * from Events where evePendingAlertEmail = 1")
rows = sql.fetchall()
for row in rows:
row_dict = dict(row)
@@ -611,9 +611,9 @@ def create_new_devices(db):
mylog("debug", '[New Devices] Insert "New Device" Events')
query_new_device_events = f"""
INSERT OR IGNORE INTO Events (
eve_MAC, eve_IP, eve_DateTime,
eve_EventType, eve_AdditionalInfo,
eve_PendingAlertEmail
eveMac, eveIp, eveDateTime,
eveEventType, eveAdditionalInfo,
evePendingAlertEmail
)
SELECT DISTINCT scanMac, scanLastIP, '{startTime}', 'New Device', scanVendor, 1
FROM CurrentScan
@@ -630,9 +630,9 @@ def create_new_devices(db):
mylog("debug", "[New Devices] Insert Connection into session table")
sql.execute(f"""INSERT INTO Sessions (
ses_MAC, ses_IP, ses_EventTypeConnection, ses_DateTimeConnection,
ses_EventTypeDisconnection, ses_DateTimeDisconnection,
ses_StillConnected, ses_AdditionalInfo
sesMac, sesIp, sesEventTypeConnection, sesDateTimeConnection,
sesEventTypeDisconnection, sesDateTimeDisconnection,
sesStillConnected, sesAdditionalInfo
)
SELECT scanMac, scanLastIP, 'Connected', '{startTime}', NULL, NULL, 1, scanVendor
FROM CurrentScan
@@ -642,7 +642,7 @@ def create_new_devices(db):
)
AND NOT EXISTS (
SELECT 1 FROM Sessions
WHERE ses_MAC = scanMac AND ses_StillConnected = 1
WHERE sesMac = scanMac AND sesStillConnected = 1
)
""")

View File

@@ -23,10 +23,10 @@ class NameResolver:
nameNotFound = ResolvedName()
# Check by MAC
sql.execute(f"""
SELECT Watched_Value2 FROM Plugins_Objects
WHERE Plugin = '{plugin}' AND Object_PrimaryID = '{pMAC}'
""")
sql.execute("""
SELECT watchedValue2 FROM Plugins_Objects
WHERE plugin = ? AND objectPrimaryId = ?
""", (plugin, pMAC))
result = sql.fetchall()
# self.db.commitDB() # Issue #1251: Optimize name resolution lookup
if result:
@@ -36,10 +36,10 @@ class NameResolver:
# Check name by IP if enabled
if get_setting_value('NEWDEV_IP_MATCH_NAME'):
sql.execute(f"""
SELECT Watched_Value2 FROM Plugins_Objects
WHERE Plugin = '{plugin}' AND Object_SecondaryID = '{pIP}'
""")
sql.execute("""
SELECT watchedValue2 FROM Plugins_Objects
WHERE plugin = ? AND objectSecondaryId = ?
""", (plugin, pIP))
result = sql.fetchall()
# self.db.commitDB() # Issue #1251: Optimize name resolution lookup
if result:

View File

@@ -120,27 +120,27 @@ def pair_sessions_events(db):
mylog("debug", "[Pair Session] - 1 Connections / New Devices")
sql.execute("""UPDATE Events
SET eve_PairEventRowid =
SET evePairEventRowid =
(SELECT ROWID
FROM Events AS EVE2
WHERE EVE2.eve_EventType IN ('New Device', 'Connected', 'Down Reconnected',
WHERE EVE2.eveEventType IN ('New Device', 'Connected', 'Down Reconnected',
'Device Down', 'Disconnected')
AND EVE2.eve_MAC = Events.eve_MAC
AND EVE2.eve_Datetime > Events.eve_DateTime
ORDER BY EVE2.eve_DateTime ASC LIMIT 1)
WHERE eve_EventType IN ('New Device', 'Connected', 'Down Reconnected')
AND eve_PairEventRowid IS NULL
AND EVE2.eveMac = Events.eveMac
AND EVE2.eveDateTime > Events.eveDateTime
ORDER BY EVE2.eveDateTime ASC LIMIT 1)
WHERE eveEventType IN ('New Device', 'Connected', 'Down Reconnected')
AND evePairEventRowid IS NULL
""")
# Pair Disconnection / Device Down
mylog("debug", "[Pair Session] - 2 Disconnections")
sql.execute("""UPDATE Events
SET eve_PairEventRowid =
SET evePairEventRowid =
(SELECT ROWID
FROM Events AS EVE2
WHERE EVE2.eve_PairEventRowid = Events.ROWID)
WHERE eve_EventType IN ('Device Down', 'Disconnected')
AND eve_PairEventRowid IS NULL
WHERE EVE2.evePairEventRowid = Events.ROWID)
WHERE eveEventType IN ('Device Down', 'Disconnected')
AND evePairEventRowid IS NULL
""")
mylog("debug", "[Pair Session] Pair session end")
@@ -171,9 +171,9 @@ def insert_events(db):
# Check device down non-sleeping devices (immediate on first absence)
mylog("debug", "[Events] - 1a - Devices down (non-sleeping)")
sql.execute(f"""INSERT OR IGNORE INTO Events (eve_MAC, eve_IP, eve_DateTime,
eve_EventType, eve_AdditionalInfo,
eve_PendingAlertEmail)
sql.execute(f"""INSERT OR IGNORE INTO Events (eveMac, eveIp, eveDateTime,
eveEventType, eveAdditionalInfo,
evePendingAlertEmail)
SELECT devMac, devLastIP, '{startTime}', 'Device Down', '', 1
FROM DevicesView
WHERE devAlertDown != 0
@@ -185,9 +185,9 @@ def insert_events(db):
# Check device down sleeping devices whose sleep window has expired
mylog("debug", "[Events] - 1b - Devices down (sleep expired)")
sql.execute(f"""INSERT OR IGNORE INTO Events (eve_MAC, eve_IP, eve_DateTime,
eve_EventType, eve_AdditionalInfo,
eve_PendingAlertEmail)
sql.execute(f"""INSERT OR IGNORE INTO Events (eveMac, eveIp, eveDateTime,
eveEventType, eveAdditionalInfo,
evePendingAlertEmail)
SELECT devMac, devLastIP, '{startTime}', 'Device Down', '', 1
FROM DevicesView
WHERE devAlertDown != 0
@@ -197,33 +197,33 @@ def insert_events(db):
AND NOT EXISTS (SELECT 1 FROM CurrentScan
WHERE devMac = scanMac)
AND NOT EXISTS (SELECT 1 FROM Events
WHERE eve_MAC = devMac
AND eve_EventType = 'Device Down'
AND eve_DateTime >= devLastConnection
WHERE eveMac = devMac
AND eveEventType = 'Device Down'
AND eveDateTime >= devLastConnection
) """)
# Check new Connections or Down Reconnections
mylog("debug", "[Events] - 2 - New Connections")
sql.execute(f""" INSERT OR IGNORE INTO Events (eve_MAC, eve_IP, eve_DateTime,
eve_EventType, eve_AdditionalInfo,
eve_PendingAlertEmail)
sql.execute(f""" INSERT OR IGNORE INTO Events (eveMac, eveIp, eveDateTime,
eveEventType, eveAdditionalInfo,
evePendingAlertEmail)
SELECT DISTINCT c.scanMac, c.scanLastIP, '{startTime}',
CASE
WHEN last_event.eve_EventType = 'Device Down' and last_event.eve_PendingAlertEmail = 0 THEN 'Down Reconnected'
WHEN last_event.eveEventType = 'Device Down' and last_event.evePendingAlertEmail = 0 THEN 'Down Reconnected'
ELSE 'Connected'
END,
'',
1
FROM CurrentScan AS c
LEFT JOIN LatestEventsPerMAC AS last_event ON c.scanMac = last_event.eve_MAC
WHERE last_event.devPresentLastScan = 0 OR last_event.eve_MAC IS NULL
LEFT JOIN LatestEventsPerMAC AS last_event ON c.scanMac = last_event.eveMac
WHERE last_event.devPresentLastScan = 0 OR last_event.eveMac IS NULL
""")
# Check disconnections
mylog("debug", "[Events] - 3 - Disconnections")
sql.execute(f"""INSERT OR IGNORE INTO Events (eve_MAC, eve_IP, eve_DateTime,
eve_EventType, eve_AdditionalInfo,
eve_PendingAlertEmail)
sql.execute(f"""INSERT OR IGNORE INTO Events (eveMac, eveIp, eveDateTime,
eveEventType, eveAdditionalInfo,
evePendingAlertEmail)
SELECT devMac, devLastIP, '{startTime}', 'Disconnected', '',
devAlertEvents
FROM Devices
@@ -235,9 +235,9 @@ def insert_events(db):
# Check IP Changed
mylog("debug", "[Events] - 4 - IP Changes")
sql.execute(f"""INSERT OR IGNORE INTO Events (eve_MAC, eve_IP, eve_DateTime,
eve_EventType, eve_AdditionalInfo,
eve_PendingAlertEmail)
sql.execute(f"""INSERT OR IGNORE INTO Events (eveMac, eveIp, eveDateTime,
eveEventType, eveAdditionalInfo,
evePendingAlertEmail)
SELECT scanMac, scanLastIP, '{startTime}', 'IP Changed',
'Previous IP: '|| devLastIP, devAlertEvents
FROM Devices, CurrentScan
@@ -279,7 +279,7 @@ def insertOnlineHistory(db):
# Prepare the insert query using parameterized inputs
insert_query = """
INSERT INTO Online_History (Scan_Date, Online_Devices, Down_Devices, All_Devices, Archived_Devices, Offline_Devices)
INSERT INTO Online_History (scanDate, onlineDevices, downDevices, allDevices, archivedDevices, offlineDevices)
VALUES (?, ?, ?, ?, ?, ?)
"""

Some files were not shown because too many files have changed in this diff Show More