mirror of
https://github.com/jokob-sk/NetAlertX.git
synced 2026-01-16 02:38:30 -05:00
DOCS: Plugins docs refactor
This commit is contained in:
File diff suppressed because it is too large
Load Diff
403
docs/PLUGINS_DEV_DATASOURCES.md
Normal file
403
docs/PLUGINS_DEV_DATASOURCES.md
Normal file
@@ -0,0 +1,403 @@
|
||||
# Plugin Data Sources
|
||||
|
||||
Learn how to configure different data sources for your plugin.
|
||||
|
||||
## Overview
|
||||
|
||||
Data sources determine **where the plugin gets its data** and **what format it returns**. NetAlertX supports multiple data source types, each suited for different use cases.
|
||||
|
||||
| Data Source | Type | Purpose | Returns | Example |
|
||||
|-------------|------|---------|---------|---------|
|
||||
| `script` | Code Execution | Execute Linux commands or Python scripts | Pipeline | Scan network, collect metrics, call APIs |
|
||||
| `app-db-query` | Database Query | Query the NetAlertX database | Result set | Show devices, open ports, recent events |
|
||||
| `sqlite-db-query` | External DB | Query external SQLite databases | Result set | PiHole database, external logs |
|
||||
| `template` | Template | Generate values from templates | Values | Initialize default settings |
|
||||
| `plugin_type` | Metadata | Declare plugin category | Metadata | Classify plugin (scanner, publisher, etc.) |
|
||||
|
||||
## Data Source: `script`
|
||||
|
||||
Execute any Linux command or Python script and capture its output.
|
||||
|
||||
### Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"data_source": "script",
|
||||
"show_ui": true,
|
||||
"mapped_to_table": "Plugins_Objects"
|
||||
}
|
||||
```
|
||||
|
||||
### How It Works
|
||||
|
||||
1. Command specified in `CMD` setting is executed
|
||||
2. Script writes results to `/tmp/log/plugins/last_result.<PREFIX>.log`
|
||||
3. Core reads file and parses pipe-delimited results
|
||||
4. Results inserted into database
|
||||
|
||||
### Example: Simple Python Script
|
||||
|
||||
```json
|
||||
{
|
||||
"function": "CMD",
|
||||
"type": {"dataType": "string", "elements": [{"elementType": "input", "elementOptions": [], "transformers": []}]},
|
||||
"default_value": "python3 /app/front/plugins/my_plugin/script.py",
|
||||
"localized": ["name"],
|
||||
"name": [{"language_code": "en_us", "string": "Command"}]
|
||||
}
|
||||
```
|
||||
|
||||
### Example: Bash Script
|
||||
|
||||
```json
|
||||
{
|
||||
"function": "CMD",
|
||||
"default_value": "bash /app/front/plugins/my_plugin/script.sh",
|
||||
"localized": ["name"],
|
||||
"name": [{"language_code": "en_us", "string": "Command"}]
|
||||
}
|
||||
```
|
||||
|
||||
### Best Practices
|
||||
|
||||
- **Always use absolute paths** (e.g., `/app/front/plugins/...`)
|
||||
- **Use `plugin_helper.py`** for output formatting
|
||||
- **Add timeouts** via `RUN_TIMEOUT` setting (default: 60s)
|
||||
- **Log errors** to `/tmp/log/plugins/*.log`
|
||||
- **Handle missing dependencies gracefully**
|
||||
|
||||
### Output Format
|
||||
|
||||
Must write to: `/tmp/log/plugins/last_result.<PREFIX>.log`
|
||||
|
||||
Format: Pipe-delimited, 9 or 13 columns
|
||||
|
||||
See [Plugin Data Contract](PLUGINS_DEV_DATA_CONTRACT.md) for exact format
|
||||
|
||||
---
|
||||
|
||||
## Data Source: `app-db-query`
|
||||
|
||||
Query the NetAlertX SQLite database and display results.
|
||||
|
||||
### Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"data_source": "app-db-query",
|
||||
"show_ui": true,
|
||||
"mapped_to_table": "Plugins_Objects"
|
||||
}
|
||||
```
|
||||
|
||||
### How It Works
|
||||
|
||||
1. SQL query specified in `CMD` setting is executed against `app.db`
|
||||
2. Results parsed according to column definitions
|
||||
3. Inserted into plugin display/database
|
||||
|
||||
### SQL Query Requirements
|
||||
|
||||
- Must return exactly **9 or 13 columns** matching the [data contract](PLUGINS_DEV_DATA_CONTRACT.md)
|
||||
- Column names must match (order matters!)
|
||||
- Must be **readable SQLite SQL** (not vendor-specific)
|
||||
|
||||
### Example: Open Ports from Nmap
|
||||
|
||||
```json
|
||||
{
|
||||
"function": "CMD",
|
||||
"type": {"dataType": "string", "elements": [{"elementType": "input", "elementOptions": [], "transformers": []}]},
|
||||
"default_value": "SELECT dv.devName as Object_PrimaryID, cast(dv.devLastIP as VARCHAR(100)) || ':' || cast(SUBSTR(ns.Port, 0, INSTR(ns.Port, '/')) as VARCHAR(100)) as Object_SecondaryID, datetime() as DateTime, ns.Service as Watched_Value1, ns.State as Watched_Value2, null as Watched_Value3, null as Watched_Value4, ns.Extra as Extra, 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"}]
|
||||
}
|
||||
```
|
||||
|
||||
### Example: Recent Device Events
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
e.EventValue as Object_PrimaryID,
|
||||
d.devName as Object_SecondaryID,
|
||||
e.EventDateTime as DateTime,
|
||||
e.EventType as Watched_Value1,
|
||||
d.devLastIP as Watched_Value2,
|
||||
null as Watched_Value3,
|
||||
null as Watched_Value4,
|
||||
e.EventDetails as Extra,
|
||||
d.devMac as ForeignKey
|
||||
FROM
|
||||
Events e
|
||||
LEFT JOIN
|
||||
Devices d ON e.DeviceMac = d.devMac
|
||||
WHERE
|
||||
e.EventDateTime > datetime('now', '-24 hours')
|
||||
ORDER BY
|
||||
e.EventDateTime DESC
|
||||
```
|
||||
|
||||
### Common Columns
|
||||
|
||||
**Devices Table:**
|
||||
- `devMac` - Device MAC address
|
||||
- `devName` - Device name
|
||||
- `devLastIP` - Last known IP
|
||||
- `devType` - Device type
|
||||
- `devStatus` - Online/offline status
|
||||
- `devVendor` - Hardware vendor
|
||||
|
||||
**CurrentScan Table:**
|
||||
- `cur_MAC` - MAC address
|
||||
- `cur_IP` - IP address
|
||||
- `cur_ScanMethod` - How it was discovered
|
||||
|
||||
**Nmap_Scan Table:**
|
||||
- `MAC` - Device MAC
|
||||
- `Port` - Port number
|
||||
- `Service` - Service name
|
||||
- `State` - open/closed/filtered
|
||||
- `Extra` - Additional notes
|
||||
|
||||
**Events Table:**
|
||||
- `EventType` - Event type
|
||||
- `EventValue` - Event value
|
||||
- `EventDateTime` - When it occurred
|
||||
- `DeviceMac` - Associated device
|
||||
|
||||
---
|
||||
|
||||
## Data Source: `sqlite-db-query`
|
||||
|
||||
Query an **external SQLite database** mounted in the container.
|
||||
|
||||
### Configuration
|
||||
|
||||
First, define the database path in a setting:
|
||||
|
||||
```json
|
||||
{
|
||||
"function": "DB_PATH",
|
||||
"type": {"dataType": "string", "elements": [{"elementType": "input", "elementOptions": [], "transformers": []}]},
|
||||
"default_value": "/etc/pihole/pihole-FTL.db",
|
||||
"localized": ["name"],
|
||||
"name": [{"language_code": "en_us", "string": "Database path"}],
|
||||
"description": [{"language_code": "en_us", "string": "Path to external SQLite database"}]
|
||||
}
|
||||
```
|
||||
|
||||
Then set data source and query:
|
||||
|
||||
```json
|
||||
{
|
||||
"data_source": "sqlite-db-query",
|
||||
"show_ui": true
|
||||
}
|
||||
```
|
||||
|
||||
### How It Works
|
||||
|
||||
1. External database file path specified in `DB_PATH` setting
|
||||
2. Database mounted at that path (e.g., via Docker volume)
|
||||
3. SQL query executed against external database using `EXTERNAL_<PREFIX>.` prefix
|
||||
4. Results returned in standard format
|
||||
|
||||
### SQL Query Example: PiHole Data
|
||||
|
||||
```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'",
|
||||
"localized": ["name"],
|
||||
"name": [{"language_code": "en_us", "string": "SQL to run"}]
|
||||
}
|
||||
```
|
||||
|
||||
### Key Points
|
||||
|
||||
- **Prefix all external tables** with `EXTERNAL_<PREFIX>.`
|
||||
- For PiHole (`PIHOLE` prefix): `EXTERNAL_PIHOLE.network`
|
||||
- For custom database (`CUSTOM` prefix): `EXTERNAL_CUSTOM.my_table`
|
||||
- **Database must be valid SQLite**
|
||||
- **Path must be accessible** inside the container
|
||||
- **No columns beyond the data contract** required
|
||||
|
||||
### Docker Volume Setup
|
||||
|
||||
To mount external database in docker-compose:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
netalertx:
|
||||
volumes:
|
||||
- /path/on/host/pihole-FTL.db:/etc/pihole/pihole-FTL.db:ro
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Data Source: `template`
|
||||
|
||||
Generate values from a template. Usually used for initialization and default settings.
|
||||
|
||||
### Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"data_source": "template"
|
||||
}
|
||||
```
|
||||
|
||||
### How It Works
|
||||
|
||||
- **Not widely used** in standard plugins
|
||||
- Used internally for generating default values
|
||||
- Check `newdev_template` plugin for implementation example
|
||||
|
||||
### Example: Default Device Template
|
||||
|
||||
```json
|
||||
{
|
||||
"function": "DEFAULT_DEVICE_PROPERTIES",
|
||||
"type": {"dataType": "string", "elements": [{"elementType": "textarea", "elementOptions": [], "transformers": []}]},
|
||||
"default_value": "type=Unknown|vendor=Unknown",
|
||||
"localized": ["name"],
|
||||
"name": [{"language_code": "en_us", "string": "Default properties"}]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Data Source: `plugin_type`
|
||||
|
||||
Declare the plugin category. Controls where settings appear in the UI.
|
||||
|
||||
### Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"data_source": "plugin_type",
|
||||
"value": "scanner"
|
||||
}
|
||||
```
|
||||
|
||||
### Supported Values
|
||||
|
||||
| Value | Section | Purpose |
|
||||
|-------|---------|---------|
|
||||
| `scanner` | Device Scanners | Discovers devices on network |
|
||||
| `system` | System Plugins | Core system functionality |
|
||||
| `publisher` | Notification/Alert Plugins | Sends notifications/alerts |
|
||||
| `importer` | Data Importers | Imports devices from external sources |
|
||||
| `other` | Other Plugins | Miscellaneous functionality |
|
||||
|
||||
### Example
|
||||
|
||||
```json
|
||||
{
|
||||
"settings": [
|
||||
{
|
||||
"function": "plugin_type",
|
||||
"type": {"dataType": "string", "elements": []},
|
||||
"default_value": "scanner",
|
||||
"options": ["scanner"],
|
||||
"data_source": "plugin_type",
|
||||
"value": "scanner",
|
||||
"localized": []
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Execution Order
|
||||
|
||||
Control plugin execution priority. Higher priority plugins run first.
|
||||
|
||||
```json
|
||||
{
|
||||
"execution_order": "Layer_0"
|
||||
}
|
||||
```
|
||||
|
||||
### Levels (highest to lowest priority)
|
||||
|
||||
- `Layer_0` - Highest priority (run first)
|
||||
- `Layer_1`
|
||||
- `Layer_2`
|
||||
- ...
|
||||
|
||||
### Example: Multi-Source Device Discovery
|
||||
|
||||
```json
|
||||
{
|
||||
"code_name": "device_scanner",
|
||||
"unique_prefix": "DEVSCAN",
|
||||
"execution_order": "Layer_0",
|
||||
"data_source": "script",
|
||||
"mapped_to_table": "CurrentScan"
|
||||
}
|
||||
```
|
||||
---
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
### Script Source
|
||||
- **Pros:** Flexible, can call external tools
|
||||
- **Cons:** Slower than database queries
|
||||
- **Optimization:** Add caching, use timeouts
|
||||
- **Default timeout:** 60 seconds (set `RUN_TIMEOUT`)
|
||||
|
||||
### Database Query Source
|
||||
- **Pros:** Fast, native query optimization
|
||||
- **Cons:** Limited to SQL capabilities
|
||||
- **Optimization:** Use indexes, avoid complex joins
|
||||
- **Timeout:** Usually instant
|
||||
|
||||
### External DB Query Source
|
||||
- **Pros:** Direct access to external data
|
||||
- **Cons:** Network latency, external availability
|
||||
- **Optimization:** Use indexes in external DB, selective queries
|
||||
- **Timeout:** Depends on DB response time
|
||||
|
||||
---
|
||||
|
||||
## Debugging Data Sources
|
||||
|
||||
### Check Script Output
|
||||
|
||||
```bash
|
||||
# Run script manually
|
||||
python3 /app/front/plugins/my_plugin/script.py
|
||||
|
||||
# Check result file
|
||||
cat /tmp/log/plugins/last_result.MYPREFIX.log
|
||||
```
|
||||
|
||||
### Test SQL Query
|
||||
|
||||
```bash
|
||||
# Connect to app database
|
||||
sqlite3 /data/db/app.db
|
||||
|
||||
# Run query
|
||||
sqlite> SELECT ... ;
|
||||
```
|
||||
|
||||
### Monitor Execution
|
||||
|
||||
```bash
|
||||
# Watch backend logs
|
||||
tail -f /tmp/log/stdout.log | grep -i "data_source\|MYPREFIX"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Plugin Data Contract](PLUGINS_DEV_DATA_CONTRACT.md) - Output format specification
|
||||
- [Plugin Settings System](PLUGINS_DEV_SETTINGS.md) - How to define settings
|
||||
- [Quick Start Guide](PLUGINS_DEV_QUICK_START.md) - Create your first plugin
|
||||
- [Database Schema](DATABASE.md) - Available tables and columns
|
||||
- [Debugging Plugins](DEBUG_PLUGINS.md) - Troubleshooting data issues
|
||||
249
docs/PLUGINS_DEV_DATA_CONTRACT.md
Normal file
249
docs/PLUGINS_DEV_DATA_CONTRACT.md
Normal file
@@ -0,0 +1,249 @@
|
||||
# Plugin Data Contract
|
||||
|
||||
This document specifies the exact interface between plugins and the NetAlertX core.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Every plugin must output data in this exact format to be recognized and processed correctly.
|
||||
|
||||
## Overview
|
||||
|
||||
Plugins communicate with NetAlertX by writing results to a **pipe-delimited log file**. The core reads this file, parses the data, and processes it for notifications, device discovery, and data integration.
|
||||
|
||||
**File Location:** `/tmp/log/plugins/last_result.<PREFIX>.log`
|
||||
|
||||
**Format:** Pipe-delimited (`|`), one record per line
|
||||
|
||||
**Required Columns:** 9 (mandatory) + up to 4 optional helper columns = 13 total
|
||||
|
||||
## 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`).
|
||||
|
||||
### Mandatory Columns (0–8)
|
||||
|
||||
| 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 |
|
||||
| 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 |
|
||||
| 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 |
|
||||
|
||||
### Optional Columns (9–12)
|
||||
|
||||
| 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 |
|
||||
|
||||
## Usage Guide
|
||||
|
||||
### Empty/Null Values
|
||||
|
||||
- Represent empty values as the literal string `null` (not Python `None`, SQL `NULL`, or empty string)
|
||||
- Example: `device_id|null|2023-01-02 15:56:30|status|null|null|null|null|null`
|
||||
|
||||
### Watched Values
|
||||
|
||||
**What are Watched Values?**
|
||||
|
||||
Watched values are fields that the NetAlertX core monitors for **changes between scans**. When a watched value differs from the previous scan, it can trigger notifications.
|
||||
|
||||
**How to use them:**
|
||||
- `Watched_Value1`: Always required; primary indicator of status/state
|
||||
- `Watched_Value2–4`: 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)
|
||||
|
||||
### Foreign Key
|
||||
|
||||
Use the `ForeignKey` column to link objects to a parent device by MAC address:
|
||||
|
||||
```
|
||||
device_name|192.168.1.100|2023-01-02 15:56:30|online|null|null|null|Found on network|aa:bb:cc:dd:ee:ff
|
||||
↑
|
||||
ForeignKey (MAC)
|
||||
```
|
||||
|
||||
This allows NetAlertX to:
|
||||
- Display the object on the device details page
|
||||
- Send notifications when the parent device is involved
|
||||
- Link events across plugins
|
||||
|
||||
## Examples
|
||||
|
||||
### Valid Data (9 columns, minimal)
|
||||
|
||||
```csv
|
||||
https://example.com|null|2023-01-02 15:56:30|200|null|null|null|null|null
|
||||
printer-hp-1|192.168.1.50|2023-01-02 15:56:30|online|50%|null|null|Last seen in office|aa:11:22:33:44:55
|
||||
gateway.local|null|2023-01-02 15:56:30|active|v2.1.5|null|null|Firmware version|null
|
||||
```
|
||||
|
||||
### Valid Data (13 columns, with helpers)
|
||||
|
||||
```csv
|
||||
service-api|192.168.1.100:8080|2023-01-02 15:56:30|200|45ms|true|null|Responding normally|aa:bb:cc:dd:ee:ff|extra1|extra2|extra3|extra4
|
||||
host-web-1|10.0.0.20|2023-01-02 15:56:30|active|256GB|online|ok|Production server|null|cpu:80|mem:92|disk:45|alerts:0
|
||||
```
|
||||
|
||||
### Invalid Data (Common Errors)
|
||||
|
||||
❌ **Missing required column** (only 8 separators instead of 8):
|
||||
```csv
|
||||
https://google.com|null|2023-01-02 15:56:30|200|0.7898||null|null
|
||||
↑
|
||||
Missing pipe
|
||||
```
|
||||
|
||||
❌ **Missing mandatory Watched_Value1** (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 HelpVal2–4):
|
||||
```csv
|
||||
device|null|2023-01-02 15:56:30|status|null|null|null|null|null|helper1
|
||||
↑
|
||||
Has helper but incomplete
|
||||
```
|
||||
|
||||
✅ **Complete with helpers** (all 4 helpers provided):
|
||||
```csv
|
||||
device|null|2023-01-02 15:56:30|status|null|null|null|null|null|h1|h2|h3|h4
|
||||
```
|
||||
|
||||
✅ **Complete without helpers** (9 columns exactly):
|
||||
```csv
|
||||
device|null|2023-01-02 15:56:30|status|null|null|null|null|null
|
||||
```
|
||||
|
||||
## Using `plugin_helper.py`
|
||||
|
||||
The easiest way to ensure correct output is to use the [`plugin_helper.py`](../front/plugins/plugin_helper.py) library:
|
||||
|
||||
```python
|
||||
from plugin_helper import Plugin_Objects
|
||||
|
||||
# Initialize with your plugin's prefix
|
||||
plugin_objects = Plugin_Objects("YOURPREFIX")
|
||||
|
||||
# Add objects
|
||||
plugin_objects.add_object(
|
||||
Object_PrimaryID="device_id",
|
||||
Object_SecondaryID="192.168.1.1",
|
||||
DateTime="2023-01-02 15:56:30",
|
||||
Watched_Value1="online",
|
||||
Watched_Value2=None,
|
||||
Watched_Value3=None,
|
||||
Watched_Value4=None,
|
||||
Extra="Additional data",
|
||||
ForeignKey="aa:bb:cc:dd:ee:ff",
|
||||
HelpVal1=None,
|
||||
HelpVal2=None,
|
||||
HelpVal3=None,
|
||||
HelpVal4=None
|
||||
)
|
||||
|
||||
# Write results (handles formatting, sanitization, and file creation)
|
||||
plugin_objects.write_result_file()
|
||||
```
|
||||
|
||||
The library automatically:
|
||||
- Validates data types
|
||||
- Sanitizes string values
|
||||
- Normalizes MAC addresses
|
||||
- Writes to the correct file location
|
||||
- Creates the file in `/tmp/log/plugins/last_result.<PREFIX>.log`
|
||||
|
||||
## De-duplication
|
||||
|
||||
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`
|
||||
- **Resolution:** Oldest duplicate entries are removed, newest are kept
|
||||
- **Use Case:** Prevents duplicate notifications when the same object is detected multiple times
|
||||
|
||||
## DateTime Format
|
||||
|
||||
**Required Format:** `YYYY-MM-DD HH:MM:SS`
|
||||
|
||||
**Examples:**
|
||||
- `2023-01-02 15:56:30` ✅
|
||||
- `2023-1-2 15:56:30` ❌ (missing leading zeros)
|
||||
- `2023-01-02T15:56:30` ❌ (wrong separator)
|
||||
- `15:56:30 2023-01-02` ❌ (wrong order)
|
||||
|
||||
**Python Helper:**
|
||||
```python
|
||||
from datetime import datetime
|
||||
|
||||
# Current time in correct format
|
||||
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
# Output: "2023-01-02 15:56:30"
|
||||
```
|
||||
|
||||
**Bash Helper:**
|
||||
```bash
|
||||
# Current time in correct format
|
||||
date '+%Y-%m-%d %H:%M:%S'
|
||||
# Output: 2023-01-02 15:56:30
|
||||
```
|
||||
|
||||
## Validation Checklist
|
||||
|
||||
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 2: `DateTime` in `YYYY-MM-DD HH:MM:SS` format
|
||||
- Column 3: `Watched_Value1` (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 9–12), all 4 must be present
|
||||
- [ ] **File written to** `/tmp/log/plugins/last_result.<PREFIX>.log`
|
||||
- [ ] **One record per line** (newline-delimited)
|
||||
- [ ] **No header row** (data only)
|
||||
|
||||
## Debugging
|
||||
|
||||
**View raw plugin output:**
|
||||
```bash
|
||||
cat /tmp/log/plugins/last_result.YOURPREFIX.log
|
||||
```
|
||||
|
||||
**Check line count:**
|
||||
```bash
|
||||
wc -l /tmp/log/plugins/last_result.YOURPREFIX.log
|
||||
```
|
||||
|
||||
**Validate column count (should be 8 or 12 pipes per line):**
|
||||
```bash
|
||||
cat /tmp/log/plugins/last_result.YOURPREFIX.log | awk -F'|' '{print NF}' | sort | uniq
|
||||
# Output: 9 (for minimal) or 13 (for with helpers)
|
||||
```
|
||||
|
||||
**Check core processing in logs:**
|
||||
```bash
|
||||
tail -f /tmp/log/stdout.log | grep -i "YOURPREFIX\|Plugins_Objects"
|
||||
```
|
||||
|
||||
## See Also
|
||||
|
||||
- [Plugin Settings System](PLUGINS_DEV_SETTINGS.md) - How to accept user input
|
||||
- [Data Sources](PLUGINS_DEV_DATASOURCES.md) - Different data source types
|
||||
- [Debugging Plugins](DEBUG_PLUGINS.md) - Troubleshooting plugin issues
|
||||
225
docs/PLUGINS_DEV_INDEX.md
Normal file
225
docs/PLUGINS_DEV_INDEX.md
Normal file
@@ -0,0 +1,225 @@
|
||||
# Plugin Development Documentation Index
|
||||
|
||||
Complete guide to building NetAlertX plugins, split into focused, easy-to-navigate documents.
|
||||
|
||||
## Quick Navigation
|
||||
|
||||
**Just getting started?** → [Quick Start Guide](PLUGINS_DEV_QUICK_START.md)
|
||||
**Need the basics?** → [Plugin Data Contract](PLUGINS_DEV_DATA_CONTRACT.md)
|
||||
**Configuring plugins?** → [Plugin Settings System](PLUGINS_DEV_SETTINGS.md)
|
||||
**Displaying results?** → [UI Components](PLUGINS_DEV_UI_COMPONENTS.md)
|
||||
**Getting data?** → [Data Sources](PLUGINS_DEV_DATASOURCES.md)
|
||||
|
||||
---
|
||||
|
||||
## Documentation Structure
|
||||
|
||||
### Getting Started (Start Here!)
|
||||
|
||||
| Document | Purpose | Read Time |
|
||||
|----------|---------|-----------|
|
||||
| **[Quick Start Guide](PLUGINS_DEV_QUICK_START.md)** | Create a working plugin in 5 minutes. Copy template, edit config, implement script, test. | 5 min |
|
||||
| **[Development Environment Setup](./DEV_ENV_SETUP.md)** | Set up your local development environment for plugin development. | 10 min |
|
||||
|
||||
### Core Concepts
|
||||
|
||||
| Document | Purpose | Read Time |
|
||||
|----------|---------|-----------|
|
||||
| **[Full Plugin Development Guide](PLUGINS_DEV.md)** | Comprehensive overview with references to all specialized guides. Architecture, file structure, manifest format. | 20 min |
|
||||
| **[Plugin Data Contract](PLUGINS_DEV_DATA_CONTRACT.md)** | **THE** authoritative reference for plugin output format. 9-13 pipe-delimited columns. Examples, validation, debugging. | 15 min |
|
||||
| **[Plugin Settings System](PLUGINS_DEV_SETTINGS.md)** | How to add user-configurable settings. Component types, reserved names, accessing in scripts. | 20 min |
|
||||
| **[Data Sources](PLUGINS_DEV_DATASOURCES.md)** | How plugins retrieve data: scripts, database queries, external SQLite, templates. Performance considerations. | 15 min |
|
||||
| **[UI Components](PLUGINS_DEV_UI_COMPONENTS.md)** | How to display plugin results: labels, links, color-coded badges, filters. Database mapping. | 25 min |
|
||||
|
||||
### Advanced Topics
|
||||
|
||||
| Document | Purpose | Read Time |
|
||||
|----------|---------|-----------|
|
||||
| **[Config Lifecycle](PLUGINS_DEV_CONFIG.md)** | Deep dive into how `config.json` is loaded, validated, and used throughout plugin lifecycle. | 15 min |
|
||||
| **[Debugging Plugins](DEBUG_PLUGINS.md)** | Troubleshooting plugin issues: logs, validation, common errors. | 10 min |
|
||||
|
||||
---
|
||||
|
||||
## Document Overview
|
||||
|
||||
### [Quick Start Guide](PLUGINS_DEV_QUICK_START.md)
|
||||
**For:** First-time plugin developers
|
||||
**Contains:**
|
||||
- 5 prerequisites
|
||||
- Step-by-step: folder setup, config.json, script.py, execution settings
|
||||
- Testing locally & via UI
|
||||
- Common issues table
|
||||
|
||||
**Read this first if you want a working plugin in 5 minutes.**
|
||||
|
||||
---
|
||||
|
||||
### [Full Plugin Development Guide](PLUGINS_DEV.md)
|
||||
**For:** Reference and architecture understanding
|
||||
**Contains:**
|
||||
- Quick links to all specialized guides
|
||||
- Use cases and limitations
|
||||
- Plugin workflow (4 steps)
|
||||
- File structure overview
|
||||
- Manifest format
|
||||
- References to detailed guides
|
||||
|
||||
**Read this for the big picture and to navigate to specific topics.**
|
||||
|
||||
---
|
||||
|
||||
### [Plugin Data Contract](PLUGINS_DEV_DATA_CONTRACT.md)
|
||||
**For:** Understanding plugin output format
|
||||
**Contains:**
|
||||
- 9 mandatory + 4 optional column specification
|
||||
- Column purpose and examples
|
||||
- Empty/null value handling
|
||||
- Watched values explanation
|
||||
- Foreign key usage
|
||||
- Valid & invalid data examples
|
||||
- Using `plugin_helper.py`
|
||||
- De-duplication rules
|
||||
- DateTime formatting
|
||||
- Validation checklist
|
||||
- Debugging commands
|
||||
|
||||
**Read this to understand exactly what format your plugin must output.**
|
||||
|
||||
---
|
||||
|
||||
### [Plugin Settings System](PLUGINS_DEV_SETTINGS.md)
|
||||
**For:** Adding user-configurable options
|
||||
**Contains:**
|
||||
- Setting definition structure
|
||||
- Required & optional properties
|
||||
- 10 reserved function names (RUN, CMD, RUN_TIMEOUT, WATCH, etc.)
|
||||
- Component types: text, password, select, multi-select, checkbox, textarea, label
|
||||
- Reading settings in scripts (3 methods)
|
||||
- Localized strings
|
||||
- Complete examples (website monitor, PiHole)
|
||||
- Validation & testing
|
||||
|
||||
**Read this to let users configure your plugin via the UI.**
|
||||
|
||||
---
|
||||
|
||||
### [Data Sources](PLUGINS_DEV_DATASOURCES.md)
|
||||
**For:** Choosing how your plugin gets data
|
||||
**Contains:**
|
||||
- 5 data source types: `script`, `app-db-query`, `sqlite-db-query`, `template`, `plugin_type`
|
||||
- Configuration for each type
|
||||
- How it works section for each
|
||||
- Script source best practices
|
||||
- SQL query examples for app database
|
||||
- External SQLite database setup
|
||||
- Execution order/priority
|
||||
- Performance considerations
|
||||
- Debugging data sources
|
||||
|
||||
**Read this to decide where your plugin data comes from.**
|
||||
|
||||
---
|
||||
|
||||
### [UI Components](PLUGINS_DEV_UI_COMPONENTS.md)
|
||||
**For:** Displaying plugin results in the web interface
|
||||
**Contains:**
|
||||
- Column definition structure
|
||||
- Property reference (10+ properties)
|
||||
- 15+ render types:
|
||||
- Display-only: label, device_mac, device_ip, device_name_mac, url, url_http_https, textarea_readonly
|
||||
- Interactive: textbox_save
|
||||
- Styled: threshold, replace, regex, eval
|
||||
- Chaining types (e.g., regex.url_http_https)
|
||||
- Dynamic options (SQL-driven, setting-driven)
|
||||
- Database mapping to CurrentScan
|
||||
- Static value mapping
|
||||
- Filters with 5+ examples
|
||||
- CSS classes reference
|
||||
- Complete examples
|
||||
|
||||
**Read this to make your plugin results look great and work intuitively.**
|
||||
|
||||
---
|
||||
|
||||
### [Config Lifecycle](PLUGINS_DEV_CONFIG.md)
|
||||
**For:** Understanding how plugins integrate
|
||||
**Contains:**
|
||||
- Plugin data flow diagram
|
||||
- 6-step lifecycle: loading, validation, preparation, execution, parsing, mapping
|
||||
- How `config.json` is processed
|
||||
- Plugin output contract
|
||||
- Plugin categories & execution
|
||||
- Data sources in detail
|
||||
|
||||
**Read this to understand the deep architecture.**
|
||||
|
||||
---
|
||||
|
||||
### [Debugging Plugins](DEBUG_PLUGINS.md)
|
||||
**For:** Troubleshooting when things don't work
|
||||
**Contains:**
|
||||
- Common errors & solutions
|
||||
- Log file locations
|
||||
- Commands to check status
|
||||
- Validation tools
|
||||
- Database queries for inspection
|
||||
- Permission issues
|
||||
- Performance troubleshooting
|
||||
|
||||
**Read this when your plugin isn't working.**
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference: Key Concepts
|
||||
|
||||
### Plugin Output Format
|
||||
```
|
||||
Object_PrimaryID|Object_SecondaryID|DateTime|Watched_Value1|Watched_Value2|Watched_Value3|Watched_Value4|Extra|ForeignKey
|
||||
```
|
||||
9 required columns, 4 optional helpers = 13 max
|
||||
|
||||
See: [Data Contract](PLUGINS_DEV_DATA_CONTRACT.md)
|
||||
|
||||
### Plugin Metadata (config.json)
|
||||
```json
|
||||
{
|
||||
"code_name": "my_plugin", // Folder name
|
||||
"unique_prefix": "MYPLN", // Settings prefix
|
||||
"display_name": [...], // UI label
|
||||
"data_source": "script", // Where data comes from
|
||||
"settings": [...], // User configurable
|
||||
"database_column_definitions": [...] // How to display
|
||||
}
|
||||
```
|
||||
|
||||
See: [Full Guide](PLUGINS_DEV.md), [Settings](PLUGINS_DEV_SETTINGS.md)
|
||||
|
||||
### Reserved Settings
|
||||
- `RUN` - When to execute (disabled, once, schedule, always_after_scan, etc.)
|
||||
- `RUN_SCHD` - Cron schedule
|
||||
- `CMD` - Command/script to execute
|
||||
- `RUN_TIMEOUT` - Max execution time
|
||||
- `WATCH` - Monitor for changes
|
||||
- `REPORT_ON` - Notification trigger
|
||||
|
||||
See: [Settings System](PLUGINS_DEV_SETTINGS.md)
|
||||
|
||||
### Display Types
|
||||
`label`, `device_mac`, `device_ip`, `url`, `threshold`, `replace`, `regex`, `textbox_save`, and more.
|
||||
|
||||
See: [UI Components](PLUGINS_DEV_UI_COMPONENTS.md)
|
||||
|
||||
---
|
||||
|
||||
## Tools & References
|
||||
|
||||
- **Template Plugin:** `/app/front/plugins/__template/` - Start here!
|
||||
- **Helper Library:** `/app/front/plugins/plugin_helper.py` - Use for output formatting
|
||||
- **Settings Helper:** `/app/server/helper.py` - Use `get_setting_value()` in scripts
|
||||
- **Example Plugins:** `/app/front/plugins/*/` - Study working implementations
|
||||
- **Logs:** `/tmp/log/plugins/` - Plugin output and execution logs
|
||||
- **Backend Logs:** `/tmp/log/stdout.log` - Core system logs
|
||||
|
||||
---
|
||||
|
||||
175
docs/PLUGINS_DEV_QUICK_START.md
Normal file
175
docs/PLUGINS_DEV_QUICK_START.md
Normal file
@@ -0,0 +1,175 @@
|
||||
# Plugin Development Quick Start
|
||||
|
||||
Get a working plugin up and running in 5 minutes.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Read [Development Environment Setup Guide](./DEV_ENV_SETUP.md) to set up your local environment
|
||||
- Understand [Plugin Architecture Overview](PLUGINS_DEV.md)
|
||||
|
||||
## Quick Start Steps
|
||||
|
||||
### 1. Create Your Plugin Folder
|
||||
|
||||
Start from the template to get the basic structure:
|
||||
|
||||
```bash
|
||||
cd /workspaces/NetAlertX/front/plugins
|
||||
cp -r __template my_plugin
|
||||
cd my_plugin
|
||||
```
|
||||
|
||||
### 2. Update `config.json` Identifiers
|
||||
|
||||
Edit `my_plugin/config.json` and update these critical fields:
|
||||
|
||||
```json
|
||||
{
|
||||
"code_name": "my_plugin",
|
||||
"unique_prefix": "MYPLN",
|
||||
"display_name": [{"language_code": "en_us", "string": "My Plugin"}],
|
||||
"description": [{"language_code": "en_us", "string": "My custom plugin"}]
|
||||
}
|
||||
```
|
||||
|
||||
**Important:**
|
||||
- `code_name` must match the folder name
|
||||
- `unique_prefix` must be unique and uppercase (check existing plugins to avoid conflicts)
|
||||
- `unique_prefix` is used as a prefix for all generated settings (e.g., `MYPLN_RUN`, `MYPLN_CMD`)
|
||||
|
||||
### 3. Implement Your Script
|
||||
|
||||
Edit `my_plugin/script.py` and implement your data collection logic:
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import sys
|
||||
import os
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../../server'))
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../plugins'))
|
||||
|
||||
from plugin_helper import Plugin_Objects, mylog
|
||||
from helper import get_setting_value
|
||||
from const import logPath
|
||||
|
||||
pluginName = "MYPLN"
|
||||
|
||||
LOG_PATH = logPath + "/plugins"
|
||||
LOG_FILE = os.path.join(LOG_PATH, f"script.{pluginName}.log")
|
||||
RESULT_FILE = os.path.join(LOG_PATH, f"last_result.{pluginName}.log")
|
||||
|
||||
# Initialize
|
||||
plugin_objects = Plugin_Objects(RESULT_FILE)
|
||||
|
||||
try:
|
||||
# Your data collection logic here
|
||||
# For example, scan something and collect results
|
||||
|
||||
# Add an object to results
|
||||
plugin_objects.add_object(
|
||||
Object_PrimaryID="example_id",
|
||||
Object_SecondaryID=None,
|
||||
DateTime="2023-01-02 15:56:30",
|
||||
Watched_Value1="value1",
|
||||
Watched_Value2=None,
|
||||
Watched_Value3=None,
|
||||
Watched_Value4=None,
|
||||
Extra="additional_data",
|
||||
ForeignKey=None
|
||||
)
|
||||
|
||||
# Write results to the log file
|
||||
plugin_objects.write_result_file()
|
||||
|
||||
except Exception as e:
|
||||
mylog("none", f"Error: {e}")
|
||||
sys.exit(1)
|
||||
```
|
||||
|
||||
### 4. Configure Execution
|
||||
|
||||
Edit the `RUN` and `CMD` settings in `config.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"function": "RUN",
|
||||
"type": {"dataType":"string", "elements": [{"elementType": "select", "elementOptions": [], "transformers": []}]},
|
||||
"default_value": "disabled",
|
||||
"options": ["disabled", "once", "schedule"],
|
||||
"localized": ["name", "description"],
|
||||
"name": [{"language_code":"en_us", "string": "When to run"}],
|
||||
"description": [{"language_code":"en_us", "string": "Enable plugin execution"}]
|
||||
},
|
||||
{
|
||||
"function": "CMD",
|
||||
"type": {"dataType":"string", "elements": [{"elementType": "input", "elementOptions": [], "transformers": []}]},
|
||||
"default_value": "python3 /app/front/plugins/my_plugin/script.py",
|
||||
"localized": ["name", "description"],
|
||||
"name": [{"language_code":"en_us", "string": "Command"}],
|
||||
"description": [{"language_code":"en_us", "string": "Command to execute"}]
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Test Your Plugin
|
||||
|
||||
#### In Dev Container
|
||||
|
||||
```bash
|
||||
# Test the script directly
|
||||
python3 /workspaces/NetAlertX/front/plugins/my_plugin/script.py
|
||||
|
||||
# Check the results
|
||||
cat /tmp/log/plugins/last_result.MYPLN.log
|
||||
```
|
||||
|
||||
#### Via UI
|
||||
|
||||
1. Restart backend: Run task `[Dev Container] Start Backend (Python)`
|
||||
2. Open Settings → Plugin Settings → My Plugin
|
||||
3. Set `My Plugin - When to run` to `once`
|
||||
4. Click Save
|
||||
5. Check `/tmp/log/plugins/last_result.MYPLN.log` for output
|
||||
|
||||
### 6. Check Results
|
||||
|
||||
Verify your plugin is working:
|
||||
|
||||
```bash
|
||||
# Check if result file was generated
|
||||
ls -la /tmp/log/plugins/last_result.MYPLN.log
|
||||
|
||||
# View contents
|
||||
cat /tmp/log/plugins/last_result.MYPLN.log
|
||||
|
||||
# Check backend logs for errors
|
||||
tail -f /tmp/log/stdout.log | grep "my_plugin\|MYPLN"
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
Now that you have a working basic plugin:
|
||||
|
||||
1. **Add Settings**: Customize behavior via user-configurable settings (see [PLUGINS_DEV_SETTINGS.md](PLUGINS_DEV_SETTINGS.md))
|
||||
2. **Implement Data Contract**: Structure your output correctly (see [PLUGINS_DEV_DATA_CONTRACT.md](PLUGINS_DEV_DATA_CONTRACT.md))
|
||||
3. **Configure UI**: Display plugin results in the web interface (see [PLUGINS_DEV_UI_COMPONENTS.md](PLUGINS_DEV_UI_COMPONENTS.md))
|
||||
4. **Map to Database**: Import data into NetAlertX tables like `CurrentScan` or `Devices`
|
||||
5. **Set Schedules**: Run your plugin on a schedule (see [PLUGINS_DEV_CONFIG.md](PLUGINS_DEV_CONFIG.md))
|
||||
|
||||
## Common Issues
|
||||
|
||||
| Issue | Solution |
|
||||
|-------|----------|
|
||||
| "Module not found" errors | Ensure `sys.path` includes `/app/server` and `/app/front/plugins` |
|
||||
| Settings not appearing | Restart backend and clear browser cache |
|
||||
| Results not showing up | Check `/tmp/log/plugins/*.log` and `/tmp/log/stdout.log` for errors |
|
||||
| Permission denied | Plugin runs in container, use absolute paths like `/app/front/plugins/...` |
|
||||
|
||||
## Resources
|
||||
|
||||
- [Full Plugin Development Guide](PLUGINS_DEV.md)
|
||||
- [Plugin Data Contract](PLUGINS_DEV_DATA_CONTRACT.md)
|
||||
- [Plugin Settings System](PLUGINS_DEV_SETTINGS.md)
|
||||
- [Data Sources](PLUGINS_DEV_DATASOURCES.md)
|
||||
- [UI Components](PLUGINS_DEV_UI_COMPONENTS.md)
|
||||
- [Debugging Plugins](DEBUG_PLUGINS.md)
|
||||
517
docs/PLUGINS_DEV_SETTINGS.md
Normal file
517
docs/PLUGINS_DEV_SETTINGS.md
Normal file
@@ -0,0 +1,517 @@
|
||||
# Plugin Settings System
|
||||
|
||||
Learn how to let users configure your plugin via the NetAlertX UI Settings page.
|
||||
|
||||
> [!TIP]
|
||||
> For the higher-level settings flow and lifecycle, see [Settings System Documentation](./SETTINGS_SYSTEM.md).
|
||||
|
||||
## Overview
|
||||
|
||||
Plugin settings allow users to configure:
|
||||
- **Execution schedule** (when the plugin runs)
|
||||
- **Runtime parameters** (API keys, URLs, thresholds)
|
||||
- **Behavior options** (which features to enable/disable)
|
||||
- **Command overrides** (customize the executed script)
|
||||
|
||||
All settings are defined in your plugin's `config.json` file under the `"settings"` array.
|
||||
|
||||
## Setting Definition Structure
|
||||
|
||||
Each setting is a JSON object with required and optional properties:
|
||||
|
||||
```json
|
||||
{
|
||||
"function": "UNIQUE_CODE",
|
||||
"type": {
|
||||
"dataType": "string",
|
||||
"elements": [
|
||||
{
|
||||
"elementType": "input",
|
||||
"elementOptions": [],
|
||||
"transformers": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"default_value": "default_value_here",
|
||||
"options": [],
|
||||
"localized": ["name", "description"],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Display Name"
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Help text describing the setting"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Required Properties
|
||||
|
||||
| Property | Type | Description | Example |
|
||||
|----------|------|-------------|---------|
|
||||
| `function` | string | Unique identifier for the setting. Used in manifest and when reading values. See [Reserved Function Names](#reserved-function-names) for special values | `"MY_CUSTOM_SETTING"` |
|
||||
| `type` | object | Defines the UI component and data type | See [Component Types](#component-types) |
|
||||
| `default_value` | varies | Initial value shown in UI | `"https://example.com"` |
|
||||
| `localized` | array | Which properties have translations | `["name", "description"]` |
|
||||
| `name` | array | Display name in Settings UI (localized) | See [Localized Strings](#localized-strings) |
|
||||
|
||||
## Optional Properties
|
||||
|
||||
| Property | Type | Description | Example |
|
||||
|----------|------|-------------|---------|
|
||||
| `description` | array | Help text in Settings UI (localized) | See [Localized Strings](#localized-strings) |
|
||||
| `options` | array | Valid values for select/checkbox controls | `["option1", "option2"]` |
|
||||
| `events` | string | Trigger action button: `"test"` or `"run"` | `"test"` for notifications |
|
||||
| `maxLength` | number | Character limit for input fields | `100` |
|
||||
| `readonly` | boolean | Make field read-only | `true` |
|
||||
| `override_value` | object | Template-based value override (advanced) | See [Templates](#templates) |
|
||||
|
||||
## Reserved Function Names
|
||||
|
||||
These function names have special meaning and control core plugin behavior:
|
||||
|
||||
### Core Execution Settings
|
||||
|
||||
| Function | Purpose | Type | Required | Options |
|
||||
|----------|---------|------|----------|---------|
|
||||
| `RUN` | **When to execute the plugin** | select | **YES** | `"disabled"`, `"once"`, `"schedule"`, `"always_after_scan"`, `"before_name_updates"`, `"on_new_device"`, `"before_config_save"` |
|
||||
| `RUN_SCHD` | **Cron schedule** | input | If `RUN="schedule"` | Cron format: `"0 * * * *"` (hourly) |
|
||||
| `CMD` | **Command/script to execute** | input | **YES** | Linux command or path to script |
|
||||
| `RUN_TIMEOUT` | **Maximum execution time in seconds** | input | optional | Numeric: `"60"`, `"120"`, etc. |
|
||||
|
||||
### Data & Filtering Settings
|
||||
|
||||
| Function | Purpose | Type | Required | Options |
|
||||
|----------|---------|------|----------|---------|
|
||||
| `WATCH` | **Which columns to monitor for changes** | multi-select | optional | Column names from data contract |
|
||||
| `REPORT_ON` | **When to send notifications** | select | optional | `"new"`, `"watched-changed"`, `"watched-not-changed"`, `"missing-in-last-scan"` |
|
||||
| `DB_PATH` | **External database path** | input | If using SQLite plugin | File path: `"/etc/pihole/pihole-FTL.db"` |
|
||||
|
||||
### API & Data Settings
|
||||
|
||||
| Function | Purpose | Type | Required | Options |
|
||||
|----------|---------|------|----------|---------|
|
||||
| `API_SQL` | **Generate API JSON file** | (reserved) | Not implemented | — |
|
||||
|
||||
## Component Types
|
||||
|
||||
### Text Input
|
||||
|
||||
Simple text field for API keys, URLs, thresholds, etc.
|
||||
|
||||
```json
|
||||
{
|
||||
"function": "URL",
|
||||
"type": {
|
||||
"dataType": "string",
|
||||
"elements": [
|
||||
{
|
||||
"elementType": "input",
|
||||
"elementOptions": [],
|
||||
"transformers": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"default_value": "https://api.example.com",
|
||||
"localized": ["name", "description"],
|
||||
"name": [{"language_code": "en_us", "string": "API URL"}],
|
||||
"description": [{"language_code": "en_us", "string": "The API endpoint to query"}]
|
||||
}
|
||||
```
|
||||
|
||||
### Password Input
|
||||
|
||||
Secure field with SHA256 hashing transformer.
|
||||
|
||||
```json
|
||||
{
|
||||
"function": "API_KEY",
|
||||
"type": {
|
||||
"dataType": "string",
|
||||
"elements": [
|
||||
{
|
||||
"elementType": "input",
|
||||
"elementOptions": [{"type": "password"}],
|
||||
"transformers": ["sha256"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"default_value": "",
|
||||
"localized": ["name", "description"],
|
||||
"name": [{"language_code": "en_us", "string": "API Key"}],
|
||||
"description": [{"language_code": "en_us", "string": "Stored securely with SHA256 hashing"}]
|
||||
}
|
||||
```
|
||||
|
||||
### Dropdown/Select
|
||||
|
||||
Choose from predefined options.
|
||||
|
||||
```json
|
||||
{
|
||||
"function": "RUN",
|
||||
"type": {
|
||||
"dataType": "string",
|
||||
"elements": [
|
||||
{
|
||||
"elementType": "select",
|
||||
"elementOptions": [],
|
||||
"transformers": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"default_value": "disabled",
|
||||
"options": ["disabled", "once", "schedule", "always_after_scan"],
|
||||
"localized": ["name", "description"],
|
||||
"name": [{"language_code": "en_us", "string": "When to run"}],
|
||||
"description": [{"language_code": "en_us", "string": "Select execution trigger"}]
|
||||
}
|
||||
```
|
||||
|
||||
### Multi-Select
|
||||
|
||||
Select multiple values (returns array).
|
||||
|
||||
```json
|
||||
{
|
||||
"function": "WATCH",
|
||||
"type": {
|
||||
"dataType": "array",
|
||||
"elements": [
|
||||
{
|
||||
"elementType": "select",
|
||||
"elementOptions": [{"isMultiSelect": true}],
|
||||
"transformers": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"default_value": [],
|
||||
"options": ["Status", "IP_Address", "Response_Time"],
|
||||
"localized": ["name", "description"],
|
||||
"name": [{"language_code": "en_us", "string": "Watch columns"}],
|
||||
"description": [{"language_code": "en_us", "string": "Which columns trigger notifications on change"}]
|
||||
}
|
||||
```
|
||||
|
||||
### Checkbox
|
||||
|
||||
Boolean toggle.
|
||||
|
||||
```json
|
||||
{
|
||||
"function": "ENABLED",
|
||||
"type": {
|
||||
"dataType": "boolean",
|
||||
"elements": [
|
||||
{
|
||||
"elementType": "input",
|
||||
"elementOptions": [{"type": "checkbox"}],
|
||||
"transformers": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"default_value": false,
|
||||
"localized": ["name", "description"],
|
||||
"name": [{"language_code": "en_us", "string": "Enable feature"}],
|
||||
"description": [{"language_code": "en_us", "string": "Toggle this feature on/off"}]
|
||||
}
|
||||
```
|
||||
|
||||
### Textarea
|
||||
|
||||
Multi-line text input.
|
||||
|
||||
```json
|
||||
{
|
||||
"function": "CUSTOM_CONFIG",
|
||||
"type": {
|
||||
"dataType": "string",
|
||||
"elements": [
|
||||
{
|
||||
"elementType": "textarea",
|
||||
"elementOptions": [],
|
||||
"transformers": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"default_value": "",
|
||||
"localized": ["name", "description"],
|
||||
"name": [{"language_code": "en_us", "string": "Custom Configuration"}],
|
||||
"description": [{"language_code": "en_us", "string": "Enter configuration (one per line)"}]
|
||||
}
|
||||
```
|
||||
|
||||
### Read-Only Label
|
||||
|
||||
Display information without user input.
|
||||
|
||||
```json
|
||||
{
|
||||
"function": "STATUS_DISPLAY",
|
||||
"type": {
|
||||
"dataType": "string",
|
||||
"elements": [
|
||||
{
|
||||
"elementType": "input",
|
||||
"elementOptions": [{"readonly": true}],
|
||||
"transformers": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"default_value": "Ready",
|
||||
"localized": ["name"],
|
||||
"name": [{"language_code": "en_us", "string": "Status"}]
|
||||
}
|
||||
```
|
||||
|
||||
## Using Settings in Your Script
|
||||
|
||||
### Method 1: Via `get_setting_value()` Helper
|
||||
|
||||
**Recommended approach** — clean and simple:
|
||||
|
||||
```python
|
||||
from helper import get_setting_value
|
||||
|
||||
# Read the setting by function name with plugin prefix
|
||||
api_url = get_setting_value('MYPLN_API_URL')
|
||||
api_key = get_setting_value('MYPLN_API_KEY')
|
||||
watch_columns = get_setting_value('MYPLN_WATCH') # Returns list if multi-select
|
||||
|
||||
# Use in your script
|
||||
mylog("none", f"Connecting to {api_url} with key {api_key}")
|
||||
```
|
||||
|
||||
### Method 2: Via Command Parameters
|
||||
|
||||
For more complex scenarios where you need to **pass settings as command-line arguments**:
|
||||
|
||||
Define `params` in your `config.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"params": [
|
||||
{
|
||||
"name": "api_url",
|
||||
"type": "setting",
|
||||
"value": "MYPLN_API_URL"
|
||||
},
|
||||
{
|
||||
"name": "timeout",
|
||||
"type": "setting",
|
||||
"value": "MYPLN_RUN_TIMEOUT"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Update your `CMD` setting:
|
||||
|
||||
```json
|
||||
{
|
||||
"function": "CMD",
|
||||
"default_value": "python3 /app/front/plugins/my_plugin/script.py --url={api_url} --timeout={timeout}"
|
||||
}
|
||||
```
|
||||
|
||||
The framework will replace `{api_url}` and `{timeout}` with actual values before execution.
|
||||
|
||||
### Method 3: Via Environment Variables (check with maintainer)
|
||||
|
||||
Settings are also available as environment variables:
|
||||
|
||||
```bash
|
||||
# Environment variable format: <PREFIX>_<FUNCTION>
|
||||
MY_PLUGIN_API_URL
|
||||
MY_PLUGIN_API_KEY
|
||||
MY_PLUGIN_RUN
|
||||
```
|
||||
|
||||
In Python:
|
||||
```python
|
||||
import os
|
||||
|
||||
api_url = os.environ.get('MYPLN_API_URL', 'default_value')
|
||||
```
|
||||
|
||||
## Localized Strings
|
||||
|
||||
Settings and UI text support multiple languages. Define translations in the `name` and `description` arrays:
|
||||
|
||||
```json
|
||||
{
|
||||
"localized": ["name", "description"],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "API URL"
|
||||
},
|
||||
{
|
||||
"language_code": "es_es",
|
||||
"string": "URL de API"
|
||||
},
|
||||
{
|
||||
"language_code": "de_de",
|
||||
"string": "API-URL"
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Enter the API endpoint URL"
|
||||
},
|
||||
{
|
||||
"language_code": "es_es",
|
||||
"string": "Ingrese la URL del endpoint de API"
|
||||
},
|
||||
{
|
||||
"language_code": "de_de",
|
||||
"string": "Geben Sie die API-Endpunkt-URL ein"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
- `en_us` - English (required)
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
### Example 1: Website Monitor Plugin
|
||||
|
||||
```json
|
||||
{
|
||||
"settings": [
|
||||
{
|
||||
"function": "RUN",
|
||||
"type": {"dataType": "string", "elements": [{"elementType": "select", "elementOptions": [], "transformers": []}]},
|
||||
"default_value": "disabled",
|
||||
"options": ["disabled", "once", "schedule"],
|
||||
"localized": ["name", "description"],
|
||||
"name": [{"language_code": "en_us", "string": "When to run"}],
|
||||
"description": [{"language_code": "en_us", "string": "Enable website monitoring"}]
|
||||
},
|
||||
{
|
||||
"function": "RUN_SCHD",
|
||||
"type": {"dataType": "string", "elements": [{"elementType": "input", "elementOptions": [], "transformers": []}]},
|
||||
"default_value": "*/5 * * * *",
|
||||
"localized": ["name", "description"],
|
||||
"name": [{"language_code": "en_us", "string": "Schedule"}],
|
||||
"description": [{"language_code": "en_us", "string": "Cron format (default: every 5 minutes)"}]
|
||||
},
|
||||
{
|
||||
"function": "CMD",
|
||||
"type": {"dataType": "string", "elements": [{"elementType": "input", "elementOptions": [], "transformers": []}]},
|
||||
"default_value": "python3 /app/front/plugins/website_monitor/script.py urls={urls}",
|
||||
"localized": ["name", "description"],
|
||||
"name": [{"language_code": "en_us", "string": "Command"}],
|
||||
"description": [{"language_code": "en_us", "string": "Command to execute"}]
|
||||
},
|
||||
{
|
||||
"function": "RUN_TIMEOUT",
|
||||
"type": {"dataType": "string", "elements": [{"elementType": "input", "elementOptions": [], "transformers": []}]},
|
||||
"default_value": "60",
|
||||
"localized": ["name", "description"],
|
||||
"name": [{"language_code": "en_us", "string": "Timeout"}],
|
||||
"description": [{"language_code": "en_us", "string": "Maximum execution time in seconds"}]
|
||||
},
|
||||
{
|
||||
"function": "URLS",
|
||||
"type": {"dataType": "array", "elements": [{"elementType": "input", "elementOptions": [], "transformers": []}]},
|
||||
"default_value": ["https://example.com"],
|
||||
"maxLength": 200,
|
||||
"localized": ["name", "description"],
|
||||
"name": [{"language_code": "en_us", "string": "URLs to monitor"}],
|
||||
"description": [{"language_code": "en_us", "string": "One URL per line"}]
|
||||
},
|
||||
{
|
||||
"function": "WATCH",
|
||||
"type": {"dataType": "array", "elements": [{"elementType": "select", "elementOptions": [{"isMultiSelect": true}], "transformers": []}]},
|
||||
"default_value": ["Status_Code"],
|
||||
"options": ["Status_Code", "Response_Time", "Certificate_Expiry"],
|
||||
"localized": ["name", "description"],
|
||||
"name": [{"language_code": "en_us", "string": "Watch columns"}],
|
||||
"description": [{"language_code": "en_us", "string": "Which changes trigger notifications"}]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Example 2: PiHole Integration Plugin
|
||||
|
||||
```json
|
||||
{
|
||||
"settings": [
|
||||
{
|
||||
"function": "RUN",
|
||||
"type": {"dataType": "string", "elements": [{"elementType": "select", "elementOptions": [], "transformers": []}]},
|
||||
"default_value": "disabled",
|
||||
"options": ["disabled", "schedule"],
|
||||
"localized": ["name", "description"],
|
||||
"name": [{"language_code": "en_us", "string": "When to run"}],
|
||||
"description": [{"language_code": "en_us", "string": "Enable PiHole integration"}]
|
||||
},
|
||||
{
|
||||
"function": "DB_PATH",
|
||||
"type": {"dataType": "string", "elements": [{"elementType": "input", "elementOptions": [], "transformers": []}]},
|
||||
"default_value": "/etc/pihole/pihole-FTL.db",
|
||||
"localized": ["name", "description"],
|
||||
"name": [{"language_code": "en_us", "string": "Database path"}],
|
||||
"description": [{"language_code": "en_us", "string": "Path to pihole-FTL.db inside container"}]
|
||||
},
|
||||
{
|
||||
"function": "API_KEY",
|
||||
"type": {"dataType": "string", "elements": [{"elementType": "input", "elementOptions": [{"type": "password"}], "transformers": ["sha256"]}]},
|
||||
"default_value": "",
|
||||
"localized": ["name", "description"],
|
||||
"name": [{"language_code": "en_us", "string": "API Key"}],
|
||||
"description": [{"language_code": "en_us", "string": "PiHole API key (optional, stored securely)"}]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Validation & Testing
|
||||
|
||||
### Check Settings Are Recognized
|
||||
|
||||
After saving your `config.json`:
|
||||
|
||||
1. Restart the backend: Run task `[Dev Container] Start Backend (Python)`
|
||||
2. Open Settings page in UI
|
||||
3. Navigate to Plugin Settings
|
||||
4. Look for your plugin's settings
|
||||
|
||||
### Read Setting Values in Script
|
||||
|
||||
Test that values are accessible:
|
||||
|
||||
```python
|
||||
from helper import get_setting_value
|
||||
|
||||
# Try to read a setting
|
||||
value = get_setting_value('MYPLN_API_URL')
|
||||
mylog('none', f"Setting value: {value}")
|
||||
|
||||
# Should print the user-configured value or default
|
||||
```
|
||||
|
||||
### Debug Settings
|
||||
|
||||
Check backend logs:
|
||||
|
||||
```bash
|
||||
tail -f /tmp/log/stdout.log | grep -i "setting\|MYPLN"
|
||||
```
|
||||
|
||||
## See Also
|
||||
|
||||
- [Settings System Documentation](./SETTINGS_SYSTEM.md) - Full settings flow and lifecycle
|
||||
- [Quick Start Guide](PLUGINS_DEV_QUICK_START.md) - Get a working plugin quickly
|
||||
- [Plugin Data Contract](PLUGINS_DEV_DATA_CONTRACT.md) - Output data format
|
||||
- [UI Components](PLUGINS_DEV_UI_COMPONENTS.md) - Display plugin results
|
||||
642
docs/PLUGINS_DEV_UI_COMPONENTS.md
Normal file
642
docs/PLUGINS_DEV_UI_COMPONENTS.md
Normal file
@@ -0,0 +1,642 @@
|
||||
# Plugin UI Components
|
||||
|
||||
Configure how your plugin's data is displayed in the NetAlertX web interface.
|
||||
|
||||
## Overview
|
||||
|
||||
Plugin results are displayed in the UI via the **Plugins page** and **Device details tabs**. You control the appearance and functionality of these displays by defining `database_column_definitions` in your plugin's `config.json`.
|
||||
|
||||
Each column definition specifies:
|
||||
- Which data field to display
|
||||
- How to render it (label, link, color-coded badge, etc.)
|
||||
- What CSS classes to apply
|
||||
- What transformations to apply (regex, string replacement, etc.)
|
||||
|
||||
## Column Definition Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"column": "Object_PrimaryID",
|
||||
"mapped_to_column": "devMac",
|
||||
"mapped_to_column_data": null,
|
||||
"css_classes": "col-sm-2",
|
||||
"show": true,
|
||||
"type": "device_mac",
|
||||
"default_value": "",
|
||||
"options": [],
|
||||
"options_params": [],
|
||||
"localized": ["name"],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "MAC Address"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Required | Description |
|
||||
|----------|------|----------|-------------|
|
||||
| `column` | string | **YES** | Source column name from data contract (e.g., `Object_PrimaryID`, `Watched_Value1`) |
|
||||
| `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"`) |
|
||||
| `show` | boolean | **YES** | Whether to display in UI (must be `true` to appear) |
|
||||
| `type` | string | **YES** | How to render the value (see [Render Types](#render-types)) |
|
||||
| `default_value` | varies | **YES** | Default if column is empty |
|
||||
| `options` | array | no | Options for `select`/`threshold`/`replace`/`regex` types |
|
||||
| `options_params` | array | no | Dynamic options from SQL or settings |
|
||||
| `localized` | array | **YES** | Which properties need translations (e.g., `["name", "description"]`) |
|
||||
| `name` | array | **YES** | Display name in UI (localized strings) |
|
||||
| `description` | array | no | Help text in UI (localized strings) |
|
||||
| `maxLength` | number | no | Character limit for input fields |
|
||||
|
||||
## Render Types
|
||||
|
||||
### Display-Only Types
|
||||
|
||||
These render as read-only display elements:
|
||||
|
||||
#### `label`
|
||||
Plain text display (read-only).
|
||||
|
||||
```json
|
||||
{
|
||||
"column": "Watched_Value1",
|
||||
"show": true,
|
||||
"type": "label",
|
||||
"localized": ["name"],
|
||||
"name": [{"language_code": "en_us", "string": "Status"}]
|
||||
}
|
||||
```
|
||||
|
||||
**Output:** `online`
|
||||
|
||||
---
|
||||
|
||||
#### `device_mac`
|
||||
Renders as a clickable link to the device with the given MAC address.
|
||||
|
||||
```json
|
||||
{
|
||||
"column": "ForeignKey",
|
||||
"show": true,
|
||||
"type": "device_mac",
|
||||
"localized": ["name"],
|
||||
"name": [{"language_code": "en_us", "string": "Device"}]
|
||||
}
|
||||
```
|
||||
|
||||
**Input:** `aa:bb:cc:dd:ee:ff`
|
||||
**Output:** Clickable link to device details page
|
||||
|
||||
---
|
||||
|
||||
#### `device_ip`
|
||||
Resolves an IP address to a MAC address and creates a device link.
|
||||
|
||||
```json
|
||||
{
|
||||
"column": "Object_SecondaryID",
|
||||
"show": true,
|
||||
"type": "device_ip",
|
||||
"localized": ["name"],
|
||||
"name": [{"language_code": "en_us", "string": "Host"}]
|
||||
}
|
||||
```
|
||||
|
||||
**Input:** `192.168.1.100`
|
||||
**Output:** Link to device with that IP (if known)
|
||||
|
||||
---
|
||||
|
||||
#### `device_name_mac`
|
||||
Creates a device link with the target device's name as the link label.
|
||||
|
||||
```json
|
||||
{
|
||||
"column": "Object_PrimaryID",
|
||||
"show": true,
|
||||
"type": "device_name_mac",
|
||||
"localized": ["name"],
|
||||
"name": [{"language_code": "en_us", "string": "Device Name"}]
|
||||
}
|
||||
```
|
||||
|
||||
**Input:** `aa:bb:cc:dd:ee:ff`
|
||||
**Output:** Device name (clickable link to device)
|
||||
|
||||
---
|
||||
|
||||
#### `url`
|
||||
Renders as a clickable HTTP/HTTPS link.
|
||||
|
||||
```json
|
||||
{
|
||||
"column": "Watched_Value1",
|
||||
"show": true,
|
||||
"type": "url",
|
||||
"localized": ["name"],
|
||||
"name": [{"language_code": "en_us", "string": "Endpoint"}]
|
||||
}
|
||||
```
|
||||
|
||||
**Input:** `https://example.com/api`
|
||||
**Output:** Clickable link
|
||||
|
||||
---
|
||||
|
||||
#### `url_http_https`
|
||||
Creates two links (HTTP and HTTPS) as lock icons for the given IP/hostname.
|
||||
|
||||
```json
|
||||
{
|
||||
"column": "Object_SecondaryID",
|
||||
"show": true,
|
||||
"type": "url_http_https",
|
||||
"localized": ["name"],
|
||||
"name": [{"language_code": "en_us", "string": "Web Links"}]
|
||||
}
|
||||
```
|
||||
|
||||
**Input:** `192.168.1.50`
|
||||
**Output:** 🔓 HTTP link | 🔒 HTTPS link
|
||||
|
||||
---
|
||||
|
||||
#### `textarea_readonly`
|
||||
Multi-line read-only display with newlines preserved.
|
||||
|
||||
```json
|
||||
{
|
||||
"column": "Extra",
|
||||
"show": true,
|
||||
"type": "textarea_readonly",
|
||||
"localized": ["name"],
|
||||
"name": [{"language_code": "en_us", "string": "Details"}]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Interactive Types
|
||||
|
||||
#### `textbox_save`
|
||||
User-editable text box that persists changes to the database (typically `UserData` column).
|
||||
|
||||
```json
|
||||
{
|
||||
"column": "UserData",
|
||||
"show": true,
|
||||
"type": "textbox_save",
|
||||
"default_value": "",
|
||||
"localized": ["name"],
|
||||
"name": [{"language_code": "en_us", "string": "Notes"}]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Styled/Transformed Types
|
||||
|
||||
#### `label` with `threshold`
|
||||
Color-codes values based on ranges. Useful for status codes, latency, capacity percentages.
|
||||
|
||||
```json
|
||||
{
|
||||
"column": "Watched_Value1",
|
||||
"show": true,
|
||||
"type": "threshold",
|
||||
"options": [
|
||||
{
|
||||
"maximum": 199,
|
||||
"hexColor": "#792D86" // Purple for <199
|
||||
},
|
||||
{
|
||||
"maximum": 299,
|
||||
"hexColor": "#5B862D" // Green for 200-299
|
||||
},
|
||||
{
|
||||
"maximum": 399,
|
||||
"hexColor": "#7D862D" // Orange for 300-399
|
||||
},
|
||||
{
|
||||
"maximum": 499,
|
||||
"hexColor": "#BF6440" // Red-orange for 400-499
|
||||
},
|
||||
{
|
||||
"maximum": 999,
|
||||
"hexColor": "#D33115" // Dark red for 500+
|
||||
}
|
||||
],
|
||||
"localized": ["name"],
|
||||
"name": [{"language_code": "en_us", "string": "HTTP Status"}]
|
||||
}
|
||||
```
|
||||
|
||||
**How it works:**
|
||||
- Value `150` → Purple (≤199)
|
||||
- Value `250` → Green (≤299)
|
||||
- Value `350` → Orange (≤399)
|
||||
- Value `450` → Red-orange (≤499)
|
||||
- Value `550` → Dark red (>500)
|
||||
|
||||
---
|
||||
|
||||
#### `replace`
|
||||
Replaces specific values with display strings or HTML.
|
||||
|
||||
```json
|
||||
{
|
||||
"column": "Watched_Value2",
|
||||
"show": true,
|
||||
"type": "replace",
|
||||
"options": [
|
||||
{
|
||||
"equals": "online",
|
||||
"replacement": "<i class='fa-solid fa-circle' style='color: green;'></i> Online"
|
||||
},
|
||||
{
|
||||
"equals": "offline",
|
||||
"replacement": "<i class='fa-solid fa-circle' style='color: red;'></i> Offline"
|
||||
},
|
||||
{
|
||||
"equals": "idle",
|
||||
"replacement": "<i class='fa-solid fa-circle' style='color: yellow;'></i> Idle"
|
||||
}
|
||||
],
|
||||
"localized": ["name"],
|
||||
"name": [{"language_code": "en_us", "string": "Status"}]
|
||||
}
|
||||
```
|
||||
|
||||
**Output Examples:**
|
||||
- `"online"` → 🟢 Online
|
||||
- `"offline"` → 🔴 Offline
|
||||
- `"idle"` → 🟡 Idle
|
||||
|
||||
---
|
||||
|
||||
#### `regex`
|
||||
Applies a regular expression to extract/transform values.
|
||||
|
||||
```json
|
||||
{
|
||||
"column": "Watched_Value1",
|
||||
"show": true,
|
||||
"type": "regex",
|
||||
"options": [
|
||||
{
|
||||
"type": "regex",
|
||||
"param": "([0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3})"
|
||||
}
|
||||
],
|
||||
"localized": ["name"],
|
||||
"name": [{"language_code": "en_us", "string": "IP Address"}]
|
||||
}
|
||||
```
|
||||
|
||||
**Input:** `Host: 192.168.1.100 Port: 8080`
|
||||
**Output:** `192.168.1.100`
|
||||
|
||||
---
|
||||
|
||||
#### `eval`
|
||||
Evaluates JavaScript code with access to the column value (use `${value}` or `{value}`).
|
||||
|
||||
```json
|
||||
{
|
||||
"column": "Watched_Value1",
|
||||
"show": true,
|
||||
"type": "eval",
|
||||
"default_value": "",
|
||||
"localized": ["name"],
|
||||
"name": [{"language_code": "en_us", "string": "Formatted Value"}]
|
||||
}
|
||||
```
|
||||
|
||||
**Example with custom formatting:**
|
||||
```json
|
||||
{
|
||||
"column": "Watched_Value1",
|
||||
"show": true,
|
||||
"type": "eval",
|
||||
"options": [
|
||||
{
|
||||
"type": "eval",
|
||||
"param": "`<b>${value}</b> units`"
|
||||
}
|
||||
],
|
||||
"localized": ["name"],
|
||||
"name": [{"language_code": "en_us", "string": "Value with Units"}]
|
||||
}
|
||||
```
|
||||
|
||||
**Input:** `42`
|
||||
**Output:** **42** units
|
||||
|
||||
---
|
||||
|
||||
### Chaining Types
|
||||
|
||||
You can chain multiple transformations with dot notation:
|
||||
|
||||
```json
|
||||
{
|
||||
"column": "Watched_Value3",
|
||||
"show": true,
|
||||
"type": "regex.url_http_https",
|
||||
"options": [
|
||||
{
|
||||
"type": "regex",
|
||||
"param": "([\\d.:]+)" // Extract IP/host
|
||||
}
|
||||
],
|
||||
"localized": ["name"],
|
||||
"name": [{"language_code": "en_us", "string": "HTTP/S Links"}]
|
||||
}
|
||||
```
|
||||
|
||||
**Flow:**
|
||||
1. Apply regex to extract `192.168.1.50` from input
|
||||
2. Create HTTP/HTTPS links for that host
|
||||
|
||||
---
|
||||
|
||||
## Dynamic Options
|
||||
|
||||
### SQL-Driven Select
|
||||
|
||||
Use SQL query results to populate dropdown options:
|
||||
|
||||
```json
|
||||
{
|
||||
"column": "Watched_Value2",
|
||||
"show": true,
|
||||
"type": "select",
|
||||
"options": ["{value}"],
|
||||
"options_params": [
|
||||
{
|
||||
"name": "value",
|
||||
"type": "sql",
|
||||
"value": "SELECT devType as id, devType as name FROM Devices UNION SELECT 'Unknown' as id, 'Unknown' as name ORDER BY id"
|
||||
}
|
||||
],
|
||||
"localized": ["name"],
|
||||
"name": [{"language_code": "en_us", "string": "Device Type"}]
|
||||
}
|
||||
```
|
||||
|
||||
The SQL query must return exactly **2 columns:**
|
||||
- **First column (id):** Option value
|
||||
- **Second column (name):** Display label
|
||||
|
||||
---
|
||||
|
||||
### Setting-Driven Select
|
||||
|
||||
Use plugin settings to populate options:
|
||||
|
||||
```json
|
||||
{
|
||||
"column": "Watched_Value1",
|
||||
"show": true,
|
||||
"type": "select",
|
||||
"options": ["{value}"],
|
||||
"options_params": [
|
||||
{
|
||||
"name": "value",
|
||||
"type": "setting",
|
||||
"value": "MYPLN_AVAILABLE_STATUSES"
|
||||
}
|
||||
],
|
||||
"localized": ["name"],
|
||||
"name": [{"language_code": "en_us", "string": "Status"}]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Mapping to Database Tables
|
||||
|
||||
### Mapping to `CurrentScan`
|
||||
|
||||
To import plugin data into the device scan pipeline (for notifications, heuristics, etc.):
|
||||
|
||||
1. Add `"mapped_to_table": "CurrentScan"` at the root level of `config.json`
|
||||
2. Add `"mapped_to_column"` property to each column definition
|
||||
|
||||
```json
|
||||
{
|
||||
"code_name": "my_device_scanner",
|
||||
"unique_prefix": "MYSCAN",
|
||||
"mapped_to_table": "CurrentScan",
|
||||
"database_column_definitions": [
|
||||
{
|
||||
"column": "Object_PrimaryID",
|
||||
"mapped_to_column": "cur_MAC",
|
||||
"show": true,
|
||||
"type": "device_mac",
|
||||
"localized": ["name"],
|
||||
"name": [{"language_code": "en_us", "string": "MAC Address"}]
|
||||
},
|
||||
{
|
||||
"column": "Object_SecondaryID",
|
||||
"mapped_to_column": "cur_IP",
|
||||
"show": true,
|
||||
"type": "device_ip",
|
||||
"localized": ["name"],
|
||||
"name": [{"language_code": "en_us", "string": "IP Address"}]
|
||||
},
|
||||
{
|
||||
"column": "NameDoesntMatter",
|
||||
"mapped_to_column": "cur_ScanMethod",
|
||||
"mapped_to_column_data": {
|
||||
"value": "MYSCAN"
|
||||
},
|
||||
"show": true,
|
||||
"type": "label",
|
||||
"localized": ["name"],
|
||||
"name": [{"language_code": "en_us", "string": "Scan Method"}]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Using Static Values
|
||||
|
||||
Use `mapped_to_column_data` to map a static value instead of reading from a column:
|
||||
|
||||
```json
|
||||
{
|
||||
"column": "NameDoesntMatter",
|
||||
"mapped_to_column": "cur_ScanMethod",
|
||||
"mapped_to_column_data": {
|
||||
"value": "MYSCAN"
|
||||
},
|
||||
"show": true,
|
||||
"type": "label",
|
||||
"localized": ["name"],
|
||||
"name": [{"language_code": "en_us", "string": "Discovery Method"}]
|
||||
}
|
||||
```
|
||||
|
||||
This always sets `cur_ScanMethod` to `"MYSCAN"` regardless of column data.
|
||||
|
||||
---
|
||||
|
||||
## Filters
|
||||
|
||||
Control which rows are displayed based on filter conditions. Filters are applied on the client-side in JavaScript.
|
||||
|
||||
```json
|
||||
{
|
||||
"data_filters": [
|
||||
{
|
||||
"compare_column": "Object_PrimaryID",
|
||||
"compare_operator": "==",
|
||||
"compare_field_id": "txtMacFilter",
|
||||
"compare_js_template": "'{value}'.toString()",
|
||||
"compare_use_quotes": true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
| Property | Description |
|
||||
|----------|-------------|
|
||||
| `compare_column` | The column from plugin results to compare (left side) |
|
||||
| `compare_operator` | JavaScript operator: `==`, `!=`, `<`, `>`, `<=`, `>=`, `includes`, `startsWith` |
|
||||
| `compare_field_id` | HTML input field ID containing the filter value (right side) |
|
||||
| `compare_js_template` | JavaScript template to transform values. Use `{value}` placeholder |
|
||||
| `compare_use_quotes` | If `true`, wrap result in quotes for string comparison |
|
||||
|
||||
**Example: Filter by MAC address**
|
||||
|
||||
```json
|
||||
{
|
||||
"data_filters": [
|
||||
{
|
||||
"compare_column": "ForeignKey",
|
||||
"compare_operator": "==",
|
||||
"compare_field_id": "txtMacFilter",
|
||||
"compare_js_template": "'{value}'.toString()",
|
||||
"compare_use_quotes": true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
When viewing a device detail page, the `txtMacFilter` field is populated with that device's MAC, and only rows where `ForeignKey == MAC` are shown.
|
||||
|
||||
---
|
||||
|
||||
## Example: Complete Column Definitions
|
||||
|
||||
```json
|
||||
{
|
||||
"database_column_definitions": [
|
||||
{
|
||||
"column": "Object_PrimaryID",
|
||||
"mapped_to_column": "cur_MAC",
|
||||
"css_classes": "col-sm-2",
|
||||
"show": true,
|
||||
"type": "device_mac",
|
||||
"default_value": "",
|
||||
"localized": ["name"],
|
||||
"name": [{"language_code": "en_us", "string": "MAC Address"}]
|
||||
},
|
||||
{
|
||||
"column": "Object_SecondaryID",
|
||||
"mapped_to_column": "cur_IP",
|
||||
"css_classes": "col-sm-2",
|
||||
"show": true,
|
||||
"type": "device_ip",
|
||||
"default_value": "unknown",
|
||||
"localized": ["name"],
|
||||
"name": [{"language_code": "en_us", "string": "IP Address"}]
|
||||
},
|
||||
{
|
||||
"column": "DateTime",
|
||||
"css_classes": "col-sm-2",
|
||||
"show": true,
|
||||
"type": "label",
|
||||
"default_value": "",
|
||||
"localized": ["name"],
|
||||
"name": [{"language_code": "en_us", "string": "Last Seen"}]
|
||||
},
|
||||
{
|
||||
"column": "Watched_Value1",
|
||||
"css_classes": "col-sm-2",
|
||||
"show": true,
|
||||
"type": "threshold",
|
||||
"options": [
|
||||
{"maximum": 199, "hexColor": "#792D86"},
|
||||
{"maximum": 299, "hexColor": "#5B862D"},
|
||||
{"maximum": 399, "hexColor": "#7D862D"},
|
||||
{"maximum": 499, "hexColor": "#BF6440"},
|
||||
{"maximum": 999, "hexColor": "#D33115"}
|
||||
],
|
||||
"localized": ["name"],
|
||||
"name": [{"language_code": "en_us", "string": "HTTP Status"}]
|
||||
},
|
||||
{
|
||||
"column": "Watched_Value2",
|
||||
"css_classes": "col-sm-1",
|
||||
"show": true,
|
||||
"type": "label",
|
||||
"default_value": "",
|
||||
"localized": ["name"],
|
||||
"name": [{"language_code": "en_us", "string": "Response Time"}]
|
||||
},
|
||||
{
|
||||
"column": "Extra",
|
||||
"css_classes": "col-sm-3",
|
||||
"show": true,
|
||||
"type": "textarea_readonly",
|
||||
"default_value": "",
|
||||
"localized": ["name"],
|
||||
"name": [{"language_code": "en_us", "string": "Additional Info"}]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CSS Classes
|
||||
|
||||
Use Bootstrap grid classes to control column widths in tables:
|
||||
|
||||
| Class | Width | Usage |
|
||||
|-------|-------|-------|
|
||||
| `col-sm-1` | ~8% | Very narrow (icons, status) |
|
||||
| `col-sm-2` | ~16% | Narrow (MACs, IPs) |
|
||||
| `col-sm-3` | ~25% | Medium (names, URLs) |
|
||||
| `col-sm-4` | ~33% | Medium-wide (descriptions) |
|
||||
| `col-sm-6` | ~50% | Wide (large content) |
|
||||
|
||||
---
|
||||
|
||||
## Validation Checklist
|
||||
|
||||
- [ ] All columns have `"show": true` or `false`
|
||||
- [ ] Display columns with `"type"` specified from supported types
|
||||
- [ ] Localized strings include at least `en_us`
|
||||
- [ ] `mapped_to_column` matches target table schema (if using mapping)
|
||||
- [ ] Options/thresholds have correct structure
|
||||
- [ ] CSS classes are valid Bootstrap grid classes
|
||||
- [ ] Chaining types (e.g., `regex.url_http_https`) are supported combinations
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Plugin Data Contract](PLUGINS_DEV_DATA_CONTRACT.md) - What data fields are available
|
||||
- [Plugin Settings System](PLUGINS_DEV_SETTINGS.md) - Configure user input
|
||||
- [Database Mapping](PLUGINS_DEV.md#-mapping-the-plugin-results-into-a-database-table) - Map data to core tables
|
||||
- [Debugging Plugins](DEBUG_PLUGINS.md) - Troubleshoot display issues
|
||||
10
mkdocs.yml
10
mkdocs.yml
@@ -89,8 +89,14 @@ nav:
|
||||
- Environment Setup: DEV_ENV_SETUP.md
|
||||
- Builds: BUILDS.md
|
||||
- Devcontainer: DEV_DEVCONTAINER.md
|
||||
- Custom Plugins: PLUGINS_DEV.md
|
||||
- Plugin Config: PLUGINS_DEV_CONFIG.md
|
||||
- Custom Plugins:
|
||||
- Overview: PLUGINS_DEV.md
|
||||
- Quick Start: PLUGINS_DEV_QUICK_START.md
|
||||
- Data Contract: PLUGINS_DEV_DATA_CONTRACT.md
|
||||
- Settings System: PLUGINS_DEV_SETTINGS.md
|
||||
- Data Sources: PLUGINS_DEV_DATASOURCES.md
|
||||
- UI Components: PLUGINS_DEV_UI_COMPONENTS.md
|
||||
- Config Lifecycle: PLUGINS_DEV_CONFIG.md
|
||||
- Frontend Development: FRONTEND_DEVELOPMENT.md
|
||||
- Database: DATABASE.md
|
||||
- Settings: SETTINGS_SYSTEM.md
|
||||
|
||||
Reference in New Issue
Block a user