{ "code_name": "pihole_monitor", "unique_prefix": "PIHOLEMON", "plugin_type": "device_scanner", "execution_order": "Layer_0", "enabled": true, "data_source": "script", "mapped_to_table": "CurrentScan", "data_filters": [ { "compare_column": "objectPrimaryId", "compare_operator": "==", "compare_field_id": "txtMacFilter", "compare_js_template": "'{value}'.toString()", "compare_use_quotes": true } ], "show_ui": true, "localized": ["display_name", "description", "icon"], "display_name": [ { "language_code": "en_us", "string": "Pi-hole Monitor" } ], "description": [ { "language_code": "en_us", "string": "Flags devices with a spike in blocked DNS queries - a common malware/compromised-device signature - and imports devices from one or two Pi-hole v6 instances." } ], "icon": [ { "language_code": "en_us", "string": "" } ], "params": [], "settings": [ { "function": "RUN", "events": ["run"], "type": { "dataType": "string", "elements": [ { "elementType": "select", "elementOptions": [], "transformers": [] } ] }, "default_value": "disabled", "options": [ "disabled", "once", "schedule", "always_after_scan", "on_new_device", "on_notification" ], "localized": ["name", "description"], "name": [{ "language_code": "en_us", "string": "When to run" }], "description": [ { "language_code": "en_us", "string": "schedule is recommended over always_after_scan." } ] }, { "function": "RUN_SCHD", "type": { "dataType": "string", "elements": [ { "elementType": "span", "elementOptions": [ { "cssClasses": "input-group-addon validityCheck" }, { "getStringKey": "Gen_ValidIcon" } ], "transformers": [] }, { "elementType": "input", "elementOptions": [ { "focusout": "validateRegex(this)" }, { "base64Regex": "Xig/OlwqfCg/OlswLTldfFsxLTVdWzAtOV18WzAtOV0rLVswLTldKyg/Oi9bMC05XSspP3xcKi9bMC05XSspKSg/OiwoPzpbMC05XXxbMS01XVswLTldfFswLTldKy1bMC05XSsoPzovWzAtOV0rKT98XCovWzAtOV0rKSkqXHMrKD86XCp8KD86WzAtOV18MVswLTldfDJbMC0zXXxbMC05XSstWzAtOV0rKD86L1swLTldKyk/fFwqL1swLTldKykpKD86LCg/OlswLTldfDFbMC05XXwyWzAtM118WzAtOV0rLVswLTldKyg/Oi9bMC05XSspP3xcKi9bMC05XSspKSpccysoPzpcKnwoPzpbMS05XXxbMTJdWzAtOV18M1swMV18WzAtOV0rLVswLTldKyg/Oi9bMC05XSspP3xcKi9bMC05XSspKSg/OiwoPzpbMS05XXxbMTJdWzAtOV18M1swMV18WzAtOV0rLVswLTldKyg/Oi9bMC05XSspP3xcKi9bMC05XSspKSpccysoPzpcKnwoPzpbMS05XXwxWzAtMl18WzAtOV0rLVswLTldKyg/Oi9bMC05XSspP3xcKi9bMC05XSspKSg/OiwoPzpbMS05XXwxWzAtMl18WzAtOV0rLVswLTldKyg/Oi9bMC05XSspP3xcKi9bMC05XSspKSpccysoPzpcKnwoPzpbMC02XXxbMC02XS1bMC02XSg/Oi9bMC05XSspP3xcKi9bMC05XSspKSg/OiwoPzpbMC02XXxbMC02XS1bMC02XSg/Oi9bMC05XSspP3xcKi9bMC05XSspKSok" } ], "transformers": [] } ] }, "default_value": "*/5 * * * *", "options": [], "localized": ["name", "description"], "name": [{ "language_code": "en_us", "string": "Schedule" }], "description": [ { "language_code": "en_us", "string": "Only used when RUN is schedule. Default: every 5 minutes." } ] }, { "function": "SET_ALWAYS", "type": { "dataType": "array", "elements": [ { "elementType": "select", "elementOptions": [{ "multiple": "true", "orderable": "true" }], "transformers": [] } ] }, "default_value": ["devMac", "devLastIP", "devName", "devVendor"], "options": ["devMac", "devLastIP", "devName", "devVendor"], "localized": ["name", "description"], "name": [{ "language_code": "en_us", "string": "Set always columns" }], "description": [ { "language_code": "en_us", "string": "These columns are treated as authoritative and overwrite existing values (including those set by other plugins), unless the current value was set by the user (Source = USER or LOCKED)." } ] }, { "function": "SET_EMPTY", "type": { "dataType": "array", "elements": [ { "elementType": "select", "elementOptions": [{ "multiple": "true", "orderable": "true" }], "transformers": [] } ] }, "default_value": [], "options": ["devMac", "devLastIP", "devName", "devVendor", "devType", "devSourcePlugin"], "localized": ["name", "description"], "name": [{ "language_code": "en_us", "string": "Set empty columns" }], "description": [ { "language_code": "en_us", "string": "These columns are only overwritten if they are empty or their Source is NEWDEV." } ] }, { "function": "PRIMARY_URL", "type": { "dataType": "string", "elements": [ { "elementType": "input", "elementOptions": [], "transformers": [] } ] }, "default_value": "", "options": [], "localized": ["name", "description"], "name": [{ "language_code": "en_us", "string": "Primary Pi-hole URL" }], "description": [ { "language_code": "en_us", "string": "Required. URL to your primary Pi-hole instance, for example http://pi.hole:8080/." } ] }, { "function": "PRIMARY_PASSWORD", "type": { "dataType": "string", "elements": [ { "elementType": "input", "elementOptions": [{ "type": "password" }], "transformers": [] } ] }, "default_value": "", "options": [], "localized": ["name", "description"], "name": [{ "language_code": "en_us", "string": "Primary Pi-hole password" }], "description": [ { "language_code": "en_us", "string": "Web UI password or App Password for the primary Pi-hole. A dedicated App Password is recommended over the admin login password." } ] }, { "function": "PRIMARY_VERIFY_SSL", "type": { "dataType": "boolean", "elements": [ { "elementType": "input", "elementOptions": [{ "type": "checkbox" }], "transformers": [] } ] }, "default_value": true, "options": [], "localized": ["name", "description"], "name": [{ "language_code": "en_us", "string": "Primary: Verify SSL" }], "description": [ { "language_code": "en_us", "string": "Only relevant for an https:// primary URL. Disabling this accepts any certificate, including one from an attacker on the network - install the self-signed CA as trusted instead if possible. Leave on unless you understand the trade-off." } ] }, { "function": "SECONDARY_URL", "type": { "dataType": "string", "elements": [ { "elementType": "input", "elementOptions": [], "transformers": [] } ] }, "default_value": "", "options": [], "localized": ["name", "description"], "name": [{ "language_code": "en_us", "string": "Secondary Pi-hole URL (optional)" }], "description": [ { "language_code": "en_us", "string": "Optional. Leave empty if you only run one Pi-hole - most setups do. If set, this second instance (typically a failover/secondary resolver) is imported and its blocked-query counts combined with the primary's, so a device can't dodge detection by using the less-watched resolver, e.g. http://192.168.1.11:8080/." } ] }, { "function": "SECONDARY_PASSWORD", "type": { "dataType": "string", "elements": [ { "elementType": "input", "elementOptions": [{ "type": "password" }], "transformers": [] } ] }, "default_value": "", "options": [], "localized": ["name", "description"], "name": [{ "language_code": "en_us", "string": "Secondary Pi-hole password (optional)" }], "description": [ { "language_code": "en_us", "string": "Only used if Secondary Pi-hole URL is set." } ] }, { "function": "SECONDARY_VERIFY_SSL", "type": { "dataType": "boolean", "elements": [ { "elementType": "input", "elementOptions": [{ "type": "checkbox" }], "transformers": [] } ] }, "default_value": true, "options": [], "localized": ["name", "description"], "name": [{ "language_code": "en_us", "string": "Secondary: Verify SSL" }], "description": [ { "language_code": "en_us", "string": "Only relevant for an https:// secondary URL. Same trade-off as the primary instance's Verify SSL - independent because primary and secondary can each be on http:// or https:// separately." } ] }, { "function": "GET_OFFLINE", "type": { "dataType": "boolean", "elements": [ { "elementType": "input", "elementOptions": [{ "type": "checkbox" }], "transformers": [] } ] }, "default_value": false, "options": [], "localized": ["name", "description"], "name": [{ "language_code": "en_us", "string": "Import offline devices" }], "description": [ { "language_code": "en_us", "string": "If enabled, devices not seen recently (see below) are still imported/updated." } ] }, { "function": "CONSIDER_ONLINE", "type": { "dataType": "integer", "elements": [ { "elementType": "input", "elementOptions": [{ "type": "number" }], "transformers": [] } ] }, "default_value": 300, "options": [], "localized": ["name", "description"], "name": [{ "language_code": "en_us", "string": "Online if last seen (seconds)" }], "description": [ { "language_code": "en_us", "string": "Time in seconds to consider a device online." } ] }, { "function": "API_MAXCLIENTS", "type": { "dataType": "integer", "elements": [ { "elementType": "input", "elementOptions": [{ "type": "number" }], "transformers": [] } ] }, "default_value": 500, "options": [], "localized": ["name", "description"], "name": [{ "language_code": "en_us", "string": "Max clients per instance" }], "description": [ { "language_code": "en_us", "string": "Maximum number of devices requested from each Pi-hole instance's device list." } ] }, { "function": "FAKE_MAC", "type": { "dataType": "boolean", "elements": [ { "elementType": "input", "elementOptions": [{ "type": "checkbox" }], "transformers": [] } ] }, "default_value": false, "options": [], "localized": ["name", "description"], "name": [{ "language_code": "en_us", "string": "Generate fake MAC from IP" }], "description": [ { "language_code": "en_us", "string": "Devices with a non-standard hardware address are skipped unless enabled, in which case a deterministic fake MAC is derived from the IP - accept the data-consistency trade-off that implies." } ] }, { "function": "GET_OWNER", "type": { "dataType": "boolean", "elements": [ { "elementType": "input", "elementOptions": [{ "type": "checkbox" }], "transformers": [] } ] }, "default_value": true, "options": [], "localized": ["name", "description"], "name": [{ "language_code": "en_us", "string": "Look up device owner" }], "description": [ { "language_code": "en_us", "string": "If enabled, looks up an already-known device's owner (devOwner) - device import itself never depends on this. Disable if you don't use device owners or don't want the extra lookup." } ] }, { "function": "MULTIPLIER", "type": { "dataType": "string", "elements": [ { "elementType": "input", "elementOptions": [{ "type": "number" }], "transformers": [] } ] }, "default_value": "4", "options": [], "localized": ["name", "description"], "name": [{ "language_code": "en_us", "string": "Anomaly multiplier" }], "description": [ { "language_code": "en_us", "string": "Flag a device when the blocked queries it generated since the last run exceed this many times its own recent per-run average. Starting value, tune after a week of real data." } ] }, { "function": "MIN_BLOCKED", "type": { "dataType": "string", "elements": [ { "elementType": "input", "elementOptions": [{ "type": "number" }], "transformers": [] } ] }, "default_value": "20", "options": [], "localized": ["name", "description"], "name": [{ "language_code": "en_us", "string": "Minimum blocked queries" }], "description": [ { "language_code": "en_us", "string": "Ignore devices below this many blocked queries since the last run, even if the multiplier is exceeded (e.g. 1 -> 5 is technically 5x but is statistical noise). Depends on your Schedule - a more frequent schedule means fewer blocked queries accumulate per run, so a lower value may be needed." } ] }, { "function": "HISTORY_DAYS", "type": { "dataType": "string", "elements": [ { "elementType": "input", "elementOptions": [{ "type": "number" }], "transformers": [] } ] }, "default_value": "7", "options": [], "localized": ["name", "description"], "name": [{ "language_code": "en_us", "string": "History window (days)" }], "description": [ { "language_code": "en_us", "string": "How many days of recent runs to keep per device for the rolling baseline. A real time window, not a sample count - it means the same thing regardless of your Schedule above, and a faster schedule only adds more data points inside it. Default 7 (one week)." } ] }, { "function": "WATCH", "type": { "dataType": "array", "elements": [ { "elementType": "select", "elementOptions": [{ "multiple": "true", "orderable": "true" }], "transformers": [] } ] }, "default_value": ["watchedValue4"], "options": ["watchedValue1", "watchedValue2", "watchedValue3", "watchedValue4"], "localized": ["name", "description"], "name": [{ "language_code": "en_us", "string": "Watched" }], "description": [ { "language_code": "en_us", "string": "Which columns count as \"changed\" for notification purposes. Default is watchedValue4 (the anomaly/normal flag) only - watchedValue3 (raw blocked-query count) fluctuates every run by design and would otherwise notify constantly." } ] }, { "function": "REPORT_ON", "type": { "dataType": "array", "elements": [ { "elementType": "select", "elementOptions": [{ "multiple": "true", "orderable": "true" }], "transformers": [] } ] }, "default_value": ["watched-changed"], "options": ["new", "watched-changed", "watched-not-changed", "missing-in-last-scan"], "localized": ["name", "description"], "name": [{ "language_code": "en_us", "string": "Report on" }], "description": [ { "language_code": "en_us", "string": "Which statuses generate a notification, dispatched through whichever publisher(s) you already have enabled under Settings (ntfy, Apprise, email, ...) - this plugin never sends notifications on its own. Default: only watched-changed, i.e. when a device flips between normal and anomaly." } ] }, { "function": "CMD", "type": { "dataType": "string", "elements": [ { "elementType": "input", "elementOptions": [{ "readonly": "true" }], "transformers": [] } ] }, "default_value": "python3 /app/server/plugins/pihole_monitor/pihole_monitor.py", "options": [], "localized": ["name", "description"], "name": [{ "language_code": "en_us", "string": "Command" }], "description": [{ "language_code": "en_us", "string": "Command to run. This can not be changed." }] }, { "function": "RUN_TIMEOUT", "type": { "dataType": "integer", "elements": [ { "elementType": "input", "elementOptions": [{ "type": "number" }], "transformers": [] } ] }, "default_value": 60, "options": [], "localized": ["name", "description"], "name": [{ "language_code": "en_us", "string": "Run timeout" }], "description": [ { "language_code": "en_us", "string": "Maximum time in seconds to wait for the script to finish (covers device list + stats for up to two Pi-hole instances, plus optional owner lookups)." } ] } ], "database_column_definitions": [ { "column": "index", "css_classes": "col-sm-1", "show": true, "type": "none", "default_value": "", "options": [], "localized": ["name"], "name": [{ "language_code": "en_us", "string": "Index" }] }, { "column": "objectPrimaryId", "mapped_to_column": "scanMac", "css_classes": "col-sm-2", "show": true, "type": "device_name_mac", "default_value": "", "options": [], "localized": ["name"], "name": [{ "language_code": "en_us", "string": "Device" }] }, { "column": "objectSecondaryId", "mapped_to_column": "scanLastIP", "css_classes": "col-sm-2", "show": true, "type": "device_ip", "default_value": "", "options": [], "localized": ["name"], "name": [{ "language_code": "en_us", "string": "IP" }] }, { "column": "watchedValue1", "mapped_to_column": "scanName", "css_classes": "col-sm-2", "show": true, "type": "label", "default_value": "", "options": [], "localized": ["name"], "name": [{ "language_code": "en_us", "string": "Name" }] }, { "column": "watchedValue2", "mapped_to_column": "scanVendor", "css_classes": "col-sm-2", "show": true, "type": "label", "default_value": "", "options": [], "localized": ["name"], "name": [{ "language_code": "en_us", "string": "Vendor" }] }, { "column": "watchedValue3", "css_classes": "col-sm-1", "show": true, "type": "label", "default_value": "", "options": [], "localized": ["name"], "name": [{ "language_code": "en_us", "string": "Blocked queries" }] }, { "column": "watchedValue4", "css_classes": "col-sm-1", "show": true, "type": "replace", "default_value": "", "options": [ { "equals": "anomaly", "replacement": "
" }, { "equals": "normal", "replacement": "
" } ], "localized": ["name"], "name": [{ "language_code": "en_us", "string": "Anomaly?" }] }, { "column": "extra", "mapped_to_column": "scanSourcePlugin", "css_classes": "col-sm-3", "show": true, "type": "label", "default_value": "", "options": [], "localized": ["name"], "name": [{ "language_code": "en_us", "string": "Detail" }] }, { "column": "dateTimeChanged", "css_classes": "col-sm-2", "show": true, "type": "label", "default_value": "", "options": [], "localized": ["name"], "name": [{ "language_code": "en_us", "string": "Last checked" }] }, { "column": "status", "css_classes": "col-sm-1", "show": true, "type": "replace", "default_value": "", "options": [ { "equals": "watched-not-changed", "replacement": "
" }, { "equals": "watched-changed", "replacement": "
" }, { "equals": "new", "replacement": "
" }, { "equals": "missing-in-last-scan", "replacement": "
" } ], "localized": ["name"], "name": [{ "language_code": "en_us", "string": "Status" }] } ] }