Added restart and shutdown buttons

This commit is contained in:
CasVT
2024-02-23 16:32:22 +01:00
parent c6590f91dd
commit 20efb2eefd
7 changed files with 111 additions and 16 deletions

10
.vscode/launch.json vendored
View File

@@ -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
}
]
}

11
MIND.py
View File

@@ -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__":

View File

@@ -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.

View File

@@ -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',

View File

@@ -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;
}

View File

@@ -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();

View File

@@ -123,6 +123,11 @@
<div class="database-container">
<button id="download-db-button">Download Database</button>
</div>
<h2>Power</h2>
<div class="database-container">
<button id="restart-button">Restart</button>
<button id="shutdown-button">Shutdown</button>
</div>
</div>
</main>
</body>