mirror of
https://github.com/jokob-sk/NetAlertX.git
synced 2025-12-06 17:15:38 -08:00
Initial commit on next_release branch
This commit is contained in:
81
back/workflows.json
Executable file
81
back/workflows.json
Executable file
@@ -0,0 +1,81 @@
|
||||
[
|
||||
{
|
||||
"name": "Sample Device Update Workflow",
|
||||
"trigger": {
|
||||
"object_type": "Devices",
|
||||
"event_type": "update"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"logic": "AND",
|
||||
"conditions": [
|
||||
{
|
||||
"field": "devVendor",
|
||||
"operator": "contains",
|
||||
"value": "Google"
|
||||
},
|
||||
{
|
||||
"field": "devIsNew",
|
||||
"operator": "equals",
|
||||
"value": "1"
|
||||
},
|
||||
{
|
||||
"logic": "OR",
|
||||
"conditions": [
|
||||
{
|
||||
"field": "devIsNew",
|
||||
"operator": "equals",
|
||||
"value": "1"
|
||||
},
|
||||
{
|
||||
"field": "devName",
|
||||
"operator": "contains",
|
||||
"value": "Google"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"type": "update_field",
|
||||
"field": "devIsNew",
|
||||
"value": "0"
|
||||
},
|
||||
{
|
||||
"type": "run_plugin",
|
||||
"plugin": "SMTP",
|
||||
"params": {
|
||||
"message": "New device from Google detected."
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Sample Plugin Object Workflow",
|
||||
"trigger": {
|
||||
"object_type": "Plugins_Objects",
|
||||
"event_type": "create"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"logic": "AND",
|
||||
"conditions": [
|
||||
{
|
||||
"field": "Plugin",
|
||||
"operator": "equals",
|
||||
"value": "ARPSCAN"
|
||||
},
|
||||
{
|
||||
"field": "Status",
|
||||
"operator": "equals",
|
||||
"value": "missing-in-last-scan"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -61,6 +61,8 @@ services:
|
||||
- ${DEV_LOCATION}/front/cloud_services.php:/app/front/cloud_services.php
|
||||
- ${DEV_LOCATION}/front/report.php:/app/front/report.php
|
||||
- ${DEV_LOCATION}/front/workflows.php:/app/front/workflows.php
|
||||
- ${DEV_LOCATION}/front/workflowsCore.php:/app/front/workflowsCore.php
|
||||
- ${DEV_LOCATION}/front/appEvents.php:/app/front/appEvents.php
|
||||
- ${DEV_LOCATION}/front/appEventsCore.php:/app/front/appEventsCore.php
|
||||
- ${DEV_LOCATION}/front/multiEditCore.php:/app/front/multiEditCore.php
|
||||
- ${DEV_LOCATION}/front/plugins:/app/front/plugins
|
||||
|
||||
@@ -37,6 +37,7 @@ export INSTALL_DIR=/app # Specify the installation directory here
|
||||
# DO NOT CHANGE ANYTHING BELOW THIS LINE!
|
||||
|
||||
CONF_FILE="app.conf"
|
||||
WF_FILE="workflows.json"
|
||||
NGINX_CONF_FILE=netalertx.conf
|
||||
DB_FILE="app.db"
|
||||
FULL_FILEDB_PATH="${INSTALL_DIR}/db/${DB_FILE}"
|
||||
@@ -54,8 +55,6 @@ if [[ $EUID -ne 0 ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[INSTALL] Copy starter ${DB_FILE} and ${CONF_FILE} if they don't exist"
|
||||
|
||||
# DANGER ZONE: ALWAYS_FRESH_INSTALL
|
||||
if [ "$ALWAYS_FRESH_INSTALL" = true ]; then
|
||||
echo "[INSTALL] ❗ ALERT /db and /config folders are cleared because the ALWAYS_FRESH_INSTALL is set to: $ALWAYS_FRESH_INSTALL❗"
|
||||
@@ -96,8 +95,11 @@ if [ -f "${INSTALL_DIR_OLD}/config/${OLD_APP_NAME}.conf" ]; then
|
||||
fi
|
||||
# 🔺 FOR BACKWARD COMPATIBILITY - REMOVE AFTER 12/12/2025
|
||||
|
||||
# Copy starter .db and .conf if they don't exist
|
||||
echo "[INSTALL] Copy starter ${DB_FILE} and ${CONF_FILE} if they don't exist"
|
||||
|
||||
# Copy starter app.db, app.conf, workflows.json if they don't exist
|
||||
cp -na "${INSTALL_DIR}/back/${CONF_FILE}" "${INSTALL_DIR}/config/${CONF_FILE}"
|
||||
cp -na "${INSTALL_DIR}/back/${WF_FILE}" "${INSTALL_DIR}/config/${WF_FILE}"
|
||||
cp -na "${INSTALL_DIR}/back/${DB_FILE}" "${FULL_FILEDB_PATH}"
|
||||
|
||||
# if custom variables not set we do not need to do anything
|
||||
@@ -143,6 +145,7 @@ fi
|
||||
# Create the execution_queue.log and app_front.log files if they don't exist
|
||||
touch "${INSTALL_DIR}"/log/{app.log,execution_queue.log,app_front.log,app.php_errors.log,stderr.log,stdout.log,db_is_locked.log}
|
||||
touch "${INSTALL_DIR}"/api/user_notifications.json
|
||||
|
||||
# Create plugins sub-directory if it doesn't exist in case a custom log folder is used
|
||||
mkdir -p "${INSTALL_DIR}"/log/plugins
|
||||
|
||||
|
||||
21
front/appEvents.php
Executable file
21
front/appEvents.php
Executable file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
require 'php/templates/header.php';
|
||||
require 'php/templates/notification.php';
|
||||
?>
|
||||
<!-- ----------------------------------------------------------------------- -->
|
||||
|
||||
|
||||
<!-- Page ------------------------------------------------------------------ -->
|
||||
<div class="content-wrapper">
|
||||
|
||||
<?php
|
||||
require 'appEventsCore.php';
|
||||
?>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<?php
|
||||
require 'php/templates/footer.php';
|
||||
?>
|
||||
@@ -1,12 +1,12 @@
|
||||
<section class="content">
|
||||
<div class="nav-tabs-custom app-event-content" style="margin-bottom: 0px;">
|
||||
<ul id="tabs-location" class="nav nav-tabs col-sm-2">
|
||||
<li class="left-nav"><a class="col-sm-12" href="#" id="" data-toggle="tab">Events</a></li>
|
||||
</ul>
|
||||
<div id="tabs-content-location" class="tab-content col-sm-10">
|
||||
<table class="table table-striped" id="appevents-table" data-my-dbtable="AppEvents"></table>
|
||||
</div>
|
||||
<div class="nav-tabs-custom app-event-content" style="margin-bottom: 0px;">
|
||||
<ul id="tabs-location" class="nav nav-tabs col-sm-2 hidden">
|
||||
<li class="left-nav"><a class="col-sm-12" href="#" id="" data-toggle="tab">Events</a></li>
|
||||
</ul>
|
||||
<div id="tabs-content-location" class="tab-content col-sm-12">
|
||||
<table class="table table-striped" id="appevents-table" data-my-dbtable="AppEvents"></table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -18,75 +18,110 @@ showSpinner()
|
||||
|
||||
$(document).ready(function() {
|
||||
|
||||
// Load JSON data from the provided URL
|
||||
$.getJSON('/php/server/query_json.php?file=table_appevents.json', function(data) {
|
||||
// Process the JSON data and generate UI dynamically
|
||||
processData(data)
|
||||
// Load JSON data from the provided URL
|
||||
$.getJSON('/php/server/query_json.php?file=table_appevents.json', function(data) {
|
||||
// Process the JSON data and generate UI dynamically
|
||||
processData(data)
|
||||
|
||||
// hide loading dialog
|
||||
hideSpinner()
|
||||
});
|
||||
// hide loading dialog
|
||||
hideSpinner()
|
||||
});
|
||||
});
|
||||
|
||||
function processData(data) {
|
||||
// Create an object to store unique ObjectType values as app event identifiers
|
||||
var appEventIdentifiers = {};
|
||||
// Create an object to store unique ObjectType values as app event identifiers
|
||||
var appEventIdentifiers = {};
|
||||
|
||||
// Array to accumulate data for DataTable
|
||||
var allData = [];
|
||||
// Array to accumulate data for DataTable
|
||||
var allData = [];
|
||||
|
||||
// Iterate through the data and generate tabs and content dynamically
|
||||
$.each(data.data, function(index, item) {
|
||||
|
||||
// Accumulate data for DataTable
|
||||
allData.push(item);
|
||||
|
||||
});
|
||||
|
||||
// Initialize DataTable for all app events
|
||||
// Iterate through the data and generate tabs and content dynamically
|
||||
$.each(data.data, function(index, item) {
|
||||
|
||||
$('#appevents-table').DataTable({
|
||||
data: allData,
|
||||
paging: true,
|
||||
lengthChange: true,
|
||||
lengthMenu: [[10, 25, 50, 100, 500, -1], [10, 25, 50, 100, 500, 'All']],
|
||||
searching: true,
|
||||
ordering: true,
|
||||
info: true,
|
||||
autoWidth: false,
|
||||
pageLength: 25, // Set the default paging to 25
|
||||
columns: [
|
||||
{ data: 'DateTimeCreated', title: getString('AppEvents_DateTimeCreated') },
|
||||
{ data: 'AppEventType', title: getString('AppEvents_Type') },
|
||||
{ data: 'ObjectType', title: getString('AppEvents_ObjectType') },
|
||||
{ data: 'ObjectPrimaryID', title: getString('AppEvents_ObjectPrimaryID') },
|
||||
{ data: 'ObjectSecondaryID', title: getString('AppEvents_ObjectSecondaryID') },
|
||||
{ data: 'ObjectStatus', title: getString('AppEvents_ObjectStatus') },
|
||||
{ data: 'Extra', title: getString('AppEvents_Extra') },
|
||||
{ data: 'ObjectPlugin', title: getString('AppEvents_Plugin') },
|
||||
// Add other columns as needed
|
||||
],
|
||||
// Add column-specific configurations if needed
|
||||
columnDefs: [
|
||||
{ className: 'text-center', targets: [3] },
|
||||
{ width: '80px', targets: [6] },
|
||||
// ... Add other columnDefs as needed
|
||||
// Full MAC
|
||||
{targets: [3, 4],
|
||||
'createdCell': function (td, cellData, rowData, row, col) {
|
||||
if (!emptyArr.includes(cellData)){
|
||||
$(td).html (createDeviceLink(cellData));
|
||||
} else {
|
||||
$(td).html ('');
|
||||
}
|
||||
} },
|
||||
]
|
||||
});
|
||||
// Accumulate data for DataTable
|
||||
allData.push(item);
|
||||
|
||||
});
|
||||
|
||||
console.log(allData);
|
||||
|
||||
|
||||
// Initialize DataTable for all app events
|
||||
|
||||
$('#appevents-table').DataTable({
|
||||
data: allData,
|
||||
paging: true,
|
||||
lengthChange: true,
|
||||
lengthMenu: [[10, 25, 50, 100, 500, -1], [10, 25, 50, 100, 500, 'All']],
|
||||
searching: true,
|
||||
ordering: true,
|
||||
info: true,
|
||||
autoWidth: false,
|
||||
pageLength: 25, // Set the default paging to 25
|
||||
columns: [
|
||||
{ data: 'DateTimeCreated', title: getString('AppEvents_DateTimeCreated') },
|
||||
{ data: 'AppEventProcessed', title: getString('AppEvents_AppEventProcessed') },
|
||||
{ data: 'AppEventType', title: getString('AppEvents_Type') },
|
||||
{ data: 'ObjectType', title: getString('AppEvents_ObjectType') },
|
||||
{ data: 'ObjectPrimaryID', title: getString('AppEvents_ObjectPrimaryID') },
|
||||
{ data: 'ObjectSecondaryID', title: getString('AppEvents_ObjectSecondaryID') },
|
||||
{ data: 'ObjectStatus', title: getString('AppEvents_ObjectStatus') },
|
||||
{ data: 'ObjectPlugin', title: getString('AppEvents_Plugin') },
|
||||
{ data: 'ObjectGUID', title: "GUID" },
|
||||
// Add other columns as needed
|
||||
],
|
||||
// Add column-specific configurations if needed
|
||||
columnDefs: [
|
||||
{ className: 'text-center', targets: [4] },
|
||||
{ width: '80px', targets: [7] },
|
||||
// ... Add other columnDefs as needed
|
||||
// Full MAC
|
||||
{targets: [4, 5],
|
||||
'createdCell': function (td, cellData, rowData, row, col) {
|
||||
if (!emptyArr.includes(cellData)){
|
||||
$(td).html (createDeviceLink(cellData));
|
||||
} else {
|
||||
$(td).html ('');
|
||||
}
|
||||
} },
|
||||
// Processed
|
||||
{targets: [1],
|
||||
'createdCell': function (td, cellData, rowData, row, col) {
|
||||
// console.log(cellData);
|
||||
$(td).html (cellData);
|
||||
}
|
||||
},
|
||||
// Datetime
|
||||
{targets: [0],
|
||||
'createdCell': function (td, cellData, rowData, row, col) {
|
||||
let timezone = $("#NAX_TZ").html(); // e.g., 'Europe/Berlin'
|
||||
let utcDate = new Date(cellData + ' UTC'); // Adding ' UTC' makes it interpreted as UTC time
|
||||
|
||||
// Format the date in the desired timezone
|
||||
let options = {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hour12: false, // Use 24-hour format
|
||||
timeZone: timezone // Use the specified timezone
|
||||
};
|
||||
|
||||
let localDate = new Intl.DateTimeFormat('en-GB', options).format(utcDate);
|
||||
|
||||
// Update the table cell
|
||||
$(td).html(localDate);
|
||||
}
|
||||
},
|
||||
]
|
||||
});
|
||||
|
||||
|
||||
// Activate the first tab
|
||||
$('#tabs-location li:first-child').addClass('active');
|
||||
$('#tabs-content-location .tab-pane:first-child').addClass('active');
|
||||
// Activate the first tab
|
||||
$('#tabs-location li:first-child').addClass('active');
|
||||
$('#tabs-content-location .tab-pane:first-child').addClass('active');
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
@@ -1840,6 +1840,47 @@ input[readonly] {
|
||||
height:50px;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
Workflows
|
||||
----------------------------------------------------------------------------- */
|
||||
|
||||
#workflowContainerWrap .panel-collapse
|
||||
{
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.workflows .btn-secondary{
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.workflows .condition-list button
|
||||
{
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
.workflows button
|
||||
{
|
||||
/* width:100%; */
|
||||
}
|
||||
|
||||
#workflowContainerWrap
|
||||
{
|
||||
display: contents;
|
||||
}
|
||||
|
||||
.workflow-card, .condition-list, .actions-list
|
||||
{
|
||||
display: grid;
|
||||
padding: 5px;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.condition
|
||||
{
|
||||
padding: 5px;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
Floating edit button
|
||||
----------------------------------------------------------------------------- */
|
||||
|
||||
@@ -123,7 +123,7 @@
|
||||
<!-- page script ----------------------------------------------------------- -->
|
||||
<script>
|
||||
var deviceStatus = 'all';
|
||||
var tableRows = getCache ("nax_parTableRows") == "" ? 10 : getCache ("nax_parTableRows") ;
|
||||
var tableRows = getCache ("nax_parTableRows") == "" ? 20 : getCache ("nax_parTableRows") ;
|
||||
var tableOrder = getCache ("nax_parTableOrder") == "" ? [[3,'desc'], [0,'asc']] : JSON.parse(getCache ("nax_parTableOrder")) ;
|
||||
|
||||
var tableColumnHide = [];
|
||||
@@ -737,7 +737,7 @@ function initializeDatatable (status) {
|
||||
},
|
||||
'paging' : true,
|
||||
'lengthChange' : true,
|
||||
'lengthMenu' : [[10, 25, 50, 100, 500, 100000], [10, 25, 50, 100, 500, getString('Device_Tablelenght_all')]],
|
||||
'lengthMenu' : [[10, 20, 25, 50, 100, 500, 100000], [10, 20, 25, 50, 100, 500, getString('Device_Tablelenght_all')]],
|
||||
'searching' : true,
|
||||
|
||||
'ordering' : true,
|
||||
|
||||
@@ -17,7 +17,13 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||
// Check if file parameter is provided
|
||||
if ($file) {
|
||||
// Define the folder where files are located
|
||||
$filePath = "/app/api/" . basename($file);
|
||||
if ($file == "workflows.json")
|
||||
{
|
||||
$filePath = "/app/config/" . basename($file);
|
||||
} else
|
||||
{
|
||||
$filePath = "/app/api/" . basename($file);
|
||||
}
|
||||
|
||||
// Check if the file exists
|
||||
if (file_exists($filePath)) {
|
||||
@@ -34,5 +40,38 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||
http_response_code(400);
|
||||
echo json_encode(["error" => "Missing 'file' parameter"]);
|
||||
}
|
||||
} elseif ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
// Read the input JSON data
|
||||
$inputData = file_get_contents("php://input");
|
||||
$decodedData = json_decode($inputData, true);
|
||||
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
http_response_code(400);
|
||||
echo json_encode(["error" => "Invalid JSON data"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Check if file parameter is provided and is workflows.json
|
||||
if (!isset($_GET['file']) || $_GET['file'] !== "workflows.json") {
|
||||
http_response_code(400);
|
||||
echo json_encode(["error" => "Invalid or missing file parameter"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$file = $_GET['file'];
|
||||
$filePath = "/app/config/" . basename($file);
|
||||
|
||||
// Save new workflows.json (replace existing content)
|
||||
if (file_put_contents($filePath, json_encode($decodedData, JSON_PRETTY_PRINT))) {
|
||||
http_response_code(200);
|
||||
echo json_encode(["success" => "Workflows replaced successfully"]);
|
||||
} else {
|
||||
http_response_code(500);
|
||||
echo json_encode(["error" => "Failed to update workflows.json"]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
http_response_code(405);
|
||||
echo json_encode(["error" => "Method Not Allowed"]);
|
||||
}
|
||||
?>
|
||||
|
||||
@@ -150,7 +150,8 @@
|
||||
let formattedDateTime = `${day}-${month}-${year} ${hour}:${minute}:${second}`;
|
||||
|
||||
if (document.getElementById) {
|
||||
document.getElementById("PIA_Servertime_place").innerHTML = '(' + formattedDateTime + ')';
|
||||
document.getElementById("NAX_Servertime_plc").innerHTML = '(' + formattedDateTime + ')';
|
||||
document.getElementById("NAX_TZ").innerHTML = timeZone;
|
||||
}
|
||||
|
||||
setTimeout(update_servertime, 1000); // Call recursively every second
|
||||
@@ -234,7 +235,13 @@
|
||||
<!-- Server Name -->
|
||||
<li>
|
||||
<div class="header-server-time small">
|
||||
<div><?php echo gethostname();?></div> <div><span id="PIA_Servertime_place"></span></div>
|
||||
<div>
|
||||
<?php echo gethostname();?>
|
||||
</div>
|
||||
<div>
|
||||
<span id="NAX_Servertime_plc"></span>
|
||||
<span id="NAX_TZ" class="hidden"></span>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
@@ -414,18 +421,22 @@
|
||||
</li>
|
||||
|
||||
<!-- Integrations menu item -->
|
||||
<li class=" treeview <?php if (in_array (basename($_SERVER['SCRIPT_NAME']), array('plugins.php', 'workflows.php' ) ) ){ echo 'active menu-open'; } ?>">
|
||||
<li class=" treeview <?php if (in_array (basename($_SERVER['SCRIPT_NAME']), array('plugins.php', 'workflows.php', 'appEvents.php' ) ) ){ echo 'active menu-open'; } ?>">
|
||||
<a href="#">
|
||||
<i class="fa fa-fw fa-plug"></i> <span><?= lang('Navigation_Integrations');?></span>
|
||||
<span class="pull-right-container">
|
||||
<i class="fa fa-angle-left pull-right"></i>
|
||||
</span>
|
||||
</a>
|
||||
<ul class="treeview-menu " style="display: <?php if (in_array (basename($_SERVER['SCRIPT_NAME']), array('plugins.php', 'workflows.php' ) ) ){ echo 'block'; } else {echo 'none';} ?>;">
|
||||
<ul class="treeview-menu " style="display: <?php if (in_array (basename($_SERVER['SCRIPT_NAME']), array('plugins.php', 'workflows.php', 'appEvents.php' ) ) ){ echo 'block'; } else {echo 'none';} ?>;">
|
||||
<li>
|
||||
<div class="info-icon-nav"> </div>
|
||||
<a href="workflows.php"><?= lang('Navigation_Workflows');?></a>
|
||||
</li>
|
||||
<li>
|
||||
<div class="info-icon-nav"> </div>
|
||||
<a href="appEvents.php"><?= lang('Navigation_AppEvents');?></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="plugins.php"><?= lang("Navigation_Plugins");?> </a>
|
||||
</li>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
"About_Design": "",
|
||||
"About_Exit": "",
|
||||
"About_Title": "",
|
||||
"AppEvents_AppEventProcessed": "",
|
||||
"AppEvents_DateTimeCreated": "",
|
||||
"AppEvents_Extra": "",
|
||||
"AppEvents_GUID": "",
|
||||
@@ -464,6 +465,7 @@
|
||||
"NETWORK_DEVICE_TYPES_description": "",
|
||||
"NETWORK_DEVICE_TYPES_name": "",
|
||||
"Navigation_About": "",
|
||||
"Navigation_AppEvents": "",
|
||||
"Navigation_Devices": "",
|
||||
"Navigation_Donations": "",
|
||||
"Navigation_Events": "",
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
"About_Design": "Dissenyat per:",
|
||||
"About_Exit": "Sortir",
|
||||
"About_Title": "Escàner de seguretat de xarxa i marc de notificacions",
|
||||
"AppEvents_AppEventProcessed": "",
|
||||
"AppEvents_DateTimeCreated": "Logged",
|
||||
"AppEvents_Extra": "Extra",
|
||||
"AppEvents_GUID": "GUID d'esdeveniments d'Aplicació",
|
||||
@@ -464,6 +465,7 @@
|
||||
"NETWORK_DEVICE_TYPES_description": "Quins tipus de dispositius es poden utilitzar com a dispositius de xarxa a la vista \"xarxa\". El tipus de dispositiu ha de coincidir exactament amb la configuració <code>Tipus</code> dels detalls de dispositiu. Afegir-ho al dispositiu fent servir el botó <code>+</code>. No elimini els tipus existents, només afegir-ne nous.",
|
||||
"NETWORK_DEVICE_TYPES_name": "Tipus de dispositiu de xarxa",
|
||||
"Navigation_About": "Sobre",
|
||||
"Navigation_AppEvents": "",
|
||||
"Navigation_Devices": "Dispositius",
|
||||
"Navigation_Donations": "Donacions",
|
||||
"Navigation_Events": "Esdeveniments",
|
||||
@@ -716,4 +718,4 @@
|
||||
"settings_update_item_warning": "Actualitza el valor sota. Sigues curós de seguir el format anterior. <b>No hi ha validació.</b>",
|
||||
"test_event_icon": "fa-vial-circle-check",
|
||||
"test_event_tooltip": "Deseu els canvis primer abans de comprovar la configuració."
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
"About_Design": "",
|
||||
"About_Exit": "",
|
||||
"About_Title": "",
|
||||
"AppEvents_AppEventProcessed": "",
|
||||
"AppEvents_DateTimeCreated": "",
|
||||
"AppEvents_Extra": "",
|
||||
"AppEvents_GUID": "",
|
||||
@@ -464,6 +465,7 @@
|
||||
"NETWORK_DEVICE_TYPES_description": "",
|
||||
"NETWORK_DEVICE_TYPES_name": "",
|
||||
"Navigation_About": "",
|
||||
"Navigation_AppEvents": "",
|
||||
"Navigation_Devices": "",
|
||||
"Navigation_Donations": "",
|
||||
"Navigation_Events": "",
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"About_Design": "Entworfen für:",
|
||||
"About_Exit": "Abmelden",
|
||||
"About_Title": "Netzwerksicherheitsscanner und Benachrichtigungsframework",
|
||||
"AppEvents_AppEventProcessed": "",
|
||||
"AppEvents_DateTimeCreated": "protokolliert",
|
||||
"AppEvents_Extra": "Extra",
|
||||
"AppEvents_GUID": "Anwendungsereignis-GUID",
|
||||
@@ -500,6 +501,7 @@
|
||||
"NTFY_display_name": "NTFY",
|
||||
"NTFY_icon": "<i class=\"fa fa-terminal\"></i>",
|
||||
"Navigation_About": "Über",
|
||||
"Navigation_AppEvents": "",
|
||||
"Navigation_Devices": "Geräte",
|
||||
"Navigation_Donations": "Spenden",
|
||||
"Navigation_Events": "Ereignisse",
|
||||
@@ -797,4 +799,4 @@
|
||||
"settings_update_item_warning": "",
|
||||
"test_event_icon": "",
|
||||
"test_event_tooltip": "Speichere die Änderungen, bevor Sie die Einstellungen testen."
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
"About_Design": "Designed for:",
|
||||
"About_Exit": "Sign out",
|
||||
"About_Title": "Network security scanner & notification framework",
|
||||
"AppEvents_AppEventProcessed": "Processed",
|
||||
"AppEvents_DateTimeCreated": "Logged",
|
||||
"AppEvents_Extra": "Extra",
|
||||
"AppEvents_GUID": "Application Event GUID",
|
||||
@@ -464,6 +465,7 @@
|
||||
"NETWORK_DEVICE_TYPES_description": "Which device types are allowed to be used as network devices in the Network view. The device type has to match exactly the <code>Type</code> setting on a specific device in Device details. Add it on the Device via the <code>+</code> button. Do not remove existing types, only add new ones.",
|
||||
"NETWORK_DEVICE_TYPES_name": "Network device types",
|
||||
"Navigation_About": "About",
|
||||
"Navigation_AppEvents": "App Events",
|
||||
"Navigation_Devices": "Devices",
|
||||
"Navigation_Donations": "Donations",
|
||||
"Navigation_Events": "Events",
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"About_Design": "Diseñado para:",
|
||||
"About_Exit": "Salir",
|
||||
"About_Title": "Escáner de seguridad de la red y marco de notificaciones",
|
||||
"AppEvents_AppEventProcessed": "",
|
||||
"AppEvents_DateTimeCreated": "Registrado",
|
||||
"AppEvents_Extra": "Extra",
|
||||
"AppEvents_GUID": "GUID del evento de aplicación",
|
||||
@@ -498,6 +499,7 @@
|
||||
"NTFY_display_name": "NTFY",
|
||||
"NTFY_icon": "<i class=\"fa fa-terminal\"></i>",
|
||||
"Navigation_About": "Acerca de",
|
||||
"Navigation_AppEvents": "",
|
||||
"Navigation_Devices": "Dispositivos",
|
||||
"Navigation_Donations": "Donaciones",
|
||||
"Navigation_Events": "Eventos",
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
"About_Design": "Conçu pour :",
|
||||
"About_Exit": "Se déconnecter",
|
||||
"About_Title": "Analyse de la sécurité du réseau et cadre de notification",
|
||||
"AppEvents_AppEventProcessed": "",
|
||||
"AppEvents_DateTimeCreated": "Connecté",
|
||||
"AppEvents_Extra": "Extra",
|
||||
"AppEvents_GUID": "GUID d’événements de l'application",
|
||||
@@ -464,6 +465,7 @@
|
||||
"NETWORK_DEVICE_TYPES_description": "Les types d'appareils autorisés à être utilisés comme appareils réseau dans la vue Réseau. Le type d'appareils doit être identique au paramètre <code>Type</code> d'un appareil dans le détail des appareils. Ajouter le sur l'appareil grâce au bouton <code>+</code>. Ne pas supprimer de valeurs, seulement en ajouter de nouvelles.",
|
||||
"NETWORK_DEVICE_TYPES_name": "Type d'appareil réseau",
|
||||
"Navigation_About": "À propos",
|
||||
"Navigation_AppEvents": "",
|
||||
"Navigation_Devices": "Appareils",
|
||||
"Navigation_Donations": "Dons",
|
||||
"Navigation_Events": "Évènements",
|
||||
@@ -716,4 +718,4 @@
|
||||
"settings_update_item_warning": "Mettre à jour la valeur ci-dessous. Veillez à bien suivre le même format qu'auparavant. <b>Il n'y a pas de pas de contrôle.</b>",
|
||||
"test_event_icon": "fa-vial-circle-check",
|
||||
"test_event_tooltip": "Enregistrer d'abord vos modifications avant de tester vôtre paramétrage."
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
"About_Design": "Progettato per:",
|
||||
"About_Exit": "Esci",
|
||||
"About_Title": "Scanner di sicurezza di rete e framework di notifica",
|
||||
"AppEvents_AppEventProcessed": "",
|
||||
"AppEvents_DateTimeCreated": "Loggato",
|
||||
"AppEvents_Extra": "Extra",
|
||||
"AppEvents_GUID": "GUID evento applicazione",
|
||||
@@ -464,6 +465,7 @@
|
||||
"NETWORK_DEVICE_TYPES_description": "Quali tipi di dispositivo possono essere utilizzati come dispositivi di rete nella vista Rete. Il tipo di dispositivo deve corrispondere esattamente all'impostazione <code>Tipo</code> su un dispositivo specifico nei Dettagli dispositivo. Aggiungilo sul Dispositivo tramite il pulsante <code>+</code>. Non rimuovere i tipi esistenti, aggiungine solo di nuovi.",
|
||||
"NETWORK_DEVICE_TYPES_name": "Tipi di dispositivi di rete",
|
||||
"Navigation_About": "Informazioni su",
|
||||
"Navigation_AppEvents": "",
|
||||
"Navigation_Devices": "Dispositivi",
|
||||
"Navigation_Donations": "Donazioni",
|
||||
"Navigation_Events": "Eventi",
|
||||
@@ -716,4 +718,4 @@
|
||||
"settings_update_item_warning": "Aggiorna il valore qui sotto. Fai attenzione a seguire il formato precedente. <b>La convalida non viene eseguita.</b>",
|
||||
"test_event_icon": "fa-vial-circle-check",
|
||||
"test_event_tooltip": "Salva le modifiche prima di provare le nuove impostazioni."
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
"About_Design": "Designet for:",
|
||||
"About_Exit": "Logg ut",
|
||||
"About_Title": "Nettverkssikkerhetsskanner og varslingsrammeverk",
|
||||
"AppEvents_AppEventProcessed": "",
|
||||
"AppEvents_DateTimeCreated": "Logget",
|
||||
"AppEvents_Extra": "Ekstra",
|
||||
"AppEvents_GUID": "Applikasjon Hendelse GUID",
|
||||
@@ -464,6 +465,7 @@
|
||||
"NETWORK_DEVICE_TYPES_description": "Hvilke enhetstyper som tillates å brukes som nettverksenheter i nettverksvisningen. Enhetstypen må samsvare med nøyaktig <code>Type</code> Innstillingen på en bestemt enhet i enhetsdetaljer. Ikke fjern eksisterende typer, legg bare til nye.",
|
||||
"NETWORK_DEVICE_TYPES_name": "Nettverksenhetstyper",
|
||||
"Navigation_About": "Om",
|
||||
"Navigation_AppEvents": "",
|
||||
"Navigation_Devices": "Enheter",
|
||||
"Navigation_Donations": "Donasjoner",
|
||||
"Navigation_Events": "Hendelser",
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
"About_Design": "Zaprojektowany dla:",
|
||||
"About_Exit": "Wyloguj",
|
||||
"About_Title": "Skaner bezpieczeństwa sieciowego i framwork powiadomień",
|
||||
"AppEvents_AppEventProcessed": "",
|
||||
"AppEvents_DateTimeCreated": "Zalogowany",
|
||||
"AppEvents_Extra": "Ekstra",
|
||||
"AppEvents_GUID": "Aplikacja GUID wydarzeń",
|
||||
@@ -464,6 +465,7 @@
|
||||
"NETWORK_DEVICE_TYPES_description": "Które typy urządzeń mają zezwolenie na bycie użytym jako urządzenia sieciowe w Widoku Sieci. Typ urządzenia musi dokładnie odpowiadać ustawieniu <code>Typ</code> na konkretnym urządzeniu w Szczegółach urządzenia. Nie usuwaj istniejących typów, tylko dodawaj nowe.",
|
||||
"NETWORK_DEVICE_TYPES_name": "Typy urządzeń sieciowych",
|
||||
"Navigation_About": "Informacje o",
|
||||
"Navigation_AppEvents": "",
|
||||
"Navigation_Devices": "Urządzenia",
|
||||
"Navigation_Donations": "Dotacje",
|
||||
"Navigation_Events": "Wydarzenia",
|
||||
@@ -716,4 +718,4 @@
|
||||
"settings_update_item_warning": "Zaktualizuj poniższą wartość. Zachowaj ostrożność i postępuj zgodnie z poprzednim formatem. <b>Walidacja nie jest wykonywana.</b>",
|
||||
"test_event_icon": "fa-vial-circle-check",
|
||||
"test_event_tooltip": "Zapisz zmiany zanim będziesz testować swoje ustawienia."
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
"About_Design": "Desenvolvido por:",
|
||||
"About_Exit": "Sair",
|
||||
"About_Title": "Analisador de segurança de rede & framework de notificação",
|
||||
"AppEvents_AppEventProcessed": "",
|
||||
"AppEvents_DateTimeCreated": "Registrado em",
|
||||
"AppEvents_Extra": "Adicional",
|
||||
"AppEvents_GUID": "Evento de aplicação GUID",
|
||||
@@ -464,6 +465,7 @@
|
||||
"NETWORK_DEVICE_TYPES_description": "",
|
||||
"NETWORK_DEVICE_TYPES_name": "",
|
||||
"Navigation_About": "",
|
||||
"Navigation_AppEvents": "",
|
||||
"Navigation_Devices": "",
|
||||
"Navigation_Donations": "",
|
||||
"Navigation_Events": "",
|
||||
@@ -716,4 +718,4 @@
|
||||
"settings_update_item_warning": "",
|
||||
"test_event_icon": "",
|
||||
"test_event_tooltip": ""
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
"About_Design": "Разработан:",
|
||||
"About_Exit": "Зарегистрироваться",
|
||||
"About_Title": "Сетевой сканер и система уведомлений",
|
||||
"AppEvents_AppEventProcessed": "",
|
||||
"AppEvents_DateTimeCreated": "Журнал",
|
||||
"AppEvents_Extra": "Дополнительно",
|
||||
"AppEvents_GUID": "GUID события приложения",
|
||||
@@ -464,6 +465,7 @@
|
||||
"NETWORK_DEVICE_TYPES_description": "Какие типы устройств разрешено использовать в качестве сетевых устройств в представлении Сеть. Тип устройства должен точно соответствовать настройке <code>Type</code> для конкретного устройства в сведениях об устройстве. Добавьте его на устройство с помощью кнопки <code>+</code>. Не удаляйте существующие типы, а только добавляйте новые.",
|
||||
"NETWORK_DEVICE_TYPES_name": "Типы сетевых устройств",
|
||||
"Navigation_About": "О NetAlertX",
|
||||
"Navigation_AppEvents": "",
|
||||
"Navigation_Devices": "Устройства",
|
||||
"Navigation_Donations": "Пожертвования",
|
||||
"Navigation_Events": "События",
|
||||
@@ -716,4 +718,4 @@
|
||||
"settings_update_item_warning": "Обновить значение ниже. Будьте осторожны, следуя предыдущему формату. <b>Проверка не выполняется.</b>",
|
||||
"test_event_icon": "fa-vial-circle-check",
|
||||
"test_event_tooltip": "Сначала сохраните изменения, прежде чем проверять настройки."
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
"About_Design": "",
|
||||
"About_Exit": "Oturum kapat",
|
||||
"About_Title": "",
|
||||
"AppEvents_AppEventProcessed": "",
|
||||
"AppEvents_DateTimeCreated": "",
|
||||
"AppEvents_Extra": "Ekstra",
|
||||
"AppEvents_GUID": "",
|
||||
@@ -464,6 +465,7 @@
|
||||
"NETWORK_DEVICE_TYPES_description": "",
|
||||
"NETWORK_DEVICE_TYPES_name": "",
|
||||
"Navigation_About": "Hakkında",
|
||||
"Navigation_AppEvents": "",
|
||||
"Navigation_Devices": "Cihazlar",
|
||||
"Navigation_Donations": "",
|
||||
"Navigation_Events": "",
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
"About_Design": "Призначений для:",
|
||||
"About_Exit": "Вийти",
|
||||
"About_Title": "Сканер безпеки мережі та структура сповіщень",
|
||||
"AppEvents_AppEventProcessed": "",
|
||||
"AppEvents_DateTimeCreated": "Зареєстровано",
|
||||
"AppEvents_Extra": "Екстра",
|
||||
"AppEvents_GUID": "GUID події програми",
|
||||
@@ -464,6 +465,7 @@
|
||||
"NETWORK_DEVICE_TYPES_description": "Які типи пристроїв дозволено використовувати як мережеві пристрої в поданні мережі. Тип пристрою має точно відповідати налаштуванню <code>Тип</code> на певному пристрої в Деталях пристрою. Додайте його на пристрій за допомогою кнопки <code>+</code>. Не видаляйте існуючі типи, лише додайте нові.",
|
||||
"NETWORK_DEVICE_TYPES_name": "Типи мережевих пристроїв",
|
||||
"Navigation_About": "про",
|
||||
"Navigation_AppEvents": "",
|
||||
"Navigation_Devices": "Пристрої",
|
||||
"Navigation_Donations": "Пожертви",
|
||||
"Navigation_Events": "Події",
|
||||
@@ -716,4 +718,4 @@
|
||||
"settings_update_item_warning": "Оновіть значення нижче. Слідкуйте за попереднім форматом. <b>Перевірка не виконана.</b>",
|
||||
"test_event_icon": "fa-vial-circle- check",
|
||||
"test_event_tooltip": "Перш ніж перевіряти налаштування, збережіть зміни."
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
"About_Design": "设计用于:",
|
||||
"About_Exit": "登出",
|
||||
"About_Title": "网络安全扫描器和通知框架",
|
||||
"AppEvents_AppEventProcessed": "",
|
||||
"AppEvents_DateTimeCreated": "已记录",
|
||||
"AppEvents_Extra": "额外的",
|
||||
"AppEvents_GUID": "应用程序事件 GUID",
|
||||
@@ -464,6 +465,7 @@
|
||||
"NETWORK_DEVICE_TYPES_description": "哪些设备类型允许在网络视图中用作网络设备。设备类型必须与设备详细信息中特定设备上的 <code>Type</code> 设置完全匹配。请勿删除现有类型,仅添加新类型。",
|
||||
"NETWORK_DEVICE_TYPES_name": "网络设备类型",
|
||||
"Navigation_About": "关于",
|
||||
"Navigation_AppEvents": "",
|
||||
"Navigation_Devices": "设备",
|
||||
"Navigation_Donations": "捐款",
|
||||
"Navigation_Events": "事件",
|
||||
|
||||
@@ -293,9 +293,6 @@ def create_sensor(mqtt_client, deviceId, deviceName, sensorType, sensorName, ico
|
||||
# check previous configs
|
||||
sensorConfig = sensor_config(deviceId, deviceName, sensorType, sensorName, icon, mac)
|
||||
|
||||
mylog('verbose', [f"[{pluginName}] Publishing sensor number {len(mqtt_sensors)}"])
|
||||
|
||||
|
||||
# send if new
|
||||
if sensorConfig.isNew:
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ from const import pluginsPath, fullDbPath, logPath
|
||||
from helper import timeNowTZ, get_setting_value
|
||||
from notification import write_notification
|
||||
from database import DB
|
||||
from device import Device_obj
|
||||
from models.device_instance import DeviceInstance
|
||||
import conf
|
||||
from pytz import timezone
|
||||
|
||||
@@ -53,8 +53,8 @@ def main():
|
||||
# Initialize the Plugin obj output file
|
||||
plugin_objects = Plugin_Objects(RESULT_FILE)
|
||||
|
||||
# Create a Device_obj instance
|
||||
device_handler = Device_obj(db)
|
||||
# Create a DeviceInstance instance
|
||||
device_handler = DeviceInstance(db)
|
||||
|
||||
# Retrieve devices
|
||||
unknown_devices = device_handler.getUnknown()
|
||||
|
||||
@@ -139,6 +139,7 @@ def cleanup_database (dbPath, DAYS_TO_KEEP_EVENTS, HRS_TO_KEEP_NEWDEV, HRS_TO_KE
|
||||
);"""
|
||||
|
||||
cursor.execute(delete_query)
|
||||
conn.commit()
|
||||
|
||||
|
||||
# -----------------------------------------------------
|
||||
|
||||
@@ -23,7 +23,7 @@ from logger import mylog, Logger, append_line_to_file
|
||||
from helper import timeNowTZ, get_setting_value
|
||||
from const import logPath, applicationPath, fullDbPath
|
||||
from database import DB
|
||||
from device import Device_obj
|
||||
from models.device_instance import DeviceInstance
|
||||
import conf
|
||||
from pytz import timezone
|
||||
|
||||
@@ -57,8 +57,8 @@ def main():
|
||||
# Initialize the Plugin obj output file
|
||||
plugin_objects = Plugin_Objects(RESULT_FILE)
|
||||
|
||||
# Create a Device_obj instance
|
||||
device_handler = Device_obj(db)
|
||||
# Create a DeviceInstance instance
|
||||
device_handler = DeviceInstance(db)
|
||||
|
||||
# Retrieve devices
|
||||
all_devices = device_handler.getAll()
|
||||
|
||||
@@ -18,7 +18,7 @@ from const import pluginsPath, fullDbPath, logPath
|
||||
from helper import timeNowTZ, get_setting_value
|
||||
from notification import write_notification
|
||||
from database import DB
|
||||
from device import Device_obj
|
||||
from models.device_instance import DeviceInstance
|
||||
import conf
|
||||
from pytz import timezone
|
||||
|
||||
@@ -53,8 +53,8 @@ def main():
|
||||
# Initialize the Plugin obj output file
|
||||
plugin_objects = Plugin_Objects(RESULT_FILE)
|
||||
|
||||
# Create a Device_obj instance
|
||||
device_handler = Device_obj(db)
|
||||
# Create a DeviceInstance instance
|
||||
device_handler = DeviceInstance(db)
|
||||
|
||||
# Retrieve devices
|
||||
unknown_devices = device_handler.getUnknown()
|
||||
|
||||
@@ -24,7 +24,7 @@ from logger import mylog, Logger, append_line_to_file
|
||||
from helper import timeNowTZ, get_setting_value, extract_between_strings, extract_ip_addresses, extract_mac_addresses
|
||||
from const import logPath, applicationPath, fullDbPath
|
||||
from database import DB
|
||||
from device import Device_obj
|
||||
from models.device_instance import DeviceInstance
|
||||
import conf
|
||||
from pytz import timezone
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ from logger import mylog, Logger, append_line_to_file
|
||||
from helper import timeNowTZ, get_setting_value
|
||||
from const import logPath, applicationPath, fullDbPath
|
||||
from database import DB
|
||||
from device import Device_obj
|
||||
from models.device_instance import DeviceInstance
|
||||
import conf
|
||||
from pytz import timezone
|
||||
|
||||
@@ -55,8 +55,8 @@ def main():
|
||||
# Initialize the Plugin obj output file
|
||||
plugin_objects = Plugin_Objects(RESULT_FILE)
|
||||
|
||||
# Create a Device_obj instance
|
||||
device_handler = Device_obj(db)
|
||||
# Create a DeviceInstance instance
|
||||
device_handler = DeviceInstance(db)
|
||||
|
||||
# Retrieve devices
|
||||
unknown_devices = device_handler.getUnknown()
|
||||
|
||||
@@ -251,12 +251,12 @@ def main():
|
||||
|
||||
mylog("verbose", [f"[{pluginName}] starting execution"])
|
||||
from database import DB
|
||||
from device import Device_obj
|
||||
from models.device_instance import DeviceInstance
|
||||
|
||||
db = DB() # instance of class DB
|
||||
db.open()
|
||||
# Create a Device_obj instance
|
||||
device_handler = Device_obj(db)
|
||||
# Create a DeviceInstance instance
|
||||
device_handler = DeviceInstance(db)
|
||||
# Retrieve configuration settings
|
||||
# these should be self-explanatory
|
||||
omada_sites = []
|
||||
|
||||
@@ -19,7 +19,7 @@ from const import pluginsPath, fullDbPath, logPath
|
||||
from helper import timeNowTZ, get_setting_value
|
||||
from notification import write_notification
|
||||
from database import DB
|
||||
from device import Device_obj
|
||||
from models.device_instance import DeviceInstance
|
||||
import conf
|
||||
|
||||
# Make sure the TIMEZONE for logging is correct
|
||||
@@ -54,8 +54,8 @@ def main():
|
||||
db = DB() # instance of class DB
|
||||
db.open()
|
||||
|
||||
# Create a Device_obj instance
|
||||
device_handler = Device_obj(db)
|
||||
# Create a DeviceInstance instance
|
||||
device_handler = DeviceInstance(db)
|
||||
|
||||
# Retrieve devices
|
||||
if 'offline' in devices_to_wake:
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<div class="content-wrapper">
|
||||
|
||||
<?php
|
||||
require 'appEventsCore.php';
|
||||
require 'workflowsCore.php';
|
||||
?>
|
||||
|
||||
|
||||
|
||||
385
front/workflowsCore.php
Executable file
385
front/workflowsCore.php
Executable file
@@ -0,0 +1,385 @@
|
||||
<?php
|
||||
//------------------------------------------------------------------------------
|
||||
// check if authenticated
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/templates/security.php';
|
||||
?>
|
||||
|
||||
|
||||
<section class="content workflows">
|
||||
<div id="workflowContainerWrap" class="bg-grey-dark color-palette col-sm-12 box-default box-info ">
|
||||
<div id="workflowContainer"></div>
|
||||
|
||||
</div>
|
||||
<div id="buttons" class="buttons col-sm-12">
|
||||
<div class="add-workflow col-sm-6">
|
||||
<button type="button" class="btn btn-primary btn-default pa-btn bg-green" id="save" onclick="addWorkflow()">
|
||||
<?= lang('Gen_Add');?>
|
||||
</button>
|
||||
</div>
|
||||
<div class="save-workflows col-sm-6">
|
||||
<button type="button" class="btn btn-primary btn-default pa-btn bg-green" id="save" onclick="saveWorkflows()">
|
||||
<?= lang('DevDetail_button_Save');?>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
let workflows = [];
|
||||
|
||||
let fieldOptions = [
|
||||
"devMac", "devName", "devOwner", "devType", "devVendor", "devFavorite",
|
||||
"devGroup", "devComments", "devFirstConnection", "devLastConnection",
|
||||
"devLastIP", "devStaticIP", "devScan", "devLogEvents", "devAlertEvents",
|
||||
"devAlertDown", "devSkipRepeated", "devLastNotification", "devPresentLastScan",
|
||||
"devIsNew", "devLocation", "devIsArchived", "devParentMAC", "devParentPort",
|
||||
"devIcon", "devGUID", "devSite", "devSSID", "devSyncHubNode", "devSourcePlugin"
|
||||
];
|
||||
|
||||
let triggerTypes = [
|
||||
"Devices", "Plugins_Objects"
|
||||
];
|
||||
|
||||
let operatorTypes = [
|
||||
"equals", "contains" , "regex"
|
||||
];
|
||||
|
||||
let actionTypes = [
|
||||
"update_field", "run_plugin"
|
||||
];
|
||||
|
||||
// --------------------------------------
|
||||
// Retrieve and process the data
|
||||
function getData() {
|
||||
showSpinner();
|
||||
|
||||
getSetting()
|
||||
|
||||
$.get('php/server/query_json.php?file=workflows.json', function (res) {
|
||||
workflows = res;
|
||||
console.log(workflows);
|
||||
renderWorkflows();
|
||||
hideSpinner();
|
||||
});
|
||||
}
|
||||
|
||||
// --------------------------------------
|
||||
// Render all workflows
|
||||
function renderWorkflows() {
|
||||
let $container = $("#workflowContainer");
|
||||
$container.empty(); // Clear previous UI
|
||||
|
||||
$.each(workflows, function (index, wf) {
|
||||
let $wfElement = generateWorkflowUI(wf, index);
|
||||
$container.append($wfElement);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// --------------------------------------
|
||||
// Generate UI for a single workflow
|
||||
function generateWorkflowUI(wf, index) {
|
||||
|
||||
let $wfContainer = $("<div>", {
|
||||
class: "workflow-card box box-solid box-primary panel panel-default",
|
||||
id: `wf-${index}-container`
|
||||
});
|
||||
|
||||
// Workflow Name
|
||||
let $wfLinkWrap = $("<div>",
|
||||
{
|
||||
class: " ",
|
||||
id: `wf-${index}-header`
|
||||
}
|
||||
)
|
||||
|
||||
let $wfHeaderLink = $("<a>",
|
||||
{
|
||||
"class": "",
|
||||
"data-toggle": "collapse",
|
||||
"data-parent": "#workflowContainer",
|
||||
"aria-expanded": false,
|
||||
"href" : `#wf-${index}-collapsible-panel`
|
||||
}
|
||||
)
|
||||
|
||||
let $wfHeaderHeading = $("<h4>",
|
||||
{
|
||||
class: "panel-title"
|
||||
}
|
||||
).text(wf.name)
|
||||
|
||||
$wfContainer.append($wfHeaderLink.append($wfLinkWrap.append($wfHeaderHeading)));
|
||||
|
||||
// Collapsible panel start
|
||||
let $wfCollapsiblePanel = $("<div>", {
|
||||
class: "panel-collapse collapse ",
|
||||
id: `wf-${index}-collapsible-panel`
|
||||
});
|
||||
|
||||
let $wfNameInput = createEditableInput("Workflow name", wf.name, `wf-name-${index}`, "workflow-name-input", function(newValue) {
|
||||
console.log(`Saved new value: ${newValue}`);
|
||||
wf.name = newValue; // Update the workflow object with the new name
|
||||
});
|
||||
|
||||
$wfCollapsiblePanel.append($wfNameInput)
|
||||
|
||||
// Trigger Section with dropdowns
|
||||
let $triggerSection = $("<div>",
|
||||
{
|
||||
class: "condition-list box box-secondary"
|
||||
}
|
||||
).append("<strong>Trigger:</strong> ");
|
||||
|
||||
let $triggerTypeDropdown = createEditableDropdown("Trigger Type", triggerTypes, wf.trigger.object_type, `trigger-${index}-type`, function(newValue) {
|
||||
wf.trigger.object_type = newValue; // Update trigger's object_type
|
||||
});
|
||||
|
||||
let $eventTypeDropdown = createEditableDropdown("Event Type", ["update", "create", "delete"], wf.trigger.event_type, `event-${index}-type`, function(newValue) {
|
||||
wf.trigger.event_type = newValue; // Update trigger's event_type
|
||||
});
|
||||
|
||||
$triggerSection.append($triggerTypeDropdown);
|
||||
$triggerSection.append($eventTypeDropdown);
|
||||
$wfCollapsiblePanel.append($triggerSection);
|
||||
|
||||
// Conditions
|
||||
let $conditionsContainer = $("<div>").append("<strong>Conditions:</strong>");
|
||||
$conditionsContainer.append(renderConditions(wf.conditions));
|
||||
|
||||
$wfCollapsiblePanel.append($conditionsContainer);
|
||||
|
||||
|
||||
// Actions with action.field as dropdown
|
||||
let $actionsContainer = $("<div>",
|
||||
{
|
||||
class: "actions-list box box-secondary"
|
||||
}
|
||||
).append("<strong>Actions:</strong>");
|
||||
|
||||
$.each(wf.actions, function (_, action) {
|
||||
let $actionEl = $("<div>");
|
||||
|
||||
// Dropdown for action.field
|
||||
let $fieldDropdown = createEditableDropdown("Field", fieldOptions, action.field, `action-${index}-field`, function(newValue) {
|
||||
action.field = newValue; // Update action.field when a new value is selected
|
||||
});
|
||||
|
||||
|
||||
// Dropdown for action.type
|
||||
let $actionDropdown= createEditableDropdown("Action", actionTypes, action.field, `action-${index}-type`, function(newValue) {
|
||||
action.field = newValue; // Update action.field when a new value is selected
|
||||
});
|
||||
|
||||
|
||||
// Action Value Input (Editable)
|
||||
let $actionValueInput = createEditableInput("Value", action.value, `action-${index}-value`, "action-value-input", function(newValue) {
|
||||
action.value = newValue; // Update action.value when saved
|
||||
});
|
||||
|
||||
$actionEl.append($actionDropdown);
|
||||
$actionEl.append($fieldDropdown);
|
||||
$actionEl.append($actionValueInput);
|
||||
|
||||
$actionsContainer.append($actionEl);
|
||||
});
|
||||
|
||||
// add conditions group button
|
||||
let $actionAddButton = $("<button>", {
|
||||
class : "btn btn-secondary "
|
||||
}).text("Add Action")
|
||||
|
||||
$actionsContainer.append($actionAddButton)
|
||||
$wfCollapsiblePanel.append($actionsContainer);
|
||||
|
||||
$wfContainer.append($wfCollapsiblePanel)
|
||||
|
||||
return $wfContainer;
|
||||
}
|
||||
|
||||
|
||||
// --------------------------------------
|
||||
// Render conditions recursively
|
||||
function renderConditions(conditions) {
|
||||
let $conditionList = $("<div>", {
|
||||
class: "condition-list panel "
|
||||
});
|
||||
|
||||
$.each(conditions, function (index, condition) {
|
||||
if (condition.logic) {
|
||||
let $nestedCondition = $("<div>",
|
||||
{
|
||||
class : "condition box box-secondary"
|
||||
}
|
||||
);
|
||||
|
||||
let $logicDropdown = createEditableDropdown("Logic Rules", ["AND", "OR"], condition.logic, `logic-${condition.field}`, function(newValue) {
|
||||
condition.logic = newValue; // Update condition logic when a new value is selected
|
||||
});
|
||||
|
||||
$nestedCondition.append($logicDropdown);
|
||||
|
||||
$conditionListNested = renderConditions(condition.conditions)
|
||||
|
||||
|
||||
// add conditions group button
|
||||
let $conditionAddButton = $("<button>", {
|
||||
class : "btn btn-secondary "
|
||||
}).text("Add Condition")
|
||||
|
||||
$conditionListNested.append($conditionAddButton);
|
||||
$nestedCondition.append($conditionListNested); // Recursive call for nested conditions
|
||||
|
||||
$conditionList.append($nestedCondition);
|
||||
|
||||
} else {
|
||||
let $conditionItem = $("<div>",
|
||||
{
|
||||
class: "panel"
|
||||
});
|
||||
|
||||
// Create dropdown for condition field
|
||||
let $fieldDropdown = createEditableDropdown("Field", fieldOptions, condition.field, `condition-${index}-field-${condition.field}`, function(newValue) {
|
||||
condition.field = newValue; // Update condition field when a new value is selected
|
||||
});
|
||||
|
||||
// Create dropdown for operator
|
||||
let $operatorDropdown = createEditableDropdown("Operator", operatorTypes, condition.operator, `condition-${index}operator-${condition.field}`, function(newValue) {
|
||||
condition.operator = newValue; // Update operator when a new value is selected
|
||||
});
|
||||
|
||||
// Editable input for condition value
|
||||
let $editableInput = createEditableInput("Condition Value", condition.value, `condition-${index}-value-${condition.field}`, "condition-value-input", function(newValue) {
|
||||
condition.value = newValue; // Update condition value when saved
|
||||
});
|
||||
|
||||
$conditionItem.append($fieldDropdown); // Append field dropdown
|
||||
$conditionItem.append($operatorDropdown); // Append operator dropdown
|
||||
$conditionItem.append($editableInput); // Append editable input for condition value
|
||||
$conditionList.append($conditionItem);
|
||||
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
// add conditions group button
|
||||
let $conditionsGroupAddButton = $("<button>", {
|
||||
class : "btn btn-secondary"
|
||||
}).text("Add Condition Group")
|
||||
|
||||
$conditionList.append($conditionsGroupAddButton);
|
||||
|
||||
|
||||
return $conditionList;
|
||||
}
|
||||
|
||||
|
||||
// --------------------------------------
|
||||
// Render SELECT Dropdown with Predefined Values
|
||||
function createEditableDropdown(labelText, options, selectedValue, id, onSave) {
|
||||
|
||||
let $wrapper = $("<div>", {
|
||||
class: "form-group col-xs-12"
|
||||
});
|
||||
|
||||
let $label = $("<label>", {
|
||||
for: id,
|
||||
class: "col-sm-4 col-xs-12 control-label "
|
||||
}).text(labelText);
|
||||
|
||||
// Create select wrapper
|
||||
let $selectWrapper = $("<div>", {
|
||||
class: "col-sm-8 col-xs-12"
|
||||
});
|
||||
|
||||
// Create select element
|
||||
let $select = $("<select>", {
|
||||
id: id,
|
||||
class: "form-control col-sm-8 col-xs-12"
|
||||
});
|
||||
|
||||
// Add options to the select dropdown
|
||||
$.each(options, function (_, option) {
|
||||
let $option = $("<option>", { value: option }).text(option);
|
||||
if (option === selectedValue) {
|
||||
$option.attr("selected", "selected"); // Set the default selection
|
||||
}
|
||||
$select.append($option);
|
||||
});
|
||||
|
||||
// Trigger onSave when the selection changes
|
||||
$select.on("change", function() {
|
||||
let newValue = $select.val();
|
||||
console.log(`Selected new value: ${newValue}`);
|
||||
if (onSave && typeof onSave === "function") {
|
||||
onSave(newValue); // Call onSave callback with the new value
|
||||
}
|
||||
});
|
||||
|
||||
$wrapper.append($label);
|
||||
$wrapper.append($selectWrapper.append($select));
|
||||
return $wrapper;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// --------------------------------------
|
||||
// Render INPUT HTML element
|
||||
function createEditableInput(labelText, value, id, className = "", onSave = null) {
|
||||
|
||||
// prepare wrapper
|
||||
$wrapper = $("<div>", {
|
||||
class: "form-group col-xs-12"
|
||||
});
|
||||
|
||||
let $label = $("<label>", {
|
||||
for: id,
|
||||
class: "col-sm-4 col-xs-12 control-label "
|
||||
}).text(labelText);
|
||||
|
||||
// Create input wrapper
|
||||
let $inputWrapper = $("<div>", {
|
||||
class: "col-sm-8 col-xs-12"
|
||||
});
|
||||
|
||||
|
||||
let $input = $("<input>", {
|
||||
type: "text",
|
||||
id: id,
|
||||
value: value,
|
||||
class: className + " col-sm-8 col-xs-12 form-control "
|
||||
});
|
||||
|
||||
// Optional: Add a change event listener to update the workflow name
|
||||
$input.on("change", function () {
|
||||
let newValue = $input.val();
|
||||
console.log(`Value changed to: ${newValue}`);
|
||||
});
|
||||
|
||||
// Trigger onSave when the user presses Enter or the input loses focus
|
||||
$input.on("blur keyup", function (e) {
|
||||
if (e.type === "blur" || e.key === "Enter") {
|
||||
if (onSave && typeof onSave === "function") {
|
||||
onSave($input.val()); // Call the onSave callback with the new value
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$wrapper.append($label)
|
||||
$wrapper.append($inputWrapper.append($input))
|
||||
return $wrapper;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// --------------------------------------
|
||||
// Initialize
|
||||
$(document).ready(function () {
|
||||
getData();
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
@@ -28,13 +28,14 @@ from logger import mylog
|
||||
from helper import filePermissions, timeNowTZ, get_setting_value
|
||||
from app_state import updateState
|
||||
from api import update_api
|
||||
from networkscan import process_scan
|
||||
from scan.session_events import process_scan
|
||||
from initialise import importConfigs
|
||||
from database import DB
|
||||
from reporting import get_notifications
|
||||
from notification import Notification_obj
|
||||
from plugin import run_plugin_scripts, check_and_run_user_event
|
||||
from device import update_devices_names
|
||||
from scan.device_handling import update_devices_names
|
||||
from workflows.manager import WorkflowManager
|
||||
|
||||
#===============================================================================
|
||||
#===============================================================================
|
||||
@@ -79,6 +80,9 @@ def main ():
|
||||
# Upgrade DB if needed
|
||||
db.upgradeDB()
|
||||
|
||||
# Initialize the WorkflowManager
|
||||
workflow_manager = WorkflowManager(db)
|
||||
|
||||
#===============================================================================
|
||||
# This is the main loop of NetAlertX
|
||||
#===============================================================================
|
||||
@@ -180,15 +184,37 @@ def main ():
|
||||
|
||||
# Commit SQL
|
||||
db.commitDB()
|
||||
|
||||
# Footer
|
||||
|
||||
|
||||
mylog('verbose', ['[MAIN] Process: Idle'])
|
||||
else:
|
||||
# do something
|
||||
# mylog('verbose', ['[MAIN] Waiting to start next loop'])
|
||||
updateState("Process: Idle")
|
||||
|
||||
updateState("Process: Idle")
|
||||
|
||||
# WORKFLOWS handling
|
||||
# ----------------------------------------
|
||||
# Fetch new unprocessed events
|
||||
new_events = workflow_manager.get_new_app_events()
|
||||
|
||||
# Process each new event and check triggers
|
||||
if new_events:
|
||||
updateState("Workflows: Start")
|
||||
update_api_flag = False
|
||||
for event in new_events:
|
||||
mylog('debug', [f'[MAIN] Processing WORKFLOW app event with GUID {event["GUID"]}'])
|
||||
|
||||
# proceed to process events
|
||||
workflow_manager.process_event(event)
|
||||
|
||||
if workflow_manager.update_api:
|
||||
# Update API endpoints if needed
|
||||
update_api_flag = True
|
||||
|
||||
if update_api_flag:
|
||||
update_api(db, all_plugins, True)
|
||||
|
||||
updateState("Workflows: End")
|
||||
|
||||
|
||||
#loop
|
||||
time.sleep(5) # wait for N seconds
|
||||
|
||||
@@ -71,7 +71,7 @@ sql_devices_all = """
|
||||
FROM Devices
|
||||
"""
|
||||
|
||||
sql_appevents = """select * from AppEvents"""
|
||||
sql_appevents = """select * from AppEvents order by DateTimeCreated desc"""
|
||||
# The below query calculates counts of devices in various categories:
|
||||
# (connected/online, offline, down, new, archived),
|
||||
# as well as a combined count for devices that match any status listed in the UI_MY_DEVICES setting
|
||||
|
||||
@@ -3,6 +3,7 @@ from Crypto.Util.Padding import pad, unpad
|
||||
import base64
|
||||
import os
|
||||
import hashlib
|
||||
import uuid
|
||||
|
||||
|
||||
# SIMPLE CRYPT - requeres C compiler -------------------------------------------------------------------------
|
||||
@@ -56,4 +57,10 @@ def get_random_bytes(length):
|
||||
# Format hexadecimal string with hyphens
|
||||
formatted_hex = '-'.join(hex_string[i:i+2] for i in range(0, len(hex_string), 2))
|
||||
|
||||
return formatted_hex
|
||||
return formatted_hex
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
def generate_deterministic_guid(plugin, primary_id, secondary_id):
|
||||
"""Generates a deterministic GUID based on plugin, primary ID, and secondary ID."""
|
||||
data = f"{plugin}-{primary_id}-{secondary_id}".encode("utf-8")
|
||||
return str(uuid.UUID(hashlib.md5(data).hexdigest()))
|
||||
@@ -9,7 +9,7 @@ from const import fullDbPath, sql_devices_stats, sql_devices_all, sql_generateGu
|
||||
|
||||
from logger import mylog
|
||||
from helper import json_obj, initOrSetParam, row_to_json, timeNowTZ
|
||||
from appevent import AppEvent_obj
|
||||
from workflows.app_events import AppEvent_obj
|
||||
|
||||
class DB():
|
||||
"""
|
||||
@@ -543,6 +543,7 @@ class DB():
|
||||
sql_Plugins_Objects = """ CREATE TABLE IF NOT EXISTS Plugins_Objects(
|
||||
"Index" INTEGER,
|
||||
Plugin TEXT NOT NULL,
|
||||
ObjectGUID TEXT,
|
||||
Object_PrimaryID TEXT NOT NULL,
|
||||
Object_SecondaryID TEXT NOT NULL,
|
||||
DateTimeCreated TEXT NOT NULL,
|
||||
@@ -589,6 +590,18 @@ class DB():
|
||||
self.sql.execute('ALTER TABLE "Plugins_Objects" ADD COLUMN "HelpVal2" TEXT')
|
||||
self.sql.execute('ALTER TABLE "Plugins_Objects" ADD COLUMN "HelpVal3" TEXT')
|
||||
self.sql.execute('ALTER TABLE "Plugins_Objects" ADD COLUMN "HelpVal4" TEXT')
|
||||
|
||||
# plug_ObjectGUID_missing column
|
||||
plug_ObjectGUID_missing = self.sql.execute ("""
|
||||
SELECT COUNT(*) AS CNTREC FROM pragma_table_info('Plugins_Objects') WHERE name='ObjectGUID'
|
||||
""").fetchone()[0] == 0
|
||||
|
||||
if plug_ObjectGUID_missing :
|
||||
mylog('verbose', ["[upgradeDB] Adding ObjectGUID to the Plugins_Objects table"])
|
||||
self.sql.execute("""
|
||||
ALTER TABLE "Plugins_Objects" ADD "ObjectGUID" TEXT
|
||||
""")
|
||||
|
||||
|
||||
# -----------------------------------------
|
||||
# REMOVE after 6/6/2025 - END
|
||||
@@ -645,6 +658,17 @@ class DB():
|
||||
self.sql.execute('ALTER TABLE "Plugins_Events" ADD COLUMN "HelpVal2" TEXT')
|
||||
self.sql.execute('ALTER TABLE "Plugins_Events" ADD COLUMN "HelpVal3" TEXT')
|
||||
self.sql.execute('ALTER TABLE "Plugins_Events" ADD COLUMN "HelpVal4" TEXT')
|
||||
|
||||
# plug_ObjectGUID_missing column
|
||||
plug_ObjectGUID_missing = self.sql.execute ("""
|
||||
SELECT COUNT(*) AS CNTREC FROM pragma_table_info('Plugins_Events') WHERE name='ObjectGUID'
|
||||
""").fetchone()[0] == 0
|
||||
|
||||
if plug_ObjectGUID_missing :
|
||||
mylog('verbose', ["[upgradeDB] Adding ObjectGUID to the Plugins_Events table"])
|
||||
self.sql.execute("""
|
||||
ALTER TABLE "Plugins_Events" ADD "ObjectGUID" TEXT
|
||||
""")
|
||||
|
||||
# -----------------------------------------
|
||||
# REMOVE after 6/6/2025 - END
|
||||
@@ -703,6 +727,18 @@ class DB():
|
||||
self.sql.execute('ALTER TABLE "Plugins_History" ADD COLUMN "HelpVal3" TEXT')
|
||||
self.sql.execute('ALTER TABLE "Plugins_History" ADD COLUMN "HelpVal4" TEXT')
|
||||
|
||||
|
||||
# plug_ObjectGUID_missing column
|
||||
plug_ObjectGUID_missing = self.sql.execute ("""
|
||||
SELECT COUNT(*) AS CNTREC FROM pragma_table_info('Plugins_History') WHERE name='ObjectGUID'
|
||||
""").fetchone()[0] == 0
|
||||
|
||||
if plug_ObjectGUID_missing :
|
||||
mylog('verbose', ["[upgradeDB] Adding ObjectGUID to the Plugins_History table"])
|
||||
self.sql.execute("""
|
||||
ALTER TABLE "Plugins_History" ADD "ObjectGUID" TEXT
|
||||
""")
|
||||
|
||||
# -----------------------------------------
|
||||
# REMOVE after 6/6/2025 - END
|
||||
# -----------------------------------------
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
import json
|
||||
|
||||
def update_value(json_data, object_path, key, value, target_property, desired_value):
|
||||
# Helper function to traverse the JSON structure and get the target object
|
||||
def traverse(obj, path):
|
||||
keys = path.split(".")
|
||||
for key in keys:
|
||||
if isinstance(obj, list):
|
||||
key = int(key)
|
||||
obj = obj[key]
|
||||
return obj
|
||||
|
||||
# Helper function to update the target property with the desired value
|
||||
def update(obj, path, key, value, target_property, desired_value):
|
||||
keys = path.split(".")
|
||||
for i, key in enumerate(keys):
|
||||
if isinstance(obj, list):
|
||||
key = int(key)
|
||||
# Check if we have reached the desired object
|
||||
if i == len(keys) - 1 and obj[key][key] == value:
|
||||
# Update the target property with the desired value
|
||||
obj[key][target_property] = desired_value
|
||||
else:
|
||||
obj = obj[key]
|
||||
return obj
|
||||
|
||||
# Get the target object based on the object path
|
||||
target_obj = traverse(json_data, object_path)
|
||||
# Update the value in the target object
|
||||
updated_obj = update(json_data, object_path, key, value, target_property, desired_value)
|
||||
return updated_obj
|
||||
84
server/models/device_instance.py
Executable file
84
server/models/device_instance.py
Executable file
@@ -0,0 +1,84 @@
|
||||
import sys
|
||||
|
||||
# Register NetAlertX directories
|
||||
INSTALL_PATH="/app"
|
||||
sys.path.extend([f"{INSTALL_PATH}/server"])
|
||||
|
||||
from logger import mylog, print_log
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Device object handling (WIP)
|
||||
#-------------------------------------------------------------------------------
|
||||
class DeviceInstance:
|
||||
def __init__(self, db):
|
||||
self.db = db
|
||||
|
||||
# Get all
|
||||
def getAll(self):
|
||||
self.db.sql.execute("""
|
||||
SELECT * FROM Devices
|
||||
""")
|
||||
return self.db.sql.fetchall()
|
||||
|
||||
# Get all with unknown names
|
||||
def getUnknown(self):
|
||||
self.db.sql.execute("""
|
||||
SELECT * FROM Devices WHERE devName in ("(unknown)", "(name not found)", "" )
|
||||
""")
|
||||
return self.db.sql.fetchall()
|
||||
|
||||
# Get specific column value based on devMac
|
||||
def getValueWithMac(self, column_name, devMac):
|
||||
|
||||
query = f"SELECT {column_name} FROM Devices WHERE devMac = ?"
|
||||
self.db.sql.execute(query, (devMac,))
|
||||
result = self.db.sql.fetchone()
|
||||
return result[column_name] if result else None
|
||||
|
||||
# Get all down
|
||||
def getDown(self):
|
||||
self.db.sql.execute("""
|
||||
SELECT * FROM Devices WHERE devAlertDown = 1 and devPresentLastScan = 0
|
||||
""")
|
||||
return self.db.sql.fetchall()
|
||||
|
||||
# Get all down
|
||||
def getOffline(self):
|
||||
self.db.sql.execute("""
|
||||
SELECT * FROM Devices WHERE devPresentLastScan = 0
|
||||
""")
|
||||
return self.db.sql.fetchall()
|
||||
|
||||
# Get a device by devGUID
|
||||
def getByGUID(self, devGUID):
|
||||
self.db.sql.execute("SELECT * FROM Devices WHERE devGUID = ?", (devGUID,))
|
||||
result = self.db.sql.fetchone()
|
||||
return dict(result) if result else None
|
||||
|
||||
# Check if a device exists by devGUID
|
||||
def exists(self, devGUID):
|
||||
self.db.sql.execute("SELECT COUNT(*) AS count FROM Devices WHERE devGUID = ?", (devGUID,))
|
||||
result = self.db.sql.fetchone()
|
||||
return result["count"] > 0
|
||||
|
||||
# Update a specific field for a device
|
||||
def updateField(self, devGUID, field, value):
|
||||
if not self.exists(devGUID):
|
||||
m = f"[Device] In 'updateField': GUID {devGUID} not found."
|
||||
mylog('none', m)
|
||||
raise ValueError(m)
|
||||
|
||||
self.db.sql.execute(f"""
|
||||
UPDATE Devices SET {field} = ? WHERE devGUID = ?
|
||||
""", (value, devGUID))
|
||||
self.db.sql.commit()
|
||||
|
||||
# Delete a device by devGUID
|
||||
def delete(self, devGUID):
|
||||
if not self.exists(devGUID):
|
||||
m = f"[Device] In 'delete': GUID {devGUID} not found."
|
||||
mylog('none', m)
|
||||
raise ValueError(m)
|
||||
|
||||
self.db.sql.execute("DELETE FROM Devices WHERE devGUID = ?", (devGUID,))
|
||||
self.db.sql.commit()
|
||||
65
server/models/plugin_object_instance.py
Executable file
65
server/models/plugin_object_instance.py
Executable file
@@ -0,0 +1,65 @@
|
||||
import sys
|
||||
|
||||
# Register NetAlertX directories
|
||||
INSTALL_PATH="/app"
|
||||
sys.path.extend([f"{INSTALL_PATH}/server"])
|
||||
|
||||
from logger import mylog, print_log
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Plugin object handling (WIP)
|
||||
#-------------------------------------------------------------------------------
|
||||
class PluginObjectInstance:
|
||||
def __init__(self, db):
|
||||
self.db = db
|
||||
|
||||
# Get all plugin objects
|
||||
def getAll(self):
|
||||
self.db.sql.execute("""
|
||||
SELECT * FROM Plugins_Objects
|
||||
""")
|
||||
return self.db.sql.fetchall()
|
||||
|
||||
# Get plugin object by ObjectGUID
|
||||
def getByGUID(self, ObjectGUID):
|
||||
self.db.sql.execute("SELECT * FROM Plugins_Objects WHERE ObjectGUID = ?", (ObjectGUID,))
|
||||
result = self.db.sql.fetchone()
|
||||
return dict(result) if result else None
|
||||
|
||||
# Check if a plugin object exists by ObjectGUID
|
||||
def exists(self, ObjectGUID):
|
||||
self.db.sql.execute("SELECT COUNT(*) AS count FROM Plugins_Objects WHERE ObjectGUID = ?", (ObjectGUID,))
|
||||
result = self.db.sql.fetchone()
|
||||
return result["count"] > 0
|
||||
|
||||
# Get objects by plugin name
|
||||
def getByPlugin(self, plugin):
|
||||
self.db.sql.execute("SELECT * FROM Plugins_Objects WHERE Plugin = ?", (plugin,))
|
||||
return self.db.sql.fetchall()
|
||||
|
||||
# Get objects by status
|
||||
def getByStatus(self, status):
|
||||
self.db.sql.execute("SELECT * FROM Plugins_Objects WHERE Status = ?", (status,))
|
||||
return self.db.sql.fetchall()
|
||||
|
||||
# Update a specific field for a plugin object
|
||||
def updateField(self, ObjectGUID, field, value):
|
||||
if not self.exists(ObjectGUID):
|
||||
m = f"[PluginObject] In 'updateField': GUID {ObjectGUID} not found."
|
||||
mylog('none', m)
|
||||
raise ValueError(m)
|
||||
|
||||
self.db.sql.execute(f"""
|
||||
UPDATE Plugins_Objects SET {field} = ? WHERE ObjectGUID = ?
|
||||
""", (value, ObjectGUID))
|
||||
self.db.sql.commit()
|
||||
|
||||
# Delete a plugin object by ObjectGUID
|
||||
def delete(self, ObjectGUID):
|
||||
if not self.exists(ObjectGUID):
|
||||
m = f"[PluginObject] In 'delete': GUID {ObjectGUID} not found."
|
||||
mylog('none', m)
|
||||
raise ValueError(m)
|
||||
|
||||
self.db.sql.execute("DELETE FROM Plugins_Objects WHERE ObjectGUID = ?", (ObjectGUID,))
|
||||
self.db.sql.commit()
|
||||
@@ -18,6 +18,7 @@ from api import update_api
|
||||
from plugin_utils import logEventStatusCounts, get_plugin_string, get_plugin_setting_obj, print_plugin_info, list_to_csv, combine_plugin_objects, resolve_wildcards_arr, handle_empty, custom_plugin_decoder, decode_and_rename_files
|
||||
from notification import Notification_obj, write_notification
|
||||
from user_events_queue import UserEventsQueue
|
||||
from crypto_utils import generate_deterministic_guid
|
||||
|
||||
# Make sure log level is initialized correctly
|
||||
Logger(get_setting_value('LOG_LEVEL'))
|
||||
@@ -582,13 +583,14 @@ def process_plugin_events(db, plugin, plugEventsArr):
|
||||
for plugObj in pluginObjects:
|
||||
# keep old createdTime time if the plugObj already was created before
|
||||
createdTime = plugObj.changed if plugObj.status == 'new' else plugObj.created
|
||||
# 18 values without Index
|
||||
# 19 values without Index
|
||||
values = (
|
||||
plugObj.pluginPref, plugObj.primaryId, plugObj.secondaryId, createdTime,
|
||||
plugObj.changed, plugObj.watched1, plugObj.watched2, plugObj.watched3,
|
||||
plugObj.watched4, plugObj.status, plugObj.extra, plugObj.userData,
|
||||
plugObj.foreignKey, plugObj.syncHubNodeName,
|
||||
plugObj.helpVal1, plugObj.helpVal2, plugObj.helpVal3, plugObj.helpVal4
|
||||
plugObj.helpVal1, plugObj.helpVal2, plugObj.helpVal3, plugObj.helpVal4,
|
||||
plugObj.objectGUID
|
||||
)
|
||||
|
||||
if plugObj.status == 'new':
|
||||
@@ -625,8 +627,8 @@ def process_plugin_events(db, plugin, plugEventsArr):
|
||||
("Plugin", "Object_PrimaryID", "Object_SecondaryID", "DateTimeCreated",
|
||||
"DateTimeChanged", "Watched_Value1", "Watched_Value2", "Watched_Value3",
|
||||
"Watched_Value4", "Status", "Extra", "UserData", "ForeignKey", "SyncHubNodeName",
|
||||
"HelpVal1", "HelpVal2", "HelpVal3", "HelpVal4")
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
"HelpVal1", "HelpVal2", "HelpVal3", "HelpVal4", "ObjectGUID")
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""", objects_to_insert
|
||||
)
|
||||
|
||||
@@ -637,7 +639,8 @@ def process_plugin_events(db, plugin, plugEventsArr):
|
||||
UPDATE Plugins_Objects
|
||||
SET "Plugin" = ?, "Object_PrimaryID" = ?, "Object_SecondaryID" = ?, "DateTimeCreated" = ?,
|
||||
"DateTimeChanged" = ?, "Watched_Value1" = ?, "Watched_Value2" = ?, "Watched_Value3" = ?,
|
||||
"Watched_Value4" = ?, "Status" = ?, "Extra" = ?, "UserData" = ?, "ForeignKey" = ?, "SyncHubNodeName" = ?, "HelpVal1" = ?, "HelpVal2" = ?, "HelpVal3" = ?, "HelpVal4" = ?
|
||||
"Watched_Value4" = ?, "Status" = ?, "Extra" = ?, "UserData" = ?, "ForeignKey" = ?, "SyncHubNodeName" = ?, "HelpVal1" = ?, "HelpVal2" = ?, "HelpVal3" = ?, "HelpVal4" = ?,
|
||||
"ObjectGUID" = ?
|
||||
WHERE "Index" = ?
|
||||
""", objects_to_update
|
||||
)
|
||||
@@ -651,8 +654,9 @@ def process_plugin_events(db, plugin, plugEventsArr):
|
||||
("Plugin", "Object_PrimaryID", "Object_SecondaryID", "DateTimeCreated",
|
||||
"DateTimeChanged", "Watched_Value1", "Watched_Value2", "Watched_Value3",
|
||||
"Watched_Value4", "Status", "Extra", "UserData", "ForeignKey", "SyncHubNodeName",
|
||||
"HelpVal1", "HelpVal2", "HelpVal3", "HelpVal4")
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
"HelpVal1", "HelpVal2", "HelpVal3", "HelpVal4",
|
||||
"ObjectGUID")
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""", events_to_insert
|
||||
)
|
||||
|
||||
@@ -665,8 +669,9 @@ def process_plugin_events(db, plugin, plugEventsArr):
|
||||
("Plugin", "Object_PrimaryID", "Object_SecondaryID", "DateTimeCreated",
|
||||
"DateTimeChanged", "Watched_Value1", "Watched_Value2", "Watched_Value3",
|
||||
"Watched_Value4", "Status", "Extra", "UserData", "ForeignKey", "SyncHubNodeName",
|
||||
"HelpVal1", "HelpVal2", "HelpVal3", "HelpVal4")
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
"HelpVal1", "HelpVal2", "HelpVal3", "HelpVal4",
|
||||
"ObjectGUID")
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""", history_to_insert
|
||||
)
|
||||
|
||||
@@ -807,6 +812,7 @@ class plugin_object_class:
|
||||
self.helpVal2 = objDbRow[16]
|
||||
self.helpVal3 = objDbRow[17]
|
||||
self.helpVal4 = objDbRow[18]
|
||||
self.objectGUID = generate_deterministic_guid(self.pluginPref, self.primaryId, self.secondaryId)
|
||||
|
||||
|
||||
# Check if self.status is valid
|
||||
|
||||
@@ -6,7 +6,7 @@ from logger import mylog
|
||||
from const import pluginsPath, logPath, apiPath
|
||||
from helper import timeNowTZ, get_file_content, write_file, get_setting, get_setting_value, setting_value_to_python_type
|
||||
from app_state import updateState
|
||||
from crypto_utils import decrypt_data
|
||||
from crypto_utils import decrypt_data, generate_deterministic_guid
|
||||
|
||||
module_name = 'Plugin utils'
|
||||
|
||||
|
||||
@@ -1,62 +1,17 @@
|
||||
import sys
|
||||
|
||||
# Register NetAlertX directories
|
||||
INSTALL_PATH="/app"
|
||||
sys.path.extend([f"{INSTALL_PATH}/server"])
|
||||
|
||||
import subprocess
|
||||
|
||||
import conf
|
||||
import os
|
||||
import re
|
||||
from helper import timeNowTZ, get_setting, get_setting_value, list_to_where, resolve_device_name_dig, get_device_name_nbtlookup, get_device_name_nslookup, get_device_name_mdns, check_IP_format, sanitize_SQL_input
|
||||
from logger import mylog, print_log
|
||||
from const import vendorsPath, vendorsPathNewest, sql_generateGuid
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Device object handling (WIP)
|
||||
#-------------------------------------------------------------------------------
|
||||
class Device_obj:
|
||||
def __init__(self, db):
|
||||
self.db = db
|
||||
|
||||
# Get all
|
||||
def getAll(self):
|
||||
self.db.sql.execute("""
|
||||
SELECT * FROM Devices
|
||||
""")
|
||||
return self.db.sql.fetchall()
|
||||
|
||||
# Get all with unknown names
|
||||
def getUnknown(self):
|
||||
self.db.sql.execute("""
|
||||
SELECT * FROM Devices WHERE devName in ("(unknown)", "(name not found)", "" )
|
||||
""")
|
||||
return self.db.sql.fetchall()
|
||||
|
||||
# Get specific column value based on devMac
|
||||
def getValueWithMac(self, column_name, devMac):
|
||||
|
||||
query = f"SELECT {column_name} FROM Devices WHERE devMac = ?"
|
||||
|
||||
self.db.sql.execute(query, (devMac,))
|
||||
|
||||
result = self.db.sql.fetchone()
|
||||
|
||||
return result[column_name] if result else None
|
||||
|
||||
# Get all down
|
||||
def getDown(self):
|
||||
self.db.sql.execute("""
|
||||
SELECT * FROM Devices WHERE devAlertDown = 1 and devPresentLastScan = 0
|
||||
""")
|
||||
return self.db.sql.fetchall()
|
||||
|
||||
# Get all down
|
||||
def getOffline(self):
|
||||
self.db.sql.execute("""
|
||||
SELECT * FROM Devices WHERE devPresentLastScan = 0
|
||||
""")
|
||||
return self.db.sql.fetchall()
|
||||
|
||||
|
||||
|
||||
|
||||
from models.device_instance import DeviceInstance
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Removing devices from the CurrentScan DB table which the user chose to ignore by MAC or IP
|
||||
@@ -535,7 +490,7 @@ def update_devices_names (db):
|
||||
foundNbtLookup = 0
|
||||
|
||||
# Gen unknown devices
|
||||
device_handler = Device_obj(db)
|
||||
device_handler = DeviceInstance(db)
|
||||
# Retrieve devices
|
||||
unknownDevices = device_handler.getUnknown()
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import sys
|
||||
|
||||
# Register NetAlertX directories
|
||||
INSTALL_PATH="/app"
|
||||
sys.path.extend([f"{INSTALL_PATH}/server"])
|
||||
|
||||
import conf
|
||||
|
||||
|
||||
from device import create_new_devices, print_scan_stats, save_scanned_devices, update_devices_data_from_scan, exclude_ignored_devices
|
||||
from scan.device_handling import create_new_devices, print_scan_stats, save_scanned_devices, update_devices_data_from_scan, exclude_ignored_devices
|
||||
from helper import timeNowTZ
|
||||
from logger import mylog
|
||||
from reporting import skip_repeated_notifications
|
||||
|
||||
|
||||
|
||||
#===============================================================================
|
||||
# SCAN NETWORK
|
||||
#===============================================================================
|
||||
90
server/workflows/actions.py
Executable file
90
server/workflows/actions.py
Executable file
@@ -0,0 +1,90 @@
|
||||
import sys
|
||||
|
||||
# Register NetAlertX directories
|
||||
INSTALL_PATH="/app"
|
||||
sys.path.extend([f"{INSTALL_PATH}/server"])
|
||||
|
||||
import conf
|
||||
from logger import mylog, Logger
|
||||
from helper import get_setting_value, timeNowTZ
|
||||
|
||||
# Make sure log level is initialized correctly
|
||||
Logger(get_setting_value('LOG_LEVEL'))
|
||||
|
||||
from workflows.triggers import Trigger
|
||||
|
||||
class Action:
|
||||
"""Base class for all actions."""
|
||||
|
||||
def __init__(self, trigger):
|
||||
self.trigger = trigger
|
||||
|
||||
def execute(self, obj):
|
||||
"""Executes the action on the given object."""
|
||||
raise NotImplementedError("Subclasses must implement execute()")
|
||||
|
||||
|
||||
class UpdateFieldAction(Action):
|
||||
"""Action to update a specific field of an object."""
|
||||
|
||||
def __init__(self, field, value, trigger):
|
||||
super().__init__(trigger) # Call the base class constructor
|
||||
self.field = field
|
||||
self.value = value
|
||||
|
||||
def execute(self):
|
||||
mylog('verbose', [f"Updating field '{self.field}' to '{self.value}' for event object {self.trigger.object_type}"])
|
||||
|
||||
obj = self.trigger.object
|
||||
|
||||
if isinstance(obj, dict) and "ObjectGUID" in obj:
|
||||
plugin_instance = PluginObjectInstance(self.trigger.db)
|
||||
plugin_instance.updateField(obj["ObjectGUID"], self.field, self.value)
|
||||
elif isinstance(obj, dict) and "devGUID" in obj:
|
||||
device_instance = DeviceInstance(self.trigger.db)
|
||||
device_instance.updateField(obj["devGUID"], self.field, self.value)
|
||||
return obj
|
||||
|
||||
|
||||
class RunPluginAction(Action):
|
||||
"""Action to run a specific plugin."""
|
||||
|
||||
def __init__(self, plugin_name, params, trigger): # Add trigger
|
||||
super().__init__(trigger) # Call parent constructor
|
||||
self.plugin_name = plugin_name
|
||||
self.params = params
|
||||
|
||||
def execute(self):
|
||||
|
||||
obj = self.trigger.object
|
||||
|
||||
mylog('verbose', [f"Executing plugin '{self.plugin_name}' with parameters {self.params} for object {obj}"])
|
||||
# PluginManager.run(self.plugin_name, self.parameters)
|
||||
return obj
|
||||
|
||||
|
||||
class SendNotificationAction(Action):
|
||||
"""Action to send a notification."""
|
||||
|
||||
def __init__(self, method, message, trigger):
|
||||
super().__init__(trigger) # Call parent constructor
|
||||
self.method = method # Fix attribute name
|
||||
self.message = message
|
||||
|
||||
def execute(self):
|
||||
obj = self.trigger.object
|
||||
mylog('verbose', [f"Sending notification via '{self.method}': {self.message} for object {obj}"])
|
||||
# NotificationManager.send(self.method, self.message)
|
||||
return obj
|
||||
|
||||
|
||||
class ActionGroup:
|
||||
"""Handles multiple actions applied to an object."""
|
||||
|
||||
def __init__(self, actions):
|
||||
self.actions = actions
|
||||
|
||||
def execute(self, obj):
|
||||
for action in self.actions:
|
||||
action.execute(obj)
|
||||
return obj
|
||||
@@ -1,13 +1,28 @@
|
||||
import datetime
|
||||
import json
|
||||
import uuid
|
||||
import sys
|
||||
import pytz
|
||||
|
||||
# Register NetAlertX directories
|
||||
INSTALL_PATH="/app"
|
||||
sys.path.extend([f"{INSTALL_PATH}/server"])
|
||||
|
||||
# Register NetAlertX modules
|
||||
import conf
|
||||
from helper import get_setting_value, timeNowTZ
|
||||
# Make sure the TIMEZONE for logging is correct
|
||||
# conf.tz = pytz.timezone(get_setting_value('TIMEZONE'))
|
||||
|
||||
from logger import mylog, Logger, print_log, logResult
|
||||
|
||||
# Make sure log level is initialized correctly
|
||||
Logger(get_setting_value('LOG_LEVEL'))
|
||||
|
||||
from const import applicationPath, logPath, apiPath, confFileName, sql_generateGuid
|
||||
from logger import logResult, mylog, print_log
|
||||
from helper import timeNowTZ
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Execution object handling
|
||||
#-------------------------------------------------------------------------------
|
||||
@@ -32,6 +47,7 @@ class AppEvent_obj:
|
||||
self.db.sql.execute("""CREATE TABLE IF NOT EXISTS "AppEvents" (
|
||||
"Index" INTEGER,
|
||||
"GUID" TEXT UNIQUE,
|
||||
"AppEventProcessed" BOOLEAN,
|
||||
"DateTimeCreated" TEXT,
|
||||
"ObjectType" TEXT, -- ObjectType (Plugins, Notifications, Events)
|
||||
"ObjectGUID" TEXT,
|
||||
@@ -59,7 +75,9 @@ class AppEvent_obj:
|
||||
sql_devices_mappedColumns = '''
|
||||
"GUID",
|
||||
"DateTimeCreated",
|
||||
"AppEventProcessed",
|
||||
"ObjectType",
|
||||
"ObjectGUID",
|
||||
"ObjectPrimaryID",
|
||||
"ObjectSecondaryID",
|
||||
"ObjectStatus",
|
||||
@@ -81,7 +99,9 @@ class AppEvent_obj:
|
||||
VALUES (
|
||||
{sql_generateGuid},
|
||||
DATETIME('now'),
|
||||
FALSE,
|
||||
'Devices',
|
||||
NEW.devGUID,
|
||||
NEW.devMac,
|
||||
NEW.devLastIP,
|
||||
CASE WHEN NEW.devPresentLastScan = 1 THEN 'online' ELSE 'offline' END,
|
||||
@@ -111,7 +131,9 @@ class AppEvent_obj:
|
||||
VALUES (
|
||||
{sql_generateGuid},
|
||||
DATETIME('now'),
|
||||
FALSE,
|
||||
'Devices',
|
||||
NEW.devGUID,
|
||||
NEW.devMac,
|
||||
NEW.devLastIP,
|
||||
CASE WHEN NEW.devPresentLastScan = 1 THEN 'online' ELSE 'offline' END,
|
||||
@@ -135,7 +157,9 @@ class AppEvent_obj:
|
||||
VALUES (
|
||||
{sql_generateGuid},
|
||||
DATETIME('now'),
|
||||
FALSE,
|
||||
'Devices',
|
||||
OLD.devGUID,
|
||||
OLD.devMac,
|
||||
OLD.devLastIP,
|
||||
CASE WHEN OLD.devPresentLastScan = 1 THEN 'online' ELSE 'offline' END,
|
||||
@@ -155,7 +179,9 @@ class AppEvent_obj:
|
||||
sql_plugins_objects_mappedColumns = '''
|
||||
"GUID",
|
||||
"DateTimeCreated",
|
||||
"AppEventProcessed",
|
||||
"ObjectType",
|
||||
"ObjectGUID",
|
||||
"ObjectPlugin",
|
||||
"ObjectPrimaryID",
|
||||
"ObjectSecondaryID",
|
||||
@@ -176,8 +202,10 @@ class AppEvent_obj:
|
||||
VALUES (
|
||||
{sql_generateGuid},
|
||||
DATETIME('now'),
|
||||
'Plugins_Objects',
|
||||
NEW.Plugin,
|
||||
FALSE,
|
||||
'Plugins_Objects',
|
||||
NEW.ObjectGUID,
|
||||
NEW.Plugin,
|
||||
NEW.Object_PrimaryID,
|
||||
NEW.Object_SecondaryID,
|
||||
NEW.ForeignKey,
|
||||
@@ -199,7 +227,9 @@ class AppEvent_obj:
|
||||
VALUES (
|
||||
{sql_generateGuid},
|
||||
DATETIME('now'),
|
||||
FALSE,
|
||||
'Plugins_Objects',
|
||||
NEW.ObjectGUID,
|
||||
NEW.Plugin,
|
||||
NEW.Object_PrimaryID,
|
||||
NEW.Object_SecondaryID,
|
||||
@@ -222,7 +252,9 @@ class AppEvent_obj:
|
||||
VALUES (
|
||||
{sql_generateGuid},
|
||||
DATETIME('now'),
|
||||
FALSE,
|
||||
'Plugins_Objects',
|
||||
OLD.ObjectGUID,
|
||||
OLD.Plugin,
|
||||
OLD.Object_PrimaryID,
|
||||
OLD.Object_SecondaryID,
|
||||
83
server/workflows/conditions.py
Executable file
83
server/workflows/conditions.py
Executable file
@@ -0,0 +1,83 @@
|
||||
import re
|
||||
import sys
|
||||
import json
|
||||
|
||||
# Register NetAlertX directories
|
||||
INSTALL_PATH="/app"
|
||||
sys.path.extend([f"{INSTALL_PATH}/server"])
|
||||
|
||||
import conf
|
||||
from logger import mylog, Logger
|
||||
from helper import get_setting_value, timeNowTZ
|
||||
|
||||
# Make sure log level is initialized correctly
|
||||
Logger(get_setting_value('LOG_LEVEL'))
|
||||
|
||||
class Condition:
|
||||
"""Evaluates a single condition."""
|
||||
|
||||
def __init__(self, condition_json):
|
||||
self.field = condition_json["field"]
|
||||
self.operator = condition_json["operator"]
|
||||
self.value = condition_json["value"]
|
||||
self.negate = condition_json.get("negate", False)
|
||||
|
||||
def evaluate(self, trigger):
|
||||
|
||||
# try finding the value of the field on the event triggering this workflow or thre object triggering the app event
|
||||
appEvent_value = trigger.event[self.field] if self.field in trigger.event.keys() else None
|
||||
eveObj_value = trigger.object[self.field] if self.field in trigger.object.keys() else None
|
||||
|
||||
|
||||
# proceed only if value found
|
||||
if appEvent_value is None and eveObj_value is None:
|
||||
return False
|
||||
elif appEvent_value is not None:
|
||||
obj_value = appEvent_value
|
||||
elif eveObj_value is not None:
|
||||
obj_value = eveObj_value
|
||||
|
||||
# process based on operators
|
||||
if self.operator == "equals":
|
||||
result = str(obj_value) == str(self.value)
|
||||
elif self.operator == "contains":
|
||||
result = str(self.value) in str(obj_value)
|
||||
elif self.operator == "regex":
|
||||
result = bool(re.match(self.value, str(obj_value)))
|
||||
else:
|
||||
m = f"[WF] Unsupported operator: {self.operator}"
|
||||
mylog('none', [m])
|
||||
raise ValueError(m)
|
||||
|
||||
return not result if self.negate else result
|
||||
|
||||
|
||||
class ConditionGroup:
|
||||
"""Handles condition groups with AND, OR logic, supporting nested groups."""
|
||||
|
||||
def __init__(self, group_json):
|
||||
|
||||
mylog('none', ["[WF] json.dumps(group_json)"])
|
||||
mylog('none', [json.dumps(group_json)])
|
||||
mylog('none', [group_json])
|
||||
|
||||
self.logic = group_json.get("logic", "AND").upper()
|
||||
self.conditions = []
|
||||
|
||||
for condition in group_json["conditions"]:
|
||||
if "field" in condition: # Simple condition
|
||||
self.conditions.append(Condition(condition))
|
||||
else: # Nested condition group
|
||||
self.conditions.append(ConditionGroup(condition))
|
||||
|
||||
def evaluate(self, event):
|
||||
results = [condition.evaluate(event) for condition in self.conditions]
|
||||
|
||||
if self.logic == "AND":
|
||||
return all(results)
|
||||
elif self.logic == "OR":
|
||||
return any(results)
|
||||
else:
|
||||
m = f"[WF] Unsupported logic: {self.logic}"
|
||||
mylog('none', [m])
|
||||
raise ValueError(m)
|
||||
154
server/workflows/manager.py
Executable file
154
server/workflows/manager.py
Executable file
@@ -0,0 +1,154 @@
|
||||
import sys
|
||||
import json
|
||||
|
||||
# Register NetAlertX directories
|
||||
INSTALL_PATH="/app"
|
||||
sys.path.extend([f"{INSTALL_PATH}/server"])
|
||||
|
||||
import conf
|
||||
from const import fullConfFolder
|
||||
import workflows.actions
|
||||
from logger import mylog, Logger
|
||||
from helper import get_setting_value, timeNowTZ
|
||||
|
||||
# Make sure log level is initialized correctly
|
||||
Logger(get_setting_value('LOG_LEVEL'))
|
||||
|
||||
from workflows.triggers import Trigger
|
||||
from workflows.conditions import ConditionGroup
|
||||
from workflows.actions import *
|
||||
|
||||
class WorkflowManager:
|
||||
def __init__(self, db):
|
||||
self.db = db
|
||||
self.workflows = self.load_workflows()
|
||||
self.update_api = False
|
||||
|
||||
def load_workflows(self):
|
||||
"""Load workflows from workflows.json."""
|
||||
try:
|
||||
workflows_json_path = fullConfFolder + '/workflows.json'
|
||||
with open(workflows_json_path, 'r') as f:
|
||||
workflows = json.load(f)
|
||||
return workflows
|
||||
except (FileNotFoundError, json.JSONDecodeError):
|
||||
mylog('none', ['[WF] Failed to load workflows.json'])
|
||||
return []
|
||||
|
||||
def get_new_app_events(self):
|
||||
"""Get new unprocessed events from the AppEvents table."""
|
||||
result = self.db.sql.execute("""
|
||||
SELECT * FROM AppEvents
|
||||
WHERE AppEventProcessed = 0
|
||||
ORDER BY DateTimeCreated ASC
|
||||
""").fetchall()
|
||||
return result
|
||||
|
||||
def process_event(self, event):
|
||||
"""Process the events. Check if events match a workflow trigger"""
|
||||
|
||||
mylog('verbose', [f"[WF] Processing event with GUID {event["GUID"]}"])
|
||||
|
||||
# Check if the trigger conditions match
|
||||
for workflow in self.workflows:
|
||||
|
||||
# construct trigger object which also evaluates if the current event triggers it
|
||||
trigger = Trigger(workflow["trigger"], event, self.db)
|
||||
|
||||
if trigger.triggered:
|
||||
|
||||
mylog('verbose', [f"[WF] Event with GUID '{event["GUID"]}' triggered the workflow '{workflow["name"]}'"])
|
||||
|
||||
self.execute_workflow(workflow, trigger)
|
||||
|
||||
# After processing the event, mark the event as processed (set AppEventProcessed to 1)
|
||||
self.db.sql.execute("""
|
||||
UPDATE AppEvents
|
||||
SET AppEventProcessed = 1
|
||||
WHERE "Index" = ?
|
||||
""", (event['Index'],)) # Pass the event's unique identifier
|
||||
self.db.commitDB()
|
||||
|
||||
|
||||
|
||||
def execute_workflow(self, workflow, trigger):
|
||||
"""Execute the actions in the given workflow if conditions are met."""
|
||||
|
||||
# Ensure conditions exist
|
||||
if not isinstance(workflow.get("conditions"), list):
|
||||
m = f"[WF] workflow['conditions'] must be a list"
|
||||
mylog('none', [m])
|
||||
raise ValueError(m)
|
||||
|
||||
# Evaluate each condition group separately
|
||||
for condition_group in workflow["conditions"]:
|
||||
|
||||
evaluator = ConditionGroup(condition_group)
|
||||
|
||||
if evaluator.evaluate(trigger): # If any group evaluates to True
|
||||
|
||||
mylog('none', [f"[WF] Workflow {workflow["name"]} will be executed - conditions were evalueted as TRUE"])
|
||||
mylog('debug', [f"[WF] Workflow condition_group: {condition_group}"])
|
||||
|
||||
self.execute_actions(workflow["actions"], trigger)
|
||||
return # Stop if a condition group succeeds
|
||||
|
||||
mylog('none', ["[WF] No condition group matched. Actions not executed."])
|
||||
|
||||
|
||||
def execute_actions(self, actions, trigger):
|
||||
"""Execute the actions defined in a workflow."""
|
||||
|
||||
for action in actions:
|
||||
if action["type"] == "update_field":
|
||||
field = action["field"]
|
||||
value = action["value"]
|
||||
action_instance = UpdateFieldAction(field, value, trigger)
|
||||
# indicate if the api has to be updated
|
||||
self.update_api = True
|
||||
|
||||
elif action["type"] == "run_plugin":
|
||||
plugin_name = action["plugin"]
|
||||
params = action["params"]
|
||||
action_instance = RunPluginAction(plugin_name, params, trigger)
|
||||
|
||||
# elif action["type"] == "send_notification":
|
||||
# method = action["method"]
|
||||
# message = action["message"]
|
||||
# action_instance = SendNotificationAction(method, message, trigger)
|
||||
|
||||
else:
|
||||
m = f"[WF] Unsupported action type: {action['type']}"
|
||||
mylog('none', [m])
|
||||
raise ValueError(m)
|
||||
|
||||
action_instance.execute() # Execute the action
|
||||
|
||||
# if result:
|
||||
# # Iterate through actions and execute them
|
||||
# for action in workflow["actions"]:
|
||||
# if action["type"] == "update_field":
|
||||
# # Action type is "update_field", so map to UpdateFieldAction
|
||||
# field = action["field"]
|
||||
# value = action["value"]
|
||||
# action_instance = UpdateFieldAction(field, value)
|
||||
# action_instance.execute(trigger.event)
|
||||
|
||||
# elif action["type"] == "run_plugin":
|
||||
# # Action type is "run_plugin", so map to RunPluginAction
|
||||
# plugin_name = action["plugin"]
|
||||
# params = action["params"]
|
||||
# action_instance = RunPluginAction(plugin_name, params)
|
||||
# action_instance.execute(trigger.event)
|
||||
# elif action["type"] == "send_notification":
|
||||
# # Action type is "send_notification", so map to SendNotificationAction
|
||||
# method = action["method"]
|
||||
# message = action["message"]
|
||||
# action_instance = SendNotificationAction(method, message)
|
||||
# action_instance.execute(trigger.event)
|
||||
# else:
|
||||
# # Handle unsupported action types
|
||||
# raise ValueError(f"Unsupported action type: {action['type']}")
|
||||
|
||||
|
||||
|
||||
62
server/workflows/triggers.py
Executable file
62
server/workflows/triggers.py
Executable file
@@ -0,0 +1,62 @@
|
||||
import sys
|
||||
|
||||
# Register NetAlertX directories
|
||||
INSTALL_PATH="/app"
|
||||
sys.path.extend([f"{INSTALL_PATH}/server"])
|
||||
|
||||
import conf
|
||||
from logger import mylog, Logger
|
||||
from helper import get_setting_value, timeNowTZ
|
||||
|
||||
# Make sure log level is initialized correctly
|
||||
Logger(get_setting_value('LOG_LEVEL'))
|
||||
|
||||
|
||||
class Trigger:
|
||||
"""Represents a trigger definition"""
|
||||
|
||||
def __init__(self, triggerJson, event, db):
|
||||
"""
|
||||
:param name: Friendly name of the trigger
|
||||
:param triggerJson: JSON trigger object {"object_type":"Devices",event_type":"update"}
|
||||
:param event: The actual event that the trigger is evaluated against
|
||||
:param db: DB connection in case trigger matches and object needs to be retrieved
|
||||
"""
|
||||
self.object_type = triggerJson["object_type"]
|
||||
self.event_type = triggerJson["event_type"]
|
||||
self.event = event # Store the triggered event context, if provided
|
||||
self.triggered = self.object_type == event["ObjectType"] and self.event_type == event["AppEventType"]
|
||||
|
||||
mylog('verbose', [f"[WF] self.triggered '{self.triggered}'"])
|
||||
|
||||
if self.triggered:
|
||||
# object type corresponds with the DB table name
|
||||
db_table = self.object_type
|
||||
|
||||
if db_table == "Devices":
|
||||
refField = "devGUID"
|
||||
elif db_table == "Plugins_Objects":
|
||||
refField = "ObjectGUID"
|
||||
else:
|
||||
m = f"[WF] Unsupported object_type: {self.object_type}"
|
||||
mylog('none', [m])
|
||||
raise ValueError(m)
|
||||
|
||||
query = f"""
|
||||
SELECT * FROM
|
||||
{db_table}
|
||||
WHERE {refField} = '{event["ObjectGUID"]}'
|
||||
"""
|
||||
|
||||
mylog('debug', [query])
|
||||
|
||||
result = db.sql.execute(query).fetchall()
|
||||
self.object = result[0]
|
||||
else:
|
||||
self.object = None
|
||||
|
||||
|
||||
def set_event(self, event):
|
||||
"""Set or update the event context for this trigger"""
|
||||
self.event = event
|
||||
|
||||
74
test/workflows.json
Executable file
74
test/workflows.json
Executable file
@@ -0,0 +1,74 @@
|
||||
[
|
||||
{
|
||||
"name": "Sample Device Update Workflow",
|
||||
"trigger": {
|
||||
"object_type": "Devices",
|
||||
"event_type": "update"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"logic": "AND",
|
||||
"conditions": [
|
||||
{
|
||||
"field": "devVendor",
|
||||
"operator": "contains",
|
||||
"value": "Google"
|
||||
},
|
||||
{
|
||||
"field": "devIsNew",
|
||||
"operator": "equals",
|
||||
"value": "1"
|
||||
},
|
||||
{
|
||||
"logic": "OR",
|
||||
"conditions": [
|
||||
{
|
||||
"field": "devIsNew",
|
||||
"operator": "equals",
|
||||
"value": "1"
|
||||
},
|
||||
{
|
||||
"field": "devName",
|
||||
"operator": "contains",
|
||||
"value": "Google"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"type": "update_field",
|
||||
"field": "devIsNew",
|
||||
"value": "0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Sample Plugin Object Workflow",
|
||||
"trigger": {
|
||||
"object_type": "Plugins_Objects",
|
||||
"event_type": "create"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"logic": "AND",
|
||||
"conditions": [
|
||||
{
|
||||
"field": "Plugin",
|
||||
"operator": "equals",
|
||||
"value": "ARPSCAN"
|
||||
},
|
||||
{
|
||||
"field": "Status",
|
||||
"operator": "equals",
|
||||
"value": "missing-in-last-scan"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
]
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user