From ac728f1cb49beb6ed6e1394080374979101583a0 Mon Sep 17 00:00:00 2001 From: Artem Synytsyn Date: Wed, 14 Feb 2024 15:25:06 +0200 Subject: [PATCH] Dev merge (#68) * Very various code revork and refactoring (#29) * Smoke try... * Fix problem with user name and icon * WIP: rework, bug fixes, renaming, etc * WIP: refactor, stage 0 * WIP: refactor, stage 1 * WIP: refactor, stage 2 * WIP: refactor, web API separated * API extended * WIP: AI-generated stuff for user table just for test:) * Web API extended for devices * Fix and rework auth * Some hacks about development described * Add columns to users table * Small fixes: delete user and device menu button * Config and logging refactored * Latest key API endpoind extended * Load config from file + API + Settings page(WIP) * Create user API fix: return code for existing user * Add functionality to users page * Fix add user * Add latest key time * Basic slack notifyer implemented * Fix slack warning * Devices pages added * Added "Delete device" functionality + some fixes * Add settings JSON editor * Pylint + Autopep8 fixes --------- Co-authored-by: Anna Deeva * Delete file, forgotten from previous commit * Fixed docker file and add ci for build docker images * change to correct tag * Implement build docker image with correct tags * Implement creating database on start if not exist * Flash RFID readers from admin panel (#42) * Automatic RFID reader code reworked and refined. Also some bug fixes etc... * Fix pylint * Fix problem with settings editor * Docker configuration changed - Now config file is specified in dockerfile - Readme updated * Improve plugin subsystem * Now we have plugin manager, which handles plugin loading * Crash in plugin load does not lead to crash in app * Plugins has separate branch in config * Plugin has its own directory * Fix UUID generator Built-in UUID generator does not work in some cases * Fix AddUser() bug * Some changes to login and database create logic - If no admin users are present force to go to init_app page - Only one admin user is allowed(to prevent to anyone to register) * UI fixes (#53) * Make navigation sticky * Add time and date selectors * Add paddings * Fix buttons with on smaller screens * Docker-related stuff changed Mainly because Docker worked poorly with automated reader device flasher, so: - ttyUSB device made accessed in container - reader firmware now automatically downloaded from git ... other changes * Fix add user functionality * Some fixes (#57) * Fix add user functionality * Set wifi credentials in settings * Fix pylint * Add image and other fix (#58) * Fix add user functionality * Set wifi credentials in settings * Fix pylint * Add image and some fixes * Another fixes * Fix add user functionality * Set wifi credentials in settings * Fix pylint * Add image and some fixes * Fix log representation issues - Remove links in logs - Device name instead device ID - Heh, remove checkbox on init page with DB restore:) * Several changes, revealed during smoke test on final stages (#61) * Several changes, revealed during smoke test on final stages + documentation start * Added instructions steps * Contributors added * Tool door split (#65) * Several changes, revealed during smoke test on final stages + documentation start * Added instructions steps * Contributors added * Added tool and door devices split * Latest activity added (#66) * Slack notifier separately notify door and tool (#67) * Slack notifier separately notify door and tool * Fix pylint --------- Co-authored-by: Anna Deeva Co-authored-by: vovastelmashchuk Co-authored-by: Volodymyr Stelmashchuk Co-authored-by: Anna Deeva <37623333+temhota@users.noreply.github.com> --- app/config_default.json | 3 +- app/models/user.py | 26 +++++- app/plugins/slack_notifier/slack_notifier.py | 87 ++++++++++++++++++-- app/static/js/devices.js | 13 +-- app/templates/layout.html | 3 + app/templates/prismo/devices.html | 6 +- app/templates/prismo/users.html | 1 + app/utils/fimware_updater.py | 22 +++-- 8 files changed, 136 insertions(+), 25 deletions(-) diff --git a/app/config_default.json b/app/config_default.json index 3e6c171..1e68910 100644 --- a/app/config_default.json +++ b/app/config_default.json @@ -19,7 +19,8 @@ ], "PLUGINS": { "slack_notifier": { - "SLACK_CHANNEL": "#prismo-debug", + "SLACK_TOOL_CHANNEL": "#prismo-debug", + "SLACK_DOOR_CHANNEL": "#prismo-door-channel", "SLACK_TOKEN": "xoxb-this-is-not-areal-slack-token" } }, diff --git a/app/models/user.py b/app/models/user.py index b3e3161..fa452f7 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -75,17 +75,38 @@ def get_permissions(cls, user_key=None): # Fetch user data and permissions user_data = [] # Filter by user_key if provided + # Here we also add "Latest Activity" column, for reporting latest any tool/door use by user if user_key: cursor.execute( - "SELECT users.name, users.key FROM users WHERE users.key = ?", + """ + SELECT users.name, users.key, + (SELECT operation_time + FROM event_logs + WHERE user_key = users.key + ORDER BY operation_time DESC + LIMIT 1) AS latest_activity + FROM users + WHERE users.key = ? + """, (user_key,), ) else: - cursor.execute("SELECT users.name, users.key FROM users") + cursor.execute( + """ + SELECT users.name, users.key, + (SELECT operation_time + FROM event_logs + WHERE user_key = users.key + ORDER BY operation_time DESC + LIMIT 1) AS latest_activity + FROM users + """ + ) for row in cursor.fetchall(): user_name = row[0] user_key = row[1] + latest_activity = row[2] # Get device permissions for the current user device_permissions = [] @@ -118,6 +139,7 @@ def get_permissions(cls, user_key=None): "user_name": user_name, "user_key": user_key, "permissions": device_permissions, + "latest_activity": latest_activity, } user_data.append(user_record) diff --git a/app/plugins/slack_notifier/slack_notifier.py b/app/plugins/slack_notifier/slack_notifier.py index ddcc63f..b9051d4 100644 --- a/app/plugins/slack_notifier/slack_notifier.py +++ b/app/plugins/slack_notifier/slack_notifier.py @@ -47,6 +47,48 @@ def unlock_message_block_constructor(tool, user): }] +def door_message_block_constructor(door, user): + return [ + { + "type": "rich_text", + "elements": [ + { + "type": "rich_text_section", + "elements": [ + { + "type": "emoji", + "name": "door", + "unicode": "1f6aa" + }, + { + "type": "text", + "text": " " + }, + { + "type": "text", + "text": "%s" % user, + "style": { + "bold": True + } + }, + { + "type": "text", + "text": " entered through the " + }, + { + "type": "text", + "text": "%s" % door, + "style": { + "bold": True + } + } + ] + } + ] + } + ] + + class SlackNotifierPlugin: def __init__(self, app_context): # Configure the Slack client with your token @@ -102,19 +144,52 @@ def get_device_name(self, device_id): return None + def get_device_type(self, device_id): + """ + Get device name based on its ID + """ + connection = sqlite3.connect(self.db_uri) + cursor = connection.cursor() + + cursor.execute("SELECT type from devices WHERE id = ?", + (device_id,), + ) + connection.commit() + result = cursor.fetchone() + + connection.close() + if result: + return result[0] + + return None + def access_log_entry_added(self, event): try: if event["operation"] == "unlock": user_name = self.get_user_name(event["user_key"]) device_name = self.get_device_name(event["device_id"]) + device_type = self.get_device_type(event["device_id"]) self.logger.info("Access log entry added") self.logger.info("User name: %s", user_name) - self.logger.info("Device name: %s", device_name) - text_message = "🔓 * %s Tool was unlocked* by %s" % (device_name, user_name) - blocks = unlock_message_block_constructor(device_name, user_name) - self.slack_app.client.chat_postMessage(channel=self.config["SLACK_CHANNEL"], - text=text_message, - blocks=blocks) + if device_type == "tool": + self.logger.info("Device name: %s", device_name) + text_message = "🔓 * %s Tool was unlocked* by %s" % (device_name, user_name) + blocks = unlock_message_block_constructor(device_name, user_name) + self.slack_app.client.chat_postMessage( + channel=self.config["SLACK_TOOL_CHANNEL"], + text=text_message, + blocks=blocks) + elif device_type == "door": + self.logger.info("Door opened: %s", device_name) + text_message = "🔓 * %s Door opened by * by %s" % (device_name, user_name) + blocks = door_message_block_constructor(device_name, user_name) + self.slack_app.client.chat_postMessage( + channel=self.config["SLACK_DOOR_CHANNEL"], + text=text_message, + blocks=blocks) + else: + self.logger.error("Unknown reader type! %s", device_type) + except Exception as e: self.logger.error("Error in SlackNotifierPlugin.access_log_entry_added: %s", e) raise e diff --git a/app/static/js/devices.js b/app/static/js/devices.js index 86cea70..1d9e7af 100644 --- a/app/static/js/devices.js +++ b/app/static/js/devices.js @@ -1,8 +1,8 @@ // Global: Device ID, which is pending for update let deviceIDForUpdate = null; - +let deviceTypeForUpdate = null; function flashFirmware() { - console.log("Flashing device: ", deviceIDForUpdate); + console.log("Flashing device: ", deviceIDForUpdate, deviceTypeForUpdate); const socket = new WebSocket("ws://" + location.host + "/reader_flasher"); const logContainer = document.getElementById("log-container"); @@ -12,7 +12,7 @@ function flashFirmware() { console.log( "WebSocket connection established, send device id for flashing", ); - socket.send(deviceIDForUpdate); + socket.send(JSON.stringify({"device_id":deviceIDForUpdate, "device_type": deviceTypeForUpdate})); }); function log(data) { const obj = JSON.parse(data); @@ -58,7 +58,7 @@ function generateAccordionItems(devices) {
- +
@@ -86,14 +86,15 @@ function generateUUID() { return uuid } -function addDevice(deviceName) { +function addDevice(deviceName, isDeviceTool) { // Generate random UUID for device ID const deviceId = generateUUID(); // Prepare device data const deviceData = { device_name: deviceName, device_id: deviceId, - device_type: "tool", + // Looks like there is a bug in bootstrap toogle 5, can not just get value of toggle:( + device_type: (isDeviceTool ? "tool" : "door"), }; // Make API call to add device fetch("/api/devices", { diff --git a/app/templates/layout.html b/app/templates/layout.html index 531a1be..cfb6035 100644 --- a/app/templates/layout.html +++ b/app/templates/layout.html @@ -5,8 +5,11 @@ Prismo + + + diff --git a/app/templates/prismo/devices.html b/app/templates/prismo/devices.html index 7975795..5c33b84 100644 --- a/app/templates/prismo/devices.html +++ b/app/templates/prismo/devices.html @@ -10,10 +10,12 @@ - + + diff --git a/app/templates/prismo/users.html b/app/templates/prismo/users.html index c3ffca2..c897055 100644 --- a/app/templates/prismo/users.html +++ b/app/templates/prismo/users.html @@ -106,6 +106,7 @@
Add last used RFID card as new user: Delete' } }) diff --git a/app/utils/fimware_updater.py b/app/utils/fimware_updater.py index d05fb27..853239b 100644 --- a/app/utils/fimware_updater.py +++ b/app/utils/fimware_updater.py @@ -13,7 +13,7 @@ from flask import current_app as app -def update_firmware_full(socket, device_id): +def update_firmware_full(socket, device_id, device_type): """Run full firmware update, by running firmware updater script This function runs firmware updater script, which erases memory, flashes @@ -26,6 +26,7 @@ def update_firmware_full(socket, device_id): socket -- communication websocket device_id -- device ID string, MUST be UUID string in format 550e8400-e29b-41d4-a716-446655440000 + device_type -- type of device, should be "tool" or "door" Returns: bool -- True if firmware update was successful, False otherwise @@ -43,7 +44,8 @@ def update_firmware_full(socket, device_id): run_environment["HOST_WIFI_SSID"] = app.config["PRISMO"]["WIFI_SSID"] run_environment["HOST_WIFI_PASSWORD"] = app.config["PRISMO"]["WIFI_PASSWORD"] script_cwd = script_path.parent - process = subprocess.Popen([script_path, device_id], cwd=script_cwd, + process = subprocess.Popen([script_path, device_id, device_type], cwd=script_cwd, + stdout=subprocess.PIPE, env=run_environment) except FileNotFoundError: data["text"] = "Cannot find firmware update script" @@ -87,19 +89,23 @@ def update_firmware_full(socket, device_id): def firmware_updater_route(websocket): device_id = None + device_type = None while device_id is None: message = websocket.receive() if message: try: + json_data = json.loads(message) # Check if we have received valid uuid string. - uuid.UUID(message) - device_id = message + uuid.UUID(json_data["device_id"]) + device_id = json_data["device_id"] + device_type = json_data["device_type"] + except ValueError: - print("No uuid string was received:", message) + app.logger.error("Wrong message received: %s", message) - if device_id is not None: - update_result = update_firmware_full(websocket, device_id) + if device_id is not None and device_type is not None: + update_result = update_firmware_full(websocket, device_id, device_type) app.logger.warning("Update result: %s", update_result) else: - app.logger.warning("No device id was received") + app.logger.warning("No device id or device type was received") app.logger.warning("Close websocket")