From 20efb2eefde82e23a46d49a3d6a13ef3959489d7 Mon Sep 17 00:00:00 2001 From: CasVT Date: Fri, 23 Feb 2024 16:32:22 +0100 Subject: [PATCH] Added restart and shutdown buttons --- .vscode/launch.json | 10 ++++++++- MIND.py | 11 ++++++++-- backend/db.py | 18 ++++++++--------- frontend/api.py | 38 ++++++++++++++++++++++++++++++++++- frontend/static/css/admin.css | 12 ++++++++--- frontend/static/js/admin.js | 33 ++++++++++++++++++++++++++++++ frontend/templates/admin.html | 5 +++++ 7 files changed, 111 insertions(+), 16 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index d81e89d..1beab3c 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,11 +1,19 @@ { "configurations": [ + { + "name": "Current File", + "type": "debugpy", + "request": "launch", + "program": "${file}", + "justMyCode": true + }, { "name": "Main File", "type": "debugpy", "request": "launch", "python": "/bin/python3", - "program": "${workspaceFolder}/MIND.py" + "program": "${workspaceFolder}/MIND.py", + "justMyCode": true } ] } \ No newline at end of file diff --git a/MIND.py b/MIND.py index ff09dbf..70dd9d8 100644 --- a/MIND.py +++ b/MIND.py @@ -6,9 +6,10 @@ The main file where MIND is started from """ import logging -from os import makedirs, urandom +from os import execv, makedirs, urandom from os.path import dirname, isfile from shutil import move +from sys import argv from flask import Flask, render_template, request from waitress.server import create_server @@ -17,7 +18,8 @@ from werkzeug.middleware.dispatcher import DispatcherMiddleware from backend.db import DBConnection, ThreadedTaskDispatcher, close_db, setup_db from backend.helpers import check_python_version, folder_path from backend.reminders import ReminderHandler -from frontend.api import admin_api, admin_api_prefix, api, api_prefix +from frontend.api import (APIVariables, admin_api, admin_api_prefix, api, + api_prefix) from frontend.ui import UIVariables, ui HOST = '0.0.0.0' @@ -116,6 +118,7 @@ def MIND() -> None: port=PORT, threads=THREADS ) + APIVariables.server_instance = server logging.info(f'MIND running on http://{HOST}:{PORT}{URL_PREFIX}') # ================= server.run() @@ -123,6 +126,10 @@ def MIND() -> None: reminder_handler.stop_handling() + if APIVariables.restart: + logging.info('Restarting MIND') + execv(__file__, argv) + return if __name__ == "__main__": diff --git a/backend/db.py b/backend/db.py index 45b4a7a..d07ad17 100644 --- a/backend/db.py +++ b/backend/db.py @@ -23,8 +23,7 @@ class DB_Singleton(type): def __call__(cls, *args, **kwargs): i = f'{cls}{current_thread()}' if (i not in cls._instances - or cls._instances[i].closed): - logging.debug(f'Creating singleton instance: {i}') + or cls._instances[i].closed): cls._instances[i] = super(DB_Singleton, cls).__call__(*args, **kwargs) return cls._instances[i] @@ -34,33 +33,34 @@ class ThreadedTaskDispatcher(OldThreadedTaskDispatcher): super().handler_thread(thread_no) i = f'{DBConnection}{current_thread()}' if i in DB_Singleton._instances and not DB_Singleton._instances[i].closed: - logging.debug(f'Closing singleton instance: {i}') DB_Singleton._instances[i].close() return def shutdown(self, cancel_pending: bool = True, timeout: int = 5) -> bool: print() - logging.info('Shutting down MIND...') - super().shutdown(cancel_pending, timeout) - DBConnection(20.0).close() - return + logging.info('Shutting down MIND') + result = super().shutdown(cancel_pending, timeout) + return result class DBConnection(Connection, metaclass=DB_Singleton): file = '' def __init__(self, timeout: float) -> None: - logging.debug(f'Opening database connection for {current_thread()}') + logging.debug(f'Creating connection {self}') super().__init__(self.file, timeout=timeout) super().cursor().execute("PRAGMA foreign_keys = ON;") self.closed = False return def close(self) -> None: - logging.debug(f'Closing database connection for {current_thread()}') + logging.debug(f'Closing connection {self}') self.closed = True super().close() return + def __repr__(self) -> str: + return f'<{self.__class__.__name__}; {current_thread().name}; {id(self)}>' + def get_db(output_type: Union[Type[dict], Type[tuple]]=tuple): """Get a database cursor instance. Coupled to Flask's g. diff --git a/frontend/api.py b/frontend/api.py index a353c01..be832a4 100644 --- a/frontend/api.py +++ b/frontend/api.py @@ -4,10 +4,12 @@ import logging from dataclasses import dataclass from io import BytesIO from os import urandom +from threading import Timer from time import time as epoch_time -from typing import Any, Callable, Dict, Tuple +from typing import Any, Callable, Dict, Tuple, Union from flask import g, request, send_file +from waitress.server import BaseWSGIServer from backend.custom_exceptions import (AccessUnauthorized, APIKeyExpired, APIKeyInvalid, InvalidKeyValue, @@ -45,6 +47,19 @@ from frontend.input_validation import (AllowNewAccountsVariable, ColorVariable, # General variables and functions #=================== +class APIVariables: + server_instance: Union[BaseWSGIServer, None] = None + restart: bool = False + +def shutdown_server() -> None: + APIVariables.server_instance.close() + APIVariables.server_instance.task_dispatcher.shutdown() + APIVariables.server_instance._map.clear() + return + +shutdown_server_thread = Timer(1.0, shutdown_server) +shutdown_server_thread.name = "InternalStateHandler" + @dataclass class ApiKeyEntry: exp: int @@ -564,6 +579,27 @@ def api_get_static_reminder(inputs: Dict[str, Any], s_id: int): # Admin panel endpoints #=================== +@admin_api.route( + '/shutdown', + 'Shut down the application', + methods=['POST'] +) +@endpoint_wrapper +def api_shutdown(): + shutdown_server_thread.start() + return return_api({}) + +@admin_api.route( + '/restart', + 'Restart the application', + methods=['POST'] +) +@endpoint_wrapper +def api_restart(): + APIVariables.restart = True + shutdown_server_thread.start() + return return_api({}) + @api.route( '/settings', 'Get the admin settings', diff --git a/frontend/static/css/admin.css b/frontend/static/css/admin.css index e8eb719..eb208e9 100644 --- a/frontend/static/css/admin.css +++ b/frontend/static/css/admin.css @@ -145,16 +145,20 @@ h2 { .add-user-container, .database-container { + margin-top: 2rem; margin-bottom: 1rem; width: 100%; display: flex; justify-content: center; align-items: center; + gap: 2rem; + flex-wrap: wrap; } #add-user-button, -#download-db-button { - margin-top: 2rem; +#download-db-button, +#restart-button, +#shutdown-button { width: min(15rem, 100%); height: 2rem; @@ -166,7 +170,9 @@ h2 { box-shadow: var(--default-shadow); } -#download-db-button { +#download-db-button, +#restart-button, +#shutdown-button { height: unset; } diff --git a/frontend/static/js/admin.js b/frontend/static/js/admin.js index 39becc2..d132707 100644 --- a/frontend/static/js/admin.js +++ b/frontend/static/js/admin.js @@ -9,6 +9,11 @@ const user_inputs = { password: document.querySelector('#new-password-input') }; +const power_buttons = { + restart: document.querySelector('#restart-button'), + shutdown: document.querySelector('#shutdown-button') +}; + function checkLogin() { fetch(`${url_prefix}/api/auth/status?api_key=${api_key}`) .then(response => { @@ -175,6 +180,32 @@ function loadUsers() { }); }; +function restart_app() { + power_buttons.restart.innerText = 'Restarting...'; + fetch(`${url_prefix}/api/admin/restart?api_key=${api_key}`, { + method: "POST" + }) + .then(response => + setTimeout( + () => window.location.reload(), + 1000 + ) + ); +}; + +function shutdown_app() { + power_buttons.shutdown.innerText = 'Shutting down...'; + fetch(`${url_prefix}/api/admin/shutdown?api_key=${api_key}`, { + method: "POST" + }) + .then(response => + setTimeout( + () => window.location.reload(), + 1000 + ) + ); +}; + // code run on load checkLogin(); @@ -187,3 +218,5 @@ document.querySelector('#add-user-button').onclick = e => toggleAddUser(); document.querySelector('#add-user-form').action = 'javascript:addUser()'; document.querySelector('#download-db-button').onclick = e => window.location.href = `${url_prefix}/api/admin/database?api_key=${api_key}`; +power_buttons.restart.onclick = e => restart_app(); +power_buttons.shutdown.onclick = e => shutdown_app(); diff --git a/frontend/templates/admin.html b/frontend/templates/admin.html index 537bbfd..fcc8a31 100644 --- a/frontend/templates/admin.html +++ b/frontend/templates/admin.html @@ -123,6 +123,11 @@
+

Power

+
+ + +