diff --git a/docker-compose.yml b/docker-compose.yml
index 4f5716be..39e130ff 100755
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -52,7 +52,8 @@ services:
- ${DEV_LOCATION}/front/settings.php:/home/pi/pialert/front/settings.php
- ${DEV_LOCATION}/front/systeminfo.php:/home/pi/pialert/front/systeminfo.php
- ${DEV_LOCATION}/front/report.php:/home/pi/pialert/front/report.php
- - ${DEV_LOCATION}/front/flows.php:/home/pi/pialert/front/flows.php
+ - ${DEV_LOCATION}/front/workflows.php:/home/pi/pialert/front/workflows.php
+ - ${DEV_LOCATION}/front/appEventsCore.php:/home/pi/pialert/front/appEventsCore.php
- ${DEV_LOCATION}/front/donations.php:/home/pi/pialert/front/donations.php
- ${DEV_LOCATION}/front/plugins:/home/pi/pialert/front/plugins
# DELETE END anyone trying to use this file: comment out / delete ABOVE lines, they are only for development purposes
diff --git a/docs/NETWORK_TREE.md b/docs/NETWORK_TREE.md
index 21368df2..b0646f72 100755
--- a/docs/NETWORK_TREE.md
+++ b/docs/NETWORK_TREE.md
@@ -1,6 +1,6 @@
## How to setup your Network page
-Make sure you have a root device with the MAC `Internet` (No other MAC addresses are currently supported as the root node).
+Make sure you have a root device with the MAC `Internet` (No other MAC addresses are currently supported as the root node) set to a network device type (e.g.: **Type**:`Router`).
> 💡 Tip: You can add dummy devices via the [Undiscoverables plugin](https://github.com/jokob-sk/Pi.Alert/blob/main/front/plugins/undiscoverables/README.md)
diff --git a/front/appEventsCore.php b/front/appEventsCore.php
new file mode 100755
index 00000000..0523c535
--- /dev/null
+++ b/front/appEventsCore.php
@@ -0,0 +1,86 @@
+
+
diff --git a/pialert/api.py b/pialert/api.py
index ea627007..f2c60ac0 100755
--- a/pialert/api.py
+++ b/pialert/api.py
@@ -3,7 +3,7 @@ import json
# pialert modules
import conf
-from const import (apiPath, sql_devices_all, sql_events_pending_alert, sql_settings, sql_plugins_events, sql_plugins_history, sql_plugins_objects,sql_language_strings, sql_notifications_all)
+from const import (apiPath, sql_appevents, sql_devices_all, sql_events_pending_alert, sql_settings, sql_plugins_events, sql_plugins_history, sql_plugins_objects,sql_language_strings, sql_notifications_all)
from logger import mylog
from helper import write_file
@@ -23,6 +23,7 @@ def update_api(db, isNotification = False, updateOnlyDataSources = []):
# prepare database tables we want to expose
dataSourcesSQLs = [
+ ["appevents", sql_appevents],
["devices", sql_devices_all],
["events_pending_alert", sql_events_pending_alert],
["settings", sql_settings],
diff --git a/pialert/appevent.py b/pialert/appevent.py
index a9a850a4..7d2050fa 100755
--- a/pialert/appevent.py
+++ b/pialert/appevent.py
@@ -16,6 +16,15 @@ class AppEvent_obj:
def __init__(self, db):
self.db = db
+ # drop table
+ self.db.sql.execute("""DROP TABLE IF EXISTS "AppEvents" """)
+
+ # Drop all triggers
+ self.db.sql.execute('DROP TRIGGER IF EXISTS trg_create_device;')
+ self.db.sql.execute('DROP TRIGGER IF EXISTS trg_read_device;')
+ self.db.sql.execute('DROP TRIGGER IF EXISTS trg_update_device;')
+ self.db.sql.execute('DROP TRIGGER IF EXISTS trg_delete_device;')
+
# Create AppEvent table if missing
self.db.sql.execute("""CREATE TABLE IF NOT EXISTS "AppEvents" (
"Index" INTEGER,
@@ -29,18 +38,133 @@ class AppEvent_obj:
"ObjectPrimaryID" TEXT,
"ObjectSecondaryID" TEXT,
"ObjectForeignKey" TEXT,
- "ObjectIndex" TEXT,
- "ObjectRowID" TEXT,
+ "ObjectIndex" TEXT,
+ "ObjectIsNew" BOOLEAN,
+ "ObjectIsArchived" BOOLEAN,
"ObjectStatusColumn" TEXT, -- Status (Notifications, Plugins), eve_EventType (Events)
- "ObjectStatus" TEXT, -- new_devices, down_devices, events, new, watched-changed, watched-not-changed, missing-in-last-scan, Device down, New Device, IP Changed, Connected, Disconnected, VOIDED - Disconnected, VOIDED - Connected,
- "AppEventStatus" TEXT, -- TBD "new", "used", "cleanup-next"
- "Extra" TEXT,
+ "ObjectStatus" TEXT, -- new_devices, down_devices, events, new, watched-changed, watched-not-changed, missing-in-last-scan, Device down, New Device, IP Changed, Connected, Disconnected, VOIDED - Disconnected, VOIDED - Connected,
+ "AppEventType" TEXT, -- "create", "update", "delete" (+TBD)
+ "Helper1" TEXT,
+ "Helper2" TEXT,
+ "Helper3" TEXT,
+ "Extra" TEXT,
PRIMARY KEY("Index" AUTOINCREMENT)
);
""")
+ # Generate a GUID
+ sql_generateGuid = '''
+ lower(
+ hex(randomblob(4)) || '-' || hex(randomblob(2)) || '-' || '4' ||
+ substr(hex( randomblob(2)), 2) || '-' ||
+ substr('AB89', 1 + (abs(random()) % 4) , 1) ||
+ substr(hex(randomblob(2)), 2) || '-' ||
+ hex(randomblob(6))
+ )
+ '''
+
+ sql_mappedColumns = '''
+ "GUID",
+ "DateTimeCreated",
+ "ObjectType",
+ "ObjectMAC",
+ "ObjectIP",
+ "ObjectStatus",
+ "ObjectStatusColumn",
+ "ObjectIsNew",
+ "ObjectIsArchived",
+ "ObjectForeignKey",
+ "AppEventType"
+ '''
+
+ # Trigger for create event
+ self.db.sql.execute(f'''
+ CREATE TRIGGER IF NOT EXISTS "trg_create_device"
+ AFTER INSERT ON "Devices"
+ BEGIN
+ INSERT INTO "AppEvents" (
+ {sql_mappedColumns}
+ )
+ VALUES (
+ -- below generates a GUID
+ {sql_generateGuid},
+ DATETIME('now'),
+ 'Devices',
+ NEW.dev_MAC,
+ NEW.dev_LastIP,
+ CASE WHEN NEW.dev_PresentLastScan = 1 THEN 'online' ELSE 'offline' END,
+ 'dev_PresentLastScan',
+ NEW.dev_NewDevice,
+ NEW.dev_Archived,
+ NEW.dev_MAC,
+ 'create'
+ );
+ END;
+ ''')
+
+ # 🔴 This would generate too many events, disabled for now
+ # # Trigger for read event
+ # self.db.sql.execute('''
+ # TODO
+ # ''')
+
+ # Trigger for update event
+ self.db.sql.execute(f'''
+ CREATE TRIGGER IF NOT EXISTS "trg_update_device"
+ AFTER UPDATE ON "Devices"
+ BEGIN
+ INSERT INTO "AppEvents" (
+ {sql_mappedColumns}
+ )
+ VALUES (
+ -- below generates a GUID
+ {sql_generateGuid},
+ DATETIME('now'),
+ 'Devices',
+ NEW.dev_MAC,
+ NEW.dev_LastIP,
+ CASE WHEN NEW.dev_PresentLastScan = 1 THEN 'online' ELSE 'offline' END,
+ 'dev_PresentLastScan',
+ NEW.dev_NewDevice,
+ NEW.dev_Archived,
+ NEW.dev_MAC,
+ 'update'
+ );
+ END;
+ ''')
+
+ # Trigger for delete event
+ self.db.sql.execute(f'''
+ CREATE TRIGGER IF NOT EXISTS "trg_delete_device"
+ AFTER DELETE ON "Devices"
+ BEGIN
+ INSERT INTO "AppEvents" (
+ {sql_mappedColumns}
+ )
+ VALUES (
+ -- below generates a GUID
+ {sql_generateGuid},
+ DATETIME('now'),
+ 'Devices',
+ OLD.dev_MAC,
+ OLD.dev_LastIP,
+ CASE WHEN OLD.dev_PresentLastScan = 1 THEN 'online' ELSE 'offline' END,
+ 'dev_PresentLastScan',
+ OLD.dev_NewDevice,
+ OLD.dev_Archived,
+ OLD.dev_MAC,
+ 'delete'
+ );
+ END;
+ ''')
+
self.save()
+ # -------------------------------------------------------------------------------
+ # -------------------------------------------------------------------------------
+ # below code is unused
+ # -------------------------------------------------------------------------------
+
# Create a new DB entry if new notifications are available, otherwise skip
def create(self, Extra="", **kwargs):
# Check if nothing to report, end
@@ -72,11 +196,6 @@ class AppEvent_obj:
return True
- # Update the status of the entry
- def updateStatus(self, newStatus):
- self.ObjectStatus = newStatus
- self.upsert()
-
def upsert(self):
self.db.sql.execute("""
INSERT OR REPLACE INTO AppEvents (
diff --git a/pialert/const.py b/pialert/const.py
index 62a10aea..e9174649 100755
--- a/pialert/const.py
+++ b/pialert/const.py
@@ -23,6 +23,7 @@ vendorsPath = '/usr/share/arp-scan/ieee-oui.txt'
# SQL queries
#===============================================================================
sql_devices_all = """select rowid, * from Devices"""
+sql_appevents = """select * from AppEvents"""
sql_devices_stats = """SELECT Online_Devices as online, Down_Devices as down, All_Devices as 'all', Archived_Devices as archived,
(select count(*) from Devices a where dev_NewDevice = 1 ) as new,
(select count(*) from Devices a where dev_Name = '(unknown)' or dev_Name = '(name not found)' ) as unknown
diff --git a/pialert/database.py b/pialert/database.py
index 53f0d505..30e7bfe5 100755
--- a/pialert/database.py
+++ b/pialert/database.py
@@ -7,6 +7,7 @@ from const import fullDbPath, sql_devices_stats, sql_devices_all
from logger import mylog
from helper import json_obj, initOrSetParam, row_to_json, timeNowTZ #, updateState
+from appevent import AppEvent_obj
@@ -83,8 +84,8 @@ class DB():
# indicates, if Online_History table is available
onlineHistoryAvailable = self.sql.execute("""
- SELECT name FROM sqlite_master WHERE type='table'
- AND name='Online_History';
+ SELECT name FROM sqlite_master WHERE type='table'
+ AND name='Online_History';
""").fetchall() != []
# Check if it is incompatible (Check if table has all required columns)
@@ -92,8 +93,8 @@ class DB():
if onlineHistoryAvailable :
isIncompatible = self.sql.execute ("""
- SELECT COUNT(*) AS CNTREC FROM pragma_table_info('Online_History') WHERE name='Archived_Devices'
- """).fetchone()[0] == 0
+ SELECT COUNT(*) AS CNTREC FROM pragma_table_info('Online_History') WHERE name='Archived_Devices'
+ """).fetchone()[0] == 0
# Drop table if available, but incompatible
if onlineHistoryAvailable and isIncompatible:
@@ -119,35 +120,35 @@ class DB():
# -------------------------------------------------------------------------
# dev_Network_Node_MAC_ADDR column
dev_Network_Node_MAC_ADDR_missing = self.sql.execute ("""
- SELECT COUNT(*) AS CNTREC FROM pragma_table_info('Devices') WHERE name='dev_Network_Node_MAC_ADDR'
+ SELECT COUNT(*) AS CNTREC FROM pragma_table_info('Devices') WHERE name='dev_Network_Node_MAC_ADDR'
""").fetchone()[0] == 0
if dev_Network_Node_MAC_ADDR_missing :
mylog('verbose', ["[upgradeDB] Adding dev_Network_Node_MAC_ADDR to the Devices table"])
self.sql.execute("""
- ALTER TABLE "Devices" ADD "dev_Network_Node_MAC_ADDR" TEXT
+ ALTER TABLE "Devices" ADD "dev_Network_Node_MAC_ADDR" TEXT
""")
# dev_Network_Node_port column
dev_Network_Node_port_missing = self.sql.execute ("""
- SELECT COUNT(*) AS CNTREC FROM pragma_table_info('Devices') WHERE name='dev_Network_Node_port'
+ SELECT COUNT(*) AS CNTREC FROM pragma_table_info('Devices') WHERE name='dev_Network_Node_port'
""").fetchone()[0] == 0
if dev_Network_Node_port_missing :
mylog('verbose', ["[upgradeDB] Adding dev_Network_Node_port to the Devices table"])
self.sql.execute("""
- ALTER TABLE "Devices" ADD "dev_Network_Node_port" INTEGER
+ ALTER TABLE "Devices" ADD "dev_Network_Node_port" INTEGER
""")
# dev_Icon column
dev_Icon_missing = self.sql.execute ("""
- SELECT COUNT(*) AS CNTREC FROM pragma_table_info('Devices') WHERE name='dev_Icon'
+ SELECT COUNT(*) AS CNTREC FROM pragma_table_info('Devices') WHERE name='dev_Icon'
""").fetchone()[0] == 0
if dev_Icon_missing :
mylog('verbose', ["[upgradeDB] Adding dev_Icon to the Devices table"])
self.sql.execute("""
- ALTER TABLE "Devices" ADD "dev_Icon" TEXT
+ ALTER TABLE "Devices" ADD "dev_Icon" TEXT
""")
@@ -263,61 +264,61 @@ class DB():
# Plugin state
sql_Plugins_Objects = """ CREATE TABLE IF NOT EXISTS Plugins_Objects(
- "Index" INTEGER,
- Plugin TEXT NOT NULL,
- Object_PrimaryID TEXT NOT NULL,
- Object_SecondaryID TEXT NOT NULL,
- DateTimeCreated TEXT NOT NULL,
- DateTimeChanged TEXT NOT NULL,
- Watched_Value1 TEXT NOT NULL,
- Watched_Value2 TEXT NOT NULL,
- Watched_Value3 TEXT NOT NULL,
- Watched_Value4 TEXT NOT NULL,
- Status TEXT NOT NULL,
- Extra TEXT NOT NULL,
- UserData TEXT NOT NULL,
- ForeignKey TEXT NOT NULL,
- PRIMARY KEY("Index" AUTOINCREMENT)
+ "Index" INTEGER,
+ Plugin TEXT NOT NULL,
+ Object_PrimaryID TEXT NOT NULL,
+ Object_SecondaryID TEXT NOT NULL,
+ DateTimeCreated TEXT NOT NULL,
+ DateTimeChanged TEXT NOT NULL,
+ Watched_Value1 TEXT NOT NULL,
+ Watched_Value2 TEXT NOT NULL,
+ Watched_Value3 TEXT NOT NULL,
+ Watched_Value4 TEXT NOT NULL,
+ Status TEXT NOT NULL,
+ Extra TEXT NOT NULL,
+ UserData TEXT NOT NULL,
+ ForeignKey TEXT NOT NULL,
+ PRIMARY KEY("Index" AUTOINCREMENT)
); """
self.sql.execute(sql_Plugins_Objects)
# Plugin execution results
sql_Plugins_Events = """ CREATE TABLE IF NOT EXISTS Plugins_Events(
- "Index" INTEGER,
- Plugin TEXT NOT NULL,
- Object_PrimaryID TEXT NOT NULL,
- Object_SecondaryID TEXT NOT NULL,
- DateTimeCreated TEXT NOT NULL,
- DateTimeChanged TEXT NOT NULL,
- Watched_Value1 TEXT NOT NULL,
- Watched_Value2 TEXT NOT NULL,
- Watched_Value3 TEXT NOT NULL,
- Watched_Value4 TEXT NOT NULL,
- Status TEXT NOT NULL,
- Extra TEXT NOT NULL,
- UserData TEXT NOT NULL,
- ForeignKey TEXT NOT NULL,
- PRIMARY KEY("Index" AUTOINCREMENT)
+ "Index" INTEGER,
+ Plugin TEXT NOT NULL,
+ Object_PrimaryID TEXT NOT NULL,
+ Object_SecondaryID TEXT NOT NULL,
+ DateTimeCreated TEXT NOT NULL,
+ DateTimeChanged TEXT NOT NULL,
+ Watched_Value1 TEXT NOT NULL,
+ Watched_Value2 TEXT NOT NULL,
+ Watched_Value3 TEXT NOT NULL,
+ Watched_Value4 TEXT NOT NULL,
+ Status TEXT NOT NULL,
+ Extra TEXT NOT NULL,
+ UserData TEXT NOT NULL,
+ ForeignKey TEXT NOT NULL,
+ PRIMARY KEY("Index" AUTOINCREMENT)
); """
self.sql.execute(sql_Plugins_Events)
# Plugin execution history
sql_Plugins_History = """ CREATE TABLE IF NOT EXISTS Plugins_History(
- "Index" INTEGER,
- Plugin TEXT NOT NULL,
- Object_PrimaryID TEXT NOT NULL,
- Object_SecondaryID TEXT NOT NULL,
- DateTimeCreated TEXT NOT NULL,
- DateTimeChanged TEXT NOT NULL,
- Watched_Value1 TEXT NOT NULL,
- Watched_Value2 TEXT NOT NULL,
- Watched_Value3 TEXT NOT NULL,
- Watched_Value4 TEXT NOT NULL,
- Status TEXT NOT NULL,
- Extra TEXT NOT NULL,
- UserData TEXT NOT NULL,
- ForeignKey TEXT NOT NULL,
- PRIMARY KEY("Index" AUTOINCREMENT)
+ "Index" INTEGER,
+ Plugin TEXT NOT NULL,
+ Object_PrimaryID TEXT NOT NULL,
+ Object_SecondaryID TEXT NOT NULL,
+ DateTimeCreated TEXT NOT NULL,
+ DateTimeChanged TEXT NOT NULL,
+ Watched_Value1 TEXT NOT NULL,
+ Watched_Value2 TEXT NOT NULL,
+ Watched_Value3 TEXT NOT NULL,
+ Watched_Value4 TEXT NOT NULL,
+ Status TEXT NOT NULL,
+ Extra TEXT NOT NULL,
+ UserData TEXT NOT NULL,
+ ForeignKey TEXT NOT NULL,
+ PRIMARY KEY("Index" AUTOINCREMENT)
); """
self.sql.execute(sql_Plugins_History)
@@ -328,12 +329,12 @@ class DB():
# Dynamically generated language strings
self.sql.execute("DROP TABLE IF EXISTS Plugins_Language_Strings;")
self.sql.execute(""" CREATE TABLE IF NOT EXISTS Plugins_Language_Strings(
- "Index" INTEGER,
- Language_Code TEXT NOT NULL,
- String_Key TEXT NOT NULL,
- String_Value TEXT NOT NULL,
- Extra TEXT NOT NULL,
- PRIMARY KEY("Index" AUTOINCREMENT)
+ "Index" INTEGER,
+ Language_Code TEXT NOT NULL,
+ String_Key TEXT NOT NULL,
+ String_Value TEXT NOT NULL,
+ Extra TEXT NOT NULL,
+ PRIMARY KEY("Index" AUTOINCREMENT)
); """)
self.commitDB()
@@ -355,6 +356,9 @@ class DB():
);
""")
+ # Init the AppEvent database table
+ AppEvent_obj(self)
+
# -------------------------------------------------------------------------
# DELETING OBSOLETE TABLES - to remove with updated db file after 1/1/2024
# -------------------------------------------------------------------------
diff --git a/pialert/device.py b/pialert/device.py
index 5f293444..2741ab09 100755
--- a/pialert/device.py
+++ b/pialert/device.py
@@ -194,6 +194,8 @@ def create_new_devices (db):
'{get_setting_value('NEWDEV_dev_Icon')}'
"""
+ # Bulk-inserting devices from the CurrentScan table as new devices in the table Devices ...
+ # ... with new device defaults and ignoring specidfied IPs and MACs)
sqlQuery = f"""INSERT OR IGNORE INTO Devices (dev_MAC, dev_name, dev_Vendor,
dev_LastIP, dev_FirstConnection, dev_LastConnection,
{newDevColumns})
diff --git a/pialert/plugin.py b/pialert/plugin.py
index d0ce49cd..6eeba48e 100755
--- a/pialert/plugin.py
+++ b/pialert/plugin.py
@@ -374,7 +374,7 @@ def execute_plugin(db, plugin, pluginsState = plugins_state() ):
pluginsState = process_plugin_events(db, plugin, pluginsState, sqlParams)
# update API endpoints
- update_api(db, False, ["plugins_events","plugins_objects", "plugins_history"])
+ update_api(db, False, ["plugins_events","plugins_objects", "plugins_history", "appevents"])
return pluginsState