🔌NBTSCAN plugin #693
This commit is contained in:
@@ -40,7 +40,7 @@ ENV S6_CMD_WAIT_FOR_SERVICES_MAXTIME=0
|
|||||||
|
|
||||||
RUN apk update --no-cache \
|
RUN apk update --no-cache \
|
||||||
&& apk add --no-cache bash zip lsblk gettext-envsubst sudo mtr tzdata s6-overlay \
|
&& apk add --no-cache bash zip lsblk gettext-envsubst sudo mtr tzdata s6-overlay \
|
||||||
&& apk add --no-cache curl arp-scan iproute2 iproute2-ss nmap nmap-scripts traceroute net-tools net-snmp-tools bind-tools awake ca-certificates \
|
&& apk add --no-cache curl arp-scan iproute2 iproute2-ss nmap nmap-scripts traceroute nbtscan net-tools net-snmp-tools bind-tools awake ca-certificates \
|
||||||
&& apk add --no-cache sqlite php83 php83-fpm php83-cgi php83-curl php83-sqlite3 php83-session \
|
&& apk add --no-cache sqlite php83 php83-fpm php83-cgi php83-curl php83-sqlite3 php83-session \
|
||||||
&& apk add --no-cache python3 nginx \
|
&& apk add --no-cache python3 nginx \
|
||||||
&& apk add --no-cache dcron \
|
&& apk add --no-cache dcron \
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ COPY --chmod=775 --chown=${USER_ID}:${USER_GID} . ${INSTALL_DIR}/
|
|||||||
RUN apt-get install -y \
|
RUN apt-get install -y \
|
||||||
tini snmp ca-certificates curl libwww-perl arp-scan perl apt-utils cron sudo \
|
tini snmp ca-certificates curl libwww-perl arp-scan perl apt-utils cron sudo \
|
||||||
nginx-light php php-cgi php-fpm php-sqlite3 php-curl sqlite3 dnsutils net-tools php-openssl \
|
nginx-light php php-cgi php-fpm php-sqlite3 php-curl sqlite3 dnsutils net-tools php-openssl \
|
||||||
python3 python3-dev iproute2 nmap python3-pip zip systemctl usbutils traceroute
|
python3 python3-dev iproute2 nmap python3-pip zip systemctl usbutils traceroute nbtscan
|
||||||
|
|
||||||
# Alternate dependencies
|
# Alternate dependencies
|
||||||
RUN apt-get install nginx nginx-core mtr php-fpm php8.2-fpm php-cli php8.2 php8.2-sqlite3 -y
|
RUN apt-get install nginx nginx-core mtr php-fpm php8.2-fpm php-cli php8.2 php8.2-sqlite3 -y
|
||||||
|
|||||||
@@ -235,6 +235,11 @@
|
|||||||
margin-left: 150px;
|
margin-left: 150px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#settingsPage
|
||||||
|
{
|
||||||
|
display: grid;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 767px) {
|
@media (max-width: 767px) {
|
||||||
.main-header .logo {
|
.main-header .logo {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -383,27 +383,56 @@ function filterRows(inputText) {
|
|||||||
inputText = "";
|
inputText = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
$(".table_row").each(function () {
|
$(".panel").each(function () {
|
||||||
// Check if the row id ends with '__metadata'
|
var $panel = $(this);
|
||||||
var idAttribute = $(this).attr("id");
|
var $panelHeader = $panel.find('.panel-heading');
|
||||||
if (idAttribute && idAttribute.endsWith("__metadata")) {
|
var $panelBody = $panel.find('.panel-collapse');
|
||||||
$(this).hide(); // Hide the row if it ends with '__metadata'
|
|
||||||
return; // Skip to the next iteration
|
|
||||||
}
|
|
||||||
|
|
||||||
var description = $(this).find(".setting_description").text().toLowerCase();
|
var anyVisible = false; // Flag to check if any row is visible
|
||||||
var codeName = $(this).find(".setting_name code").text().toLowerCase();
|
|
||||||
if (
|
$panelBody.find(".table_row:not(.docs)").each(function () {
|
||||||
description.includes(inputText.toLowerCase()) ||
|
var $row = $(this);
|
||||||
codeName.includes(inputText.toLowerCase())
|
|
||||||
) {
|
// Check if the row ID ends with "__metadata"
|
||||||
$(this).show(); // Show the row if it matches the input text
|
var rowId = $row.attr("id");
|
||||||
|
var isMetadataRow = rowId && rowId.endsWith("__metadata");
|
||||||
|
|
||||||
|
// Always hide metadata rows
|
||||||
|
if (isMetadataRow) {
|
||||||
|
$row.hide();
|
||||||
|
return; // Skip further processing for metadata rows
|
||||||
|
}
|
||||||
|
|
||||||
|
var description = $row.find(".setting_description").text().toLowerCase();
|
||||||
|
var codeName = $row.find(".setting_name code").text().toLowerCase();
|
||||||
|
|
||||||
|
if (
|
||||||
|
description.includes(inputText.toLowerCase()) ||
|
||||||
|
codeName.includes(inputText.toLowerCase())
|
||||||
|
) {
|
||||||
|
$row.show();
|
||||||
|
anyVisible = true; // Set the flag to true if at least one row is visible
|
||||||
|
} else {
|
||||||
|
$row.hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Determine whether to hide or show the panel based on visibility of rows
|
||||||
|
if (anyVisible) {
|
||||||
|
$panelBody.collapse('show'); // Ensure the panel body is shown if there are visible rows
|
||||||
|
$panelHeader.show(); // Show the panel header
|
||||||
|
$panel.show(); // Show the entire panel if there are visible rows
|
||||||
} else {
|
} else {
|
||||||
$(this).hide(); // Hide the row if it doesn't match the input text
|
$panelBody.collapse('hide'); // Hide the panel body if no rows are visible
|
||||||
|
$panelHeader.hide(); // Hide the panel header if no rows are visible
|
||||||
|
$panel.hide(); // Hide the entire panel if no rows are visible
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// Event listener for input change
|
// Event listener for input change
|
||||||
$("#settingsSearch").on("input", function () {
|
$("#settingsSearch").on("input", function () {
|
||||||
|
|||||||
@@ -408,7 +408,7 @@ $db->close();
|
|||||||
<div class="row actions">
|
<div class="row actions">
|
||||||
<div class="col-sm-2">
|
<div class="col-sm-2">
|
||||||
<div class="form-check toggle">
|
<div class="form-check toggle">
|
||||||
<label class="form-check-label" for="logsAutoRefresh">
|
<label class="form-check-label pointer" for="logsAutoRefresh">
|
||||||
<input class="form-check-input" type="checkbox" id="logsAutoRefresh" onchange="toggleAutoRefresh()" />
|
<input class="form-check-input" type="checkbox" id="logsAutoRefresh" onchange="toggleAutoRefresh()" />
|
||||||
Auto-refresh
|
Auto-refresh
|
||||||
</label>
|
</label>
|
||||||
@@ -416,7 +416,7 @@ $db->close();
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-sm-2">
|
<div class="col-sm-2">
|
||||||
<div class="form-check toggle">
|
<div class="form-check toggle">
|
||||||
<label class="form-check-label" for="logsAutoScroll">
|
<label class="form-check-label pointer" for="logsAutoScroll">
|
||||||
<input class="form-check-input" type="checkbox" checked id="logsAutoScroll" />
|
<input class="form-check-input" type="checkbox" checked id="logsAutoScroll" />
|
||||||
Auto-scroll
|
Auto-scroll
|
||||||
</label>
|
</label>
|
||||||
|
|||||||
@@ -577,15 +577,7 @@
|
|||||||
"description": [
|
"description": [
|
||||||
{
|
{
|
||||||
"language_code": "en_us",
|
"language_code": "en_us",
|
||||||
"string": "Add all dhcp.leases mapped paths to watch. Enter full path within the container, e.g. <code>/mnt/dhcp2.leases</code>. You must map these files accordingly in your <code>docker-compose.yml</code> file. (If you are mapping a PiHole dhcp.leases file the path in the container must contain <code>pihole</code>, e.g.: <code>:/etc/pihole/dhcp.leases</code>)"
|
"string": "Add all dhcp.leases mapped paths to watch. Enter full path within the container, e.g. <code>/mnt/dhcp2.leases</code>. You must map these files accordingly in your <code>docker-compose.yml</code> file. Supports pihole, dnsmasq, and generic formats. <br><br> For dnsmasq or pihole the path in the container must contain <code>pihole</code> or <code>dnsmasq</code>; e.g.: <code>:/etc/pihole/dhcp.leases</code>). See the above docs for details."
|
||||||
},
|
|
||||||
{
|
|
||||||
"language_code": "es_es",
|
|
||||||
"string": "Agregue todas las rutas asignadas de dhcp.leases para observar. Ingrese la ruta completa dentro del contenedor, p.e. <code>/mnt/dhcp2.leases</code>. Debe asignar estos archivos en consecuencia en su archivo <code>docker-compose.yml</code>. (Si está asignando un archivo PiHole dhcp.leases, la ruta en el contenedor debe contener <code>pihole</code>, por ejemplo: <code>:/etc/pihole/dhcp.leases</code>)"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"language_code": "de_de",
|
|
||||||
"string": "Alle zu überwachenden dhcp.leases-Pfade hinzufügen. Den absoluten Pfad innerhalb des Containers angeben, z.B. <code>/mnt/dchp2.leases</code>. Diese Dateien müssen korrekt in der <code>docker-compose.yml</code> gemapped werden. Wird eine PiHole dhcp.leases-Datei in den Container gemapped, so muss der Pfad im Container <code>pihole</code> inkludieren, z.B. <code>:/etc/pihole/dhcp.leases</code>"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
23
front/plugins/nbtscan_scan/README.md
Executable file
23
front/plugins/nbtscan_scan/README.md
Executable file
@@ -0,0 +1,23 @@
|
|||||||
|
## Overview
|
||||||
|
|
||||||
|
Plugin functionality overview and links to external resources if relevant. Include use cases if available.
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> Some tip.
|
||||||
|
|
||||||
|
### Quick setup guide
|
||||||
|
|
||||||
|
To set up the plugin correctly, make sure...
|
||||||
|
|
||||||
|
#### Required Settings
|
||||||
|
|
||||||
|
- When to run `PREF_RUN`
|
||||||
|
-
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
- Head to **Settings** > **Plugin name** to adjust the default values.
|
||||||
|
|
||||||
|
### Notes
|
||||||
|
|
||||||
|
- Additional notes, limitations, Author info.
|
||||||
337
front/plugins/nbtscan_scan/config.json
Executable file
337
front/plugins/nbtscan_scan/config.json
Executable file
@@ -0,0 +1,337 @@
|
|||||||
|
{
|
||||||
|
"code_name": "nbtscan_scan",
|
||||||
|
"unique_prefix": "NBTSCAN",
|
||||||
|
"plugin_type": "other",
|
||||||
|
"enabled": true,
|
||||||
|
"data_source": "script",
|
||||||
|
"show_ui": true,
|
||||||
|
"localized": ["display_name", "description", "icon"],
|
||||||
|
"display_name": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "NBTSCAN (Name discovery)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"icon": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "<i class=\"fa-solid fa-search\"></i>"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "A plugin to discover device names."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"name": "ips",
|
||||||
|
"type": "sql",
|
||||||
|
"value": "SELECT dev_LastIP from DEVICES order by dev_MAC",
|
||||||
|
"timeoutMultiplier": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"function": "RUN",
|
||||||
|
"events": ["run"],
|
||||||
|
"type": {
|
||||||
|
"dataType": "string",
|
||||||
|
"elements": [
|
||||||
|
{ "elementType": "select", "elementOptions": [], "transformers": [] }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default_value": "disabled",
|
||||||
|
"options": [
|
||||||
|
"disabled",
|
||||||
|
"before_name_updates",
|
||||||
|
"on_new_device",
|
||||||
|
"once",
|
||||||
|
"schedule",
|
||||||
|
"always_after_scan"
|
||||||
|
],
|
||||||
|
"localized": ["name", "description"],
|
||||||
|
"name": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "When to run"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"language_code": "es_es",
|
||||||
|
"string": "Cuándo ejecutar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"language_code": "de_de",
|
||||||
|
"string": "Wann laufen"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "When the plugin should be executed. If enabled this will execute the scan until there are no <code>(unknown)</code> or <code>(name not found)</code> devices. Setting this to <code>on_new_device</code> or a daily <code>schedule</code> is recommended.<br/><br/> Depends on the <a onclick=\"toggleAllSettings()\" href=\"#SCAN_SUBNETS\"><code>SCAN_SUBNETS</code> setting</a>."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"function": "CMD",
|
||||||
|
"type": {
|
||||||
|
"dataType": "string",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"elementType": "input",
|
||||||
|
"elementOptions": [{ "readonly": "true" }],
|
||||||
|
"transformers": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default_value": "python3 /app/front/plugins/nbtscan_scan/nbtscan.py",
|
||||||
|
"options": [],
|
||||||
|
"localized": ["name", "description"],
|
||||||
|
"name": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "Command"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"language_code": "es_es",
|
||||||
|
"string": "Comando"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"language_code": "de_de",
|
||||||
|
"string": "Befehl"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "Command to run. This can not be changed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"language_code": "es_es",
|
||||||
|
"string": "Comando a ejecutar. Esto no se puede cambiar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"language_code": "de_de",
|
||||||
|
"string": "Befehl zum Ausführen. Dies kann nicht geändert werden"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"function": "RUN_SCHD",
|
||||||
|
"type": {
|
||||||
|
"dataType": "string",
|
||||||
|
"elements": [
|
||||||
|
{ "elementType": "input", "elementOptions": [], "transformers": [] }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default_value": "*/30 * * * *",
|
||||||
|
"options": [],
|
||||||
|
"localized": ["name", "description"],
|
||||||
|
"name": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "Schedule"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"language_code": "es_es",
|
||||||
|
"string": "Schedule"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"language_code": "de_de",
|
||||||
|
"string": "Schedule"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "Only enabled if you select <code>schedule</code> in the <a href=\"#NBTSCAN_RUN\"><code>NBTSCAN_RUN</code> setting</a>. Make sure you enter the schedule in the correct cron-like format (e.g. validate at <a href=\"https://crontab.guru/\" target=\"_blank\">crontab.guru</a>). For example entering <code>0 4 * * *</code> will run the scan after 4 am in the <a onclick=\"toggleAllSettings()\" href=\"#TIMEZONE\"><code>TIMEZONE</code> you set above</a>. Will be run NEXT time the time passes."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"language_code": "es_es",
|
||||||
|
"string": "Solo está habilitado si selecciona <code>schedule</code> en la configuración <a href=\"#NBTSCAN_RUN\"><code>NBTSCAN_RUN</code></a>. Asegúrese de ingresar la programación en el formato similar a cron correcto (por ejemplo, valide en <a href=\"https://crontab.guru/\" target=\"_blank\">crontab.guru</a>). Por ejemplo, ingresar <code>0 4 * * *</code> ejecutará el escaneo después de las 4 a.m. en el <a onclick=\"toggleAllSettings()\" href=\"#TIMEZONE\"><code>TIMEZONE</ código> que configuró arriba</a>. Se ejecutará la PRÓXIMA vez que pase el tiempo."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"language_code": "de_de",
|
||||||
|
"string": "Nur aktiviert, wenn Sie <code>schedule</code> in der <a href=\"#NBTSCAN_RUN\"><code>NBTSCAN_RUN</code>-Einstellung</a> auswählen. Stellen Sie sicher, dass Sie den Zeitplan im richtigen Cron-ähnlichen Format eingeben (z. B. validieren unter <a href=\"https://crontab.guru/\" target=\"_blank\">crontab.guru</a>). Wenn Sie beispielsweise <code>0 4 * * *</code> eingeben, wird der Scan nach 4 Uhr morgens in der <a onclick=\"toggleAllSettings()\" href=\"#TIMEZONE\"><code>TIMEZONE</ ausgeführt. Code> den Sie oben festgelegt haben</a>. Wird das NÄCHSTE Mal ausgeführt, wenn die Zeit vergeht."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"function": "RUN_TIMEOUT",
|
||||||
|
"type": {
|
||||||
|
"dataType": "integer",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"elementType": "input",
|
||||||
|
"elementOptions": [{ "type": "number" }],
|
||||||
|
"transformers": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default_value": 10,
|
||||||
|
"options": [],
|
||||||
|
"localized": ["name", "description"],
|
||||||
|
"name": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "Run timeout"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"language_code": "es_es",
|
||||||
|
"string": "Tiempo límite de ejecución"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"language_code": "de_de",
|
||||||
|
"string": "Zeitüberschreitung"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "Maximum time in seconds to wait for the script to finish. If this time is exceeded the script is aborted."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"language_code": "es_es",
|
||||||
|
"string": "Tiempo máximo en segundos para esperar a que finalice el script. Si se supera este tiempo, el script se cancela."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"language_code": "de_de",
|
||||||
|
"string": "Maximale Zeit in Sekunden, die auf den Abschluss des Skripts gewartet werden soll. Bei Überschreitung dieser Zeit wird das Skript abgebrochen."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"database_column_definitions": [
|
||||||
|
{
|
||||||
|
"column": "Object_PrimaryID",
|
||||||
|
"css_classes": "col-sm-2",
|
||||||
|
"show": true,
|
||||||
|
"type": "device_name_mac",
|
||||||
|
"default_value": "",
|
||||||
|
"options": [],
|
||||||
|
"localized": ["name"],
|
||||||
|
"name": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "MAC"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"language_code": "es_es",
|
||||||
|
"string": "MAC"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"column": "Object_SecondaryID",
|
||||||
|
"css_classes": "col-sm-2",
|
||||||
|
"show": true,
|
||||||
|
"type": "label",
|
||||||
|
"default_value": "",
|
||||||
|
"options": [],
|
||||||
|
"localized": ["name"],
|
||||||
|
"name": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "IP"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"language_code": "es_es",
|
||||||
|
"string": "IP"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"column": "Watched_Value1",
|
||||||
|
"css_classes": "col-sm-2",
|
||||||
|
"show": true,
|
||||||
|
"type": "label",
|
||||||
|
"default_value": "",
|
||||||
|
"options": [],
|
||||||
|
"localized": ["name"],
|
||||||
|
"name": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "Server"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"column": "Watched_Value2",
|
||||||
|
"css_classes": "col-sm-2",
|
||||||
|
"show": true,
|
||||||
|
"type": "label",
|
||||||
|
"default_value": "",
|
||||||
|
"options": [],
|
||||||
|
"localized": ["name"],
|
||||||
|
"name": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "Name"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"column": "DateTimeCreated",
|
||||||
|
"css_classes": "col-sm-2",
|
||||||
|
"show": true,
|
||||||
|
"type": "label",
|
||||||
|
"default_value": "",
|
||||||
|
"options": [],
|
||||||
|
"localized": ["name"],
|
||||||
|
"name": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "Created"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"column": "DateTimeChanged",
|
||||||
|
"css_classes": "col-sm-2",
|
||||||
|
"show": true,
|
||||||
|
"type": "label",
|
||||||
|
"default_value": "",
|
||||||
|
"options": [],
|
||||||
|
"localized": ["name"],
|
||||||
|
"name": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "Changed"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"column": "Status",
|
||||||
|
"css_classes": "col-sm-1",
|
||||||
|
"show": true,
|
||||||
|
"type": "replace",
|
||||||
|
"default_value": "",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"equals": "watched-not-changed",
|
||||||
|
"replacement": "<div style='text-align:center'><i class='fa-solid fa-square-check'></i><div></div>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"equals": "watched-changed",
|
||||||
|
"replacement": "<div style='text-align:center'><i class='fa-solid fa-triangle-exclamation'></i></div>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"equals": "new",
|
||||||
|
"replacement": "<div style='text-align:center'><i class='fa-solid fa-circle-plus'></i></div>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"equals": "missing-in-last-scan",
|
||||||
|
"replacement": "<div style='text-align:center'><i class='fa-solid fa-question'></i></div>"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"localized": ["name"],
|
||||||
|
"name": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "Status"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
139
front/plugins/nbtscan_scan/nbtscan.py
Executable file
139
front/plugins/nbtscan_scan/nbtscan.py
Executable file
@@ -0,0 +1,139 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import os
|
||||||
|
import pathlib
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import sqlite3
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
# Define the installation path and extend the system path for plugin imports
|
||||||
|
INSTALL_PATH = "/app"
|
||||||
|
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
|
||||||
|
|
||||||
|
from plugin_helper import Plugin_Object, Plugin_Objects, decodeBase64
|
||||||
|
from plugin_utils import get_plugins_configs
|
||||||
|
from logger import mylog
|
||||||
|
from const import pluginsPath, fullDbPath
|
||||||
|
from helper import timeNowTZ, get_setting_value
|
||||||
|
from notification import write_notification
|
||||||
|
from database import DB
|
||||||
|
from device import Device_obj
|
||||||
|
|
||||||
|
# Define the current path and log file paths
|
||||||
|
CUR_PATH = str(pathlib.Path(__file__).parent.resolve())
|
||||||
|
LOG_FILE = os.path.join(CUR_PATH, 'script.log')
|
||||||
|
RESULT_FILE = os.path.join(CUR_PATH, 'last_result.log')
|
||||||
|
|
||||||
|
# Initialize the Plugin obj output file
|
||||||
|
plugin_objects = Plugin_Objects(RESULT_FILE)
|
||||||
|
|
||||||
|
pluginName = 'NBTSCAN'
|
||||||
|
|
||||||
|
def main():
|
||||||
|
mylog('verbose', [f'[{pluginName}] In script'])
|
||||||
|
|
||||||
|
# timeout = get_setting_value('NBLOOKUP_RUN_TIMEOUT')
|
||||||
|
timeout = 20
|
||||||
|
|
||||||
|
# Create a database connection
|
||||||
|
db = DB() # instance of class DB
|
||||||
|
db.open()
|
||||||
|
|
||||||
|
# Initialize the Plugin obj output file
|
||||||
|
plugin_objects = Plugin_Objects(RESULT_FILE)
|
||||||
|
|
||||||
|
# Create a Device_obj instance
|
||||||
|
device_handler = Device_obj(db)
|
||||||
|
|
||||||
|
# Retrieve devices
|
||||||
|
unknown_devices = device_handler.getUnknown()
|
||||||
|
|
||||||
|
mylog('verbose', [f'[{pluginName}] Unknown devices count: {len(unknown_devices)}'])
|
||||||
|
|
||||||
|
# TEST
|
||||||
|
# execute_name_lookup('192.168.1.121', timeout)
|
||||||
|
|
||||||
|
for device in unknown_devices:
|
||||||
|
domain_name, dns_server = execute_name_lookup(device['dev_LastIP'], timeout)
|
||||||
|
|
||||||
|
if domain_name != '':
|
||||||
|
plugin_objects.add_object(
|
||||||
|
# "MAC", "IP", "Server", "Name"
|
||||||
|
primaryId = device['dev_MAC'],
|
||||||
|
secondaryId = device['dev_LastIP'],
|
||||||
|
watched1 = dns_server,
|
||||||
|
watched2 = domain_name,
|
||||||
|
watched3 = '',
|
||||||
|
watched4 = '',
|
||||||
|
extra = '',
|
||||||
|
foreignKey = device['dev_MAC'])
|
||||||
|
|
||||||
|
plugin_objects.write_result_file()
|
||||||
|
|
||||||
|
|
||||||
|
mylog('verbose', [f'[{pluginName}] Script finished'])
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
#===============================================================================
|
||||||
|
# Execute scan
|
||||||
|
#===============================================================================
|
||||||
|
def execute_name_lookup (ip, timeout):
|
||||||
|
"""
|
||||||
|
Execute the NBLOOKUP command on IP.
|
||||||
|
"""
|
||||||
|
|
||||||
|
args = ['nbtscan', ip]
|
||||||
|
|
||||||
|
# Execute command
|
||||||
|
output = ""
|
||||||
|
|
||||||
|
try:
|
||||||
|
# try runnning a subprocess with a forced (timeout) in case the subprocess hangs
|
||||||
|
output = subprocess.check_output (args, universal_newlines=True, stderr=subprocess.STDOUT, timeout=(timeout), text=True)
|
||||||
|
|
||||||
|
mylog('verbose', [f'[{pluginName}] DEBUG OUTPUT : {output}'])
|
||||||
|
|
||||||
|
domain_name = ''
|
||||||
|
dns_server = ''
|
||||||
|
|
||||||
|
# Split the output into lines
|
||||||
|
lines = output.splitlines()
|
||||||
|
|
||||||
|
# Look for the first line containing a valid NetBIOS name entry
|
||||||
|
index = 0
|
||||||
|
for line in lines:
|
||||||
|
if ip in line:
|
||||||
|
# Split the line and extract the primary NetBIOS name
|
||||||
|
parts = line.split()
|
||||||
|
if parts:
|
||||||
|
domain_name = parts[1]
|
||||||
|
|
||||||
|
|
||||||
|
mylog('verbose', [f'[{pluginName}] Domain Name: {domain_name}'])
|
||||||
|
|
||||||
|
return domain_name, dns_server
|
||||||
|
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
# An error occurred, handle it
|
||||||
|
# if "NXDOMAIN" in e.output:
|
||||||
|
# mylog('verbose', [f'[{pluginName}]', f"No PTR record found for IP: {ip}"])
|
||||||
|
# else:
|
||||||
|
mylog('verbose', [f'[{pluginName}]', e.output])
|
||||||
|
# Handle other errors here
|
||||||
|
# mylog('verbose', [f'[{pluginName}] ⚠ ERROR - check logs'])
|
||||||
|
|
||||||
|
except subprocess.TimeoutExpired as timeErr:
|
||||||
|
mylog('verbose', [f'[{pluginName}] TIMEOUT - the process forcefully terminated as timeout reached'])
|
||||||
|
|
||||||
|
if output == "": # check if the subprocess failed
|
||||||
|
mylog('verbose', [f'[{pluginName}] Scan: FAIL - check logs'])
|
||||||
|
else:
|
||||||
|
mylog('verbose', [f'[{pluginName}] Scan: SUCCESS'])
|
||||||
|
|
||||||
|
return '', ''
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
|
|
||||||
@@ -184,11 +184,11 @@
|
|||||||
"description": [
|
"description": [
|
||||||
{
|
{
|
||||||
"language_code": "en_us",
|
"language_code": "en_us",
|
||||||
"string": "Only enabled if you select <code>schedule</code> in the <a href=\"#ARPSCAN_RUN\"><code>ARPSCAN_RUN</code> setting</a>. Make sure you enter the schedule in the correct cron-like format (e.g. validate at <a href=\"https://crontab.guru/\" target=\"_blank\">crontab.guru</a>). For example entering <code>*/30 * * * *</code> will run the scan every 30 minutes. Will be run NEXT time the time passes. <br/> It's recommended to use the same schedule interval for all plugins responsible for discovering new devices."
|
"string": "Only enabled if you select <code>schedule</code> in the <a href=\"#PIHOLE_RUN\"><code>PIHOLE_RUN</code> setting</a>. Make sure you enter the schedule in the correct cron-like format (e.g. validate at <a href=\"https://crontab.guru/\" target=\"_blank\">crontab.guru</a>). For example entering <code>*/30 * * * *</code> will run the scan every 30 minutes. Will be run NEXT time the time passes. <br/> It's recommended to use the same schedule interval for all plugins responsible for discovering new devices."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"language_code": "es_es",
|
"language_code": "es_es",
|
||||||
"string": "Solo está habilitado si selecciona <code>schedule</code> en la configuración <a href=\"#ARPSCAN_RUN\"><code>ARPSCAN_RUN</code></a>. Asegúrese de ingresar la programación en el formato similar a cron correcto (por ejemplo, valide en <a href=\"https://crontab.guru/\" target=\"_blank\">crontab.guru</a>). Por ejemplo, ingresar <code>*/30 * * * *</code> ejecutará el escaneo cada 30 minutos. Se ejecutará la PRÓXIMA vez que pase el tiempo. <br/> Se recomienda utilizar el mismo intervalo de programación para todos los complementos que analizan su red."
|
"string": "Solo está habilitado si selecciona <code>schedule</code> en la configuración <a href=\"#PIHOLE_RUN\"><code>PIHOLE_RUN</code></a>. Asegúrese de ingresar la programación en el formato similar a cron correcto (por ejemplo, valide en <a href=\"https://crontab.guru/\" target=\"_blank\">crontab.guru</a>). Por ejemplo, ingresar <code>*/30 * * * *</code> ejecutará el escaneo cada 30 minutos. Se ejecutará la PRÓXIMA vez que pase el tiempo. <br/> Se recomienda utilizar el mismo intervalo de programación para todos los complementos que analizan su red."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -442,14 +442,6 @@
|
|||||||
{
|
{
|
||||||
"language_code": "en_us",
|
"language_code": "en_us",
|
||||||
"string": "Vendor"
|
"string": "Vendor"
|
||||||
},
|
|
||||||
{
|
|
||||||
"language_code": "es_es",
|
|
||||||
"string": "Proveedor"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"language_code": "de_de",
|
|
||||||
"string": "Hersteller"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -333,7 +333,7 @@ $settingsJSON_DB = json_encode($settings, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX
|
|||||||
|
|
||||||
// Start constructing the main settings HTML
|
// Start constructing the main settings HTML
|
||||||
let pluginHtml = `
|
let pluginHtml = `
|
||||||
<div class="row table_row">
|
<div class="row table_row docs">
|
||||||
<div class="table_cell bold">
|
<div class="table_cell bold">
|
||||||
<i class="fa-regular fa-book fa-sm"></i>
|
<i class="fa-regular fa-book fa-sm"></i>
|
||||||
${getString(prefix+'_description')}
|
${getString(prefix+'_description')}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ fi
|
|||||||
apt-get install -y \
|
apt-get install -y \
|
||||||
tini snmp ca-certificates curl libwww-perl arp-scan perl apt-utils cron sudo \
|
tini snmp ca-certificates curl libwww-perl arp-scan perl apt-utils cron sudo \
|
||||||
nginx-light php php-cgi php-fpm php-sqlite3 php-curl php-openssl sqlite3 dnsutils net-tools \
|
nginx-light php php-cgi php-fpm php-sqlite3 php-curl php-openssl sqlite3 dnsutils net-tools \
|
||||||
python3 python3-dev iproute2 nmap python3-pip zip systemctl usbutils traceroute
|
python3 python3-dev iproute2 nmap python3-pip zip systemctl usbutils traceroute nbtscan
|
||||||
|
|
||||||
# alternate dependencies
|
# alternate dependencies
|
||||||
sudo apt-get install nginx nginx-core mtr php-fpm php8.2-fpm php-cli php8.2 php8.2-sqlite3 -y
|
sudo apt-get install nginx nginx-core mtr php-fpm php8.2-fpm php-cli php8.2 php8.2-sqlite3 -y
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ class app_state_class:
|
|||||||
def __init__(self, currentState, settingsSaved=None, settingsImported=None, showSpinner=False):
|
def __init__(self, currentState, settingsSaved=None, settingsImported=None, showSpinner=False):
|
||||||
# json file containing the state to communicate with the frontend
|
# json file containing the state to communicate with the frontend
|
||||||
stateFile = apiPath + '/app_state.json'
|
stateFile = apiPath + '/app_state.json'
|
||||||
|
previousState = ""
|
||||||
|
|
||||||
# if currentState == 'Initializing':
|
# if currentState == 'Initializing':
|
||||||
# checkNewVersion(False)
|
# checkNewVersion(False)
|
||||||
@@ -66,15 +67,21 @@ class app_state_class:
|
|||||||
self.currentState = currentState
|
self.currentState = currentState
|
||||||
self.lastUpdated = str(timeNowTZ())
|
self.lastUpdated = str(timeNowTZ())
|
||||||
|
|
||||||
# Check if the file exists and init values
|
|
||||||
if os.path.exists(stateFile):
|
if os.path.exists(stateFile):
|
||||||
with open(stateFile, 'r') as json_file:
|
try:
|
||||||
previousState = json.load(json_file)
|
with open(stateFile, 'r') as json_file:
|
||||||
self.settingsSaved = previousState.get("settingsSaved", 0)
|
previousState = json.load(json_file)
|
||||||
self.settingsImported = previousState.get("settingsImported", 0)
|
except json.decoder.JSONDecodeError as e:
|
||||||
self.showSpinner = previousState.get("showSpinner", False)
|
mylog('none', [f'[app_state_class] Failed to handle app_state.json: {e}'])
|
||||||
self.isNewVersion = previousState.get("isNewVersion", False)
|
|
||||||
self.isNewVersionChecked = previousState.get("isNewVersionChecked", 0)
|
|
||||||
|
# Check if the file exists and init values
|
||||||
|
if previousState != "":
|
||||||
|
self.settingsSaved = previousState.get("settingsSaved", 0)
|
||||||
|
self.settingsImported = previousState.get("settingsImported", 0)
|
||||||
|
self.showSpinner = previousState.get("showSpinner", False)
|
||||||
|
self.isNewVersion = previousState.get("isNewVersion", False)
|
||||||
|
self.isNewVersionChecked = previousState.get("isNewVersionChecked", 0)
|
||||||
else:
|
else:
|
||||||
self.settingsSaved = 0
|
self.settingsSaved = 0
|
||||||
self.settingsImported = 0
|
self.settingsImported = 0
|
||||||
@@ -96,8 +103,17 @@ class app_state_class:
|
|||||||
self.isNewVersionChecked = int(timeNow().timestamp())
|
self.isNewVersionChecked = int(timeNow().timestamp())
|
||||||
|
|
||||||
# Update .json file
|
# Update .json file
|
||||||
with open(stateFile, 'w') as json_file:
|
# with open(stateFile, 'w') as json_file:
|
||||||
json.dump(self, json_file, cls=AppStateEncoder, indent=4)
|
# json.dump(self, json_file, cls=AppStateEncoder, indent=4)
|
||||||
|
|
||||||
|
# Sanity check before saving the .json file
|
||||||
|
try:
|
||||||
|
json_data = json.dumps(self, cls=AppStateEncoder, indent=4)
|
||||||
|
with open(stateFile, 'w') as json_file:
|
||||||
|
json_file.write(json_data)
|
||||||
|
except (TypeError, ValueError) as e:
|
||||||
|
mylog('none', [f'[app_state_class] Failed to serialize object to JSON: {e}'])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def isSet(self):
|
def isSet(self):
|
||||||
|
|||||||
Reference in New Issue
Block a user