Improved logging setup with debug log files

This commit is contained in:
CasVT
2024-03-03 23:19:04 +01:00
parent 622915c5ad
commit 3b57d01596
16 changed files with 226 additions and 78 deletions

View File

@@ -5,12 +5,11 @@
The main file where MIND is started from
"""
import logging
from sys import argv
from backend.db import setup_db, setup_db_location
from backend.helpers import check_python_version
from backend.logging import setup_logging
from backend.logging import LOGGER, setup_logging
from backend.reminders import ReminderHandler
from backend.server import SERVER, handle_flags
from backend.settings import get_setting
@@ -29,7 +28,7 @@ def MIND() -> None:
"""The main function of MIND
"""
setup_logging()
logging.info('Starting up MIND')
LOGGER.info('Starting up MIND')
if not check_python_version():
exit(1)

View File

@@ -8,13 +8,13 @@ All custom exceptions are defined here
Note: Not all CE's inherit from CustomException.
"""
import logging
from typing import Any, Dict
from backend.logging import LOGGER
class CustomException(Exception):
def __init__(self, e=None) -> None:
logging.warning(self.__doc__)
LOGGER.warning(self.__doc__)
super().__init__(e)
return
@@ -29,7 +29,7 @@ class UsernameInvalid(Exception):
def __init__(self, username: str):
self.username = username
super().__init__(self.username)
logging.warning(
LOGGER.warning(
f'The username contains invalid characters: {username}'
)
return
@@ -58,7 +58,7 @@ class NotificationServiceInUse(Exception):
def __init__(self, type: str=''):
self.type = type
super().__init__(self.type)
logging.warning(
LOGGER.warning(
f'The notification is wished to be deleted but a reminder of type {type} is still using it'
)
return
@@ -80,7 +80,7 @@ class KeyNotFound(Exception):
def __init__(self, key: str=''):
self.key = key
super().__init__(self.key)
logging.warning(
LOGGER.warning(
"This key was not found in the API request,"
+ f" eventhough it's required: {key}"
)
@@ -100,7 +100,7 @@ class InvalidKeyValue(Exception):
self.key = key
self.value = value
super().__init__(self.key)
logging.warning(
LOGGER.warning(
'This key in the API request has an invalid value: ' +
f'{key} = {value}'
)
@@ -132,3 +132,7 @@ class NewAccountsNotAllowed(CustomException):
class InvalidDatabaseFile(CustomException):
"""The uploaded database file is invalid or not supported"""
api_response = {'error': 'InvalidDatabaseFile', 'result': {}, 'code': 400}
class LogFileNotFound(CustomException):
"""No log file was found"""
api_response = {'error': 'LogFileNotFound', 'result': {}, 'code': 404}

View File

@@ -4,7 +4,6 @@
Setting up and interacting with the database.
"""
import logging
from datetime import datetime
from os import makedirs, remove
from os.path import dirname, isfile, join
@@ -19,7 +18,7 @@ from flask import g
from backend.custom_exceptions import (AccessUnauthorized, InvalidDatabaseFile,
UserNotFound)
from backend.helpers import RestartVars, folder_path
from backend.logging import set_log_level
from backend.logging import LOGGER, set_log_level
DB_FILENAME = 'db', 'MIND.db'
__DATABASE_VERSION__ = 10
@@ -39,14 +38,14 @@ class DBConnection(Connection, metaclass=DB_Singleton):
file = ''
def __init__(self, timeout: float) -> None:
logging.debug(f'Creating connection {self}')
LOGGER.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 connection {self}')
LOGGER.debug(f'Closing connection {self}')
self.closed = True
super().close()
return
@@ -61,7 +60,6 @@ def setup_db_location() -> None:
move(folder_path('db', 'Noted.db'), folder_path(*DB_FILENAME))
db_location = folder_path(*DB_FILENAME)
logging.debug(f'Database location: {db_location}')
makedirs(dirname(db_location), exist_ok=True)
DBConnection.file = db_location
@@ -111,7 +109,7 @@ def migrate_db(current_db_version: int) -> None:
Migrate a MIND database from it's current version
to the newest version supported by the MIND version installed.
"""
logging.info('Migrating database to newer version...')
LOGGER.info('Migrating database to newer version...')
cursor = get_db()
if current_db_version == 1:
# V1 -> V2
@@ -394,11 +392,11 @@ def setup_db() -> None:
)
)
set_log_level(get_setting('log_level'))
set_log_level(get_setting('log_level'), clear_file=False)
current_db_version = get_setting('database_version')
if current_db_version < __DATABASE_VERSION__:
logging.debug(
LOGGER.debug(
f'Database migration: {current_db_version} -> {__DATABASE_VERSION__}'
)
migrate_db(current_db_version)
@@ -458,7 +456,7 @@ def import_db(
Raises:
InvalidDatabaseFile: The new database file is invalid or unsupported.
"""
logging.info(f'Importing new database; {copy_hosting_settings=}')
LOGGER.info(f'Importing new database; {copy_hosting_settings=}')
try:
cursor = Connection(new_db_file, timeout=20.0).cursor()
@@ -469,7 +467,7 @@ def import_db(
raise InvalidDatabaseFile
except (OperationalError, InvalidDatabaseFile):
logging.error('Uploaded database is not a MIND database file')
LOGGER.error('Uploaded database is not a MIND database file')
cursor.connection.close()
revert_db_import(
swap=False,
@@ -478,7 +476,7 @@ def import_db(
raise InvalidDatabaseFile
if database_version > __DATABASE_VERSION__:
logging.error('Uploaded database is higher version than this MIND installation can support')
LOGGER.error('Uploaded database is higher version than this MIND installation can support')
revert_db_import(
swap=False,
imported_db_file=new_db_file

View File

@@ -4,7 +4,6 @@
General functions
"""
import logging
from enum import Enum
from os.path import abspath, dirname, join
from sys import version_info
@@ -29,7 +28,9 @@ def check_python_version() -> bool:
bool: Whether or not the python version is version 3.8 or above or not.
"""
if not (version_info.major == 3 and version_info.minor >= 8):
logging.critical(
from backend.logging import LOGGER
LOGGER.critical(
'The minimum python version required is python3.8 ' +
'(currently ' + str(version_info.major) + '.' + str(version_info.minor) + '.' + str(version_info.micro) + ').'
)

View File

@@ -1,27 +1,138 @@
#-*- coding: utf-8 -*-
import logging
import logging.config
from os.path import exists
from typing import Any
from backend.helpers import folder_path
class InfoOnlyFilter(logging.Filter):
def filter(self, record: logging.LogRecord) -> bool:
return record.levelno == logging.INFO
class DebuggingOnlyFilter(logging.Filter):
def filter(self, record: logging.LogRecord) -> bool:
return LOGGER.level == logging.DEBUG
class ErrorColorFormatter(logging.Formatter):
def format(self, record: logging.LogRecord) -> Any:
result = super().format(record)
return f'\033[1;31:40m{result}\033[0m'
LOGGER_NAME = "MIND"
LOGGER_DEBUG_FILENAME = "MIND_debug.log"
LOGGER = logging.getLogger(LOGGER_NAME)
LOGGING_CONFIG = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"simple": {
"format": "[%(asctime)s][%(levelname)s] %(message)s",
"datefmt": "%H:%M:%S"
},
"simple_red": {
"()": ErrorColorFormatter,
"format": "[%(asctime)s][%(levelname)s] %(message)s",
"datefmt": "%H:%M:%S"
},
"detailed": {
"format": "%(asctime)s | %(threadName)s | %(filename)sL%(lineno)s | %(levelname)s | %(message)s",
"datefmt": "%Y-%m-%dT%H:%M:%S%z",
}
},
"filters": {
"only_info": {
"()": InfoOnlyFilter
},
"only_if_debugging": {
"()": DebuggingOnlyFilter
}
},
"handlers": {
"console_error": {
"class": "logging.StreamHandler",
"level": "WARNING",
"formatter": "simple_red",
"stream": "ext://sys.stderr"
},
"console": {
"class": "logging.StreamHandler",
"level": "INFO",
"formatter": "simple",
"filters": ["only_info"],
"stream": "ext://sys.stdout"
},
"debug_file": {
"class": "logging.StreamHandler",
"level": "DEBUG",
"formatter": "detailed",
"filters": ["only_if_debugging"],
"stream": ""
}
},
"loggers": {
LOGGER_NAME: {
"level": "INFO"
}
},
"root": {
"level": "DEBUG",
"handlers": [
"console",
"console_error",
"debug_file"
]
}
}
def setup_logging() -> None:
"Setup the basic config of the logging module"
logging.basicConfig(
level=logging.INFO,
format='[%(asctime)s][%(threadName)s][%(levelname)s] %(message)s',
datefmt='%Y-%m-%d %H:%M:%S',
force=True
)
logging.config.dictConfig(LOGGING_CONFIG)
return
def set_log_level(level: int) -> None:
def get_debug_log_filepath() -> str:
"""
Get the filepath to the debug logging file.
Not in a global variable to avoid unnecessary computation.
"""
return folder_path(LOGGER_DEBUG_FILENAME)
def set_log_level(
level: int,
clear_file: bool = True
) -> None:
"""Change the logging level
Args:
level (int): The level to set the logging to.
Should be a logging level, like `logging.INFO` or `logging.DEBUG`.
clear_file (bool, optional): Empty the debug logging file.
Defaults to True.
"""
logging.debug(f'Setting logging level: {level}')
logging.getLogger().setLevel(
level=level
)
LOGGER.debug(f'Setting logging level: {level}')
LOGGER.setLevel(level)
if level == logging.DEBUG:
stream_handler = logging.getLogger().handlers[
LOGGING_CONFIG["root"]["handlers"].index('debug_file')
]
file = get_debug_log_filepath()
if clear_file:
if exists(file):
open(file, "w").close()
else:
open(file, "x").close()
stream_handler.setStream(
open(file, "a")
)
return

View File

@@ -1,6 +1,5 @@
#-*- coding: utf-8 -*-
import logging
from re import compile
from typing import Dict, List, Optional, Union
@@ -10,6 +9,7 @@ from backend.custom_exceptions import (NotificationServiceInUse,
NotificationServiceNotFound)
from backend.db import get_db
from backend.helpers import when_not_none
from backend.logging import LOGGER
remove_named_groups = compile(r'(?<=\()\?P<\w+>')
@@ -220,7 +220,7 @@ class NotificationService:
Returns:
dict: The new info about the service
"""
logging.info(f'Updating notification service {self.id}: {title=}, {url=}')
LOGGER.info(f'Updating notification service {self.id}: {title=}, {url=}')
# Get current data and update it with new values
data = self.get()
@@ -253,7 +253,7 @@ class NotificationService:
Raises:
NotificationServiceInUse: The service is still used by a reminder
"""
logging.info(f'Deleting notification service {self.id}')
LOGGER.info(f'Deleting notification service {self.id}')
# Check if no reminders exist with this service
cursor = get_db()
@@ -344,7 +344,7 @@ class NotificationServices:
Returns:
NotificationService: The instance representing the new service
"""
logging.info(f'Adding notification service with {title=}, {url=}')
LOGGER.info(f'Adding notification service with {title=}, {url=}')
new_id = get_db().execute("""
INSERT INTO notification_services(user_id, title, url)
@@ -364,7 +364,7 @@ class NotificationServices:
Args:
url (str): The Apprise URL to use to send the test notification
"""
logging.info(f'Testing service with {url=}')
LOGGER.info(f'Testing service with {url=}')
a = Apprise()
a.add(url)
a.notify(title='MIND: Test title', body='MIND: Test body')

View File

@@ -2,7 +2,6 @@
from __future__ import annotations
import logging
from datetime import datetime
from sqlite3 import IntegrityError
from threading import Timer
@@ -18,6 +17,7 @@ from backend.custom_exceptions import (InvalidKeyValue, InvalidTime,
from backend.db import get_db
from backend.helpers import (RepeatQuantity, Singleton, SortingMethod,
search_filter, when_not_none)
from backend.logging import LOGGER
if TYPE_CHECKING:
from flask.ctx import AppContext
@@ -98,7 +98,7 @@ def _find_next_time(
one_to_go = False
result = int(new_time.timestamp())
logging.debug(
LOGGER.debug(
f'{original_time=}, {current_time=} ' +
f'and interval of {repeat_interval} {repeat_quantity} ' +
f'leads to {result}'
@@ -237,7 +237,7 @@ class Reminder:
Returns:
dict: The new reminder info.
"""
logging.info(
LOGGER.info(
f'Updating notification service {self.id}: '
+ f'{title=}, {time=}, {notification_services=}, {text=}, '
+ f'{repeat_quantity=}, {repeat_interval=}, {weekdays=}, {color=}'
@@ -375,7 +375,7 @@ class Reminder:
def delete(self) -> None:
"""Delete the reminder
"""
logging.info(f'Deleting reminder {self.id}')
LOGGER.info(f'Deleting reminder {self.id}')
get_db().execute("DELETE FROM reminders WHERE id = ?", (self.id,))
ReminderHandler().find_next_reminder()
return
@@ -518,7 +518,7 @@ class Reminders:
Returns:
dict: The info about the reminder.
"""
logging.info(
LOGGER.info(
f'Adding reminder with {title=}, {time=}, {notification_services=}, '
+ f'{text=}, {repeat_quantity=}, {repeat_interval=}, {weekdays=}, {color=}'
)
@@ -636,7 +636,7 @@ class Reminders:
text (str, optional): The body of the reminder.
Defaults to ''.
"""
logging.info(f'Testing reminder with {title=}, {notification_services=}, {text=}')
LOGGER.info(f'Testing reminder with {title=}, {notification_services=}, {text=}')
a = Apprise()
cursor = get_db(dict)

View File

@@ -2,7 +2,6 @@
from __future__ import annotations
import logging
from os import execv, urandom
from sys import argv
from threading import Timer, current_thread
@@ -15,6 +14,7 @@ from werkzeug.middleware.dispatcher import DispatcherMiddleware
from backend.db import DB_Singleton, DBConnection, close_db, revert_db_import
from backend.helpers import RestartVars, Singleton, folder_path
from backend.logging import LOGGER
from backend.settings import restore_hosting_settings
if TYPE_CHECKING:
@@ -32,8 +32,9 @@ class ThreadedTaskDispatcher(TTD):
def shutdown(self, cancel_pending: bool = True, timeout: int = 5) -> bool:
print()
logging.info('Shutting down MIND')
LOGGER.info('Shutting down MIND')
result = super().shutdown(cancel_pending, timeout)
DBConnection(timeout=20.0).close()
return result
@@ -153,7 +154,7 @@ class Server(metaclass=Singleton):
port (int): The port to listen on.
"""
self.server = self.__create_waitress_server(host, port)
logging.info(f'MIND running on http://{host}:{port}{self.url_prefix}')
LOGGER.info(f'MIND running on http://{host}:{port}{self.url_prefix}')
self.server.run()
return
@@ -207,21 +208,21 @@ class Server(metaclass=Singleton):
if self.handle_flags:
handle_flags_pre_restart(flag)
logging.info('Restarting MIND')
LOGGER.info('Restarting MIND')
from MIND import __file__ as mind_file
execv(folder_path(mind_file), [argv[0], *self.restart_args])
def __revert_db(self) -> None:
"""Revert database import and restart.
"""
logging.warning(f'Timer for database import expired; reverting back to original file')
LOGGER.warning(f'Timer for database import expired; reverting back to original file')
self.restart(handle_flags=True)
return
def __revert_hosting(self) -> None:
"""Revert the hosting changes.
"""
logging.warning(f'Timer for hosting changes expired; reverting back to original settings')
LOGGER.warning(f'Timer for hosting changes expired; reverting back to original settings')
self.restart(handle_flags=True)
return
@@ -236,11 +237,11 @@ def handle_flags(flag: Union[None, str]) -> None:
flag (Union[None, str]): The flag or `None` if there is no flag set.
"""
if flag == RestartVars.DB_IMPORT:
logging.info('Starting timer for database import')
LOGGER.info('Starting timer for database import')
SERVER.revert_db_timer.start()
elif flag == RestartVars.HOST_CHANGE:
logging.info('Starting timer for hosting changes')
LOGGER.info('Starting timer for hosting changes')
SERVER.revert_hosting_timer.start()
return

View File

@@ -1,6 +1,5 @@
#-*- coding: utf-8 -*-
import logging
from sqlite3 import IntegrityError
from typing import List, Optional, Union
@@ -10,6 +9,7 @@ from backend.custom_exceptions import (NotificationServiceNotFound,
ReminderNotFound)
from backend.db import get_db
from backend.helpers import TimelessSortingMethod, search_filter
from backend.logging import LOGGER
class StaticReminder:
@@ -108,7 +108,7 @@ class StaticReminder:
Returns:
dict: The new static reminder info
"""
logging.info(
LOGGER.info(
f'Updating static reminder {self.id}: '
+ f'{title=}, {notification_services=}, {text=}, {color=}'
)
@@ -168,7 +168,7 @@ class StaticReminder:
def delete(self) -> None:
"""Delete the static reminder
"""
logging.info(f'Deleting static reminder {self.id}')
LOGGER.info(f'Deleting static reminder {self.id}')
get_db().execute("DELETE FROM static_reminders WHERE id = ?", (self.id,))
return
@@ -281,7 +281,7 @@ class StaticReminders:
Returns:
StaticReminder: The info about the static reminder
"""
logging.info(
LOGGER.info(
f'Adding static reminder with {title=}, {notification_services=}, {text=}, {color=}'
)
@@ -324,7 +324,7 @@ class StaticReminders:
Raises:
ReminderNotFound: The static reminder with the given id was not found
"""
logging.info(f'Triggering static reminder {id}')
LOGGER.info(f'Triggering static reminder {id}')
cursor = get_db(dict)
reminder = cursor.execute("""
SELECT title, text

View File

@@ -1,6 +1,5 @@
#-*- coding: utf-8 -*-
import logging
from sqlite3 import IntegrityError
from typing import List, Optional, Union
@@ -8,6 +7,7 @@ from backend.custom_exceptions import (NotificationServiceNotFound,
TemplateNotFound)
from backend.db import get_db
from backend.helpers import TimelessSortingMethod, search_filter
from backend.logging import LOGGER
class Template:
@@ -104,7 +104,7 @@ class Template:
Returns:
dict: The new template info
"""
logging.info(
LOGGER.info(
f'Updating template {self.id}: '
+ f'{title=}, {notification_services=}, {text=}, {color=}'
)
@@ -162,7 +162,7 @@ class Template:
def delete(self) -> None:
"""Delete the template
"""
logging.info(f'Deleting template {self.id}')
LOGGER.info(f'Deleting template {self.id}')
get_db().execute("DELETE FROM templates WHERE id = ?;", (self.id,))
return
@@ -275,7 +275,7 @@ class Templates:
Returns:
Template: The info about the template
"""
logging.info(
LOGGER.info(
f'Adding template with {title=}, {notification_services=}, {text=}, {color=}'
)

View File

@@ -1,12 +1,12 @@
#-*- coding: utf-8 -*-
import logging
from typing import List
from backend.custom_exceptions import (AccessUnauthorized,
NewAccountsNotAllowed, UsernameInvalid,
UsernameTaken, UserNotFound)
from backend.db import get_db
from backend.logging import LOGGER
from backend.notification_service import NotificationServices
from backend.reminders import Reminders
from backend.security import generate_salt_hash, get_hash
@@ -97,7 +97,7 @@ class User:
"UPDATE users SET hash = ? WHERE id = ?",
(hash_password, self.user_id)
)
logging.info(f'The user {self.username} ({self.user_id}) changed their password')
LOGGER.info(f'The user {self.username} ({self.user_id}) changed their password')
return
def delete(self) -> None:
@@ -106,7 +106,7 @@ class User:
if self.username == 'admin':
raise UserNotFound
logging.info(f'Deleting the user {self.username} ({self.user_id})')
LOGGER.info(f'Deleting the user {self.username} ({self.user_id})')
cursor = get_db()
cursor.execute(
@@ -141,7 +141,7 @@ class Users:
Raises:
UsernameInvalid: The username is not valid
"""
logging.debug(f'Checking the username {username}')
LOGGER.debug(f'Checking the username {username}')
if username in ONEPASS_INVALID_USERNAMES or username.isdigit():
raise UsernameInvalid(username)
if list(filter(lambda c: not c in ONEPASS_USERNAME_CHARACTERS, username)):
@@ -173,7 +173,7 @@ class Users:
Returns:
int: The id of the new user. User registered successful
"""
logging.info(f'Registering user with username {username}')
LOGGER.info(f'Registering user with username {username}')
if not from_admin and not get_setting('allow_new_accounts'):
raise NewAccountsNotAllowed
@@ -200,7 +200,7 @@ class Users:
(username, salt, hashed_password)
).lastrowid
logging.debug(f'Newly registered user has id {user_id}')
LOGGER.debug(f'Newly registered user has id {user_id}')
return user_id
def get_all(self) -> List[dict]:

View File

@@ -2,12 +2,11 @@
from __future__ import annotations
import logging
from dataclasses import dataclass
from datetime import datetime
from io import BytesIO
from os import remove, urandom
from os.path import basename
from os.path import basename, exists
from time import time as epoch_time
from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Tuple
@@ -16,7 +15,8 @@ from flask import g, request, send_file
from backend.custom_exceptions import (AccessUnauthorized, APIKeyExpired,
APIKeyInvalid, InvalidDatabaseFile,
InvalidKeyValue, InvalidTime,
KeyNotFound, NewAccountsNotAllowed,
KeyNotFound, LogFileNotFound,
NewAccountsNotAllowed,
NotificationServiceInUse,
NotificationServiceNotFound,
ReminderNotFound, TemplateNotFound,
@@ -24,6 +24,7 @@ from backend.custom_exceptions import (AccessUnauthorized, APIKeyExpired,
UserNotFound)
from backend.db import get_db, import_db, revert_db_import
from backend.helpers import RestartVars, folder_path
from backend.logging import LOGGER, get_debug_log_filepath
from backend.notification_service import get_apprise_services
from backend.server import SERVER
from backend.settings import (backup_hosting_settings, get_admin_settings,
@@ -136,7 +137,8 @@ def endpoint_wrapper(method: Callable) -> Callable:
AccessUnauthorized, APIKeyExpired,
APIKeyInvalid, InvalidDatabaseFile,
InvalidKeyValue, InvalidTime,
KeyNotFound, NewAccountsNotAllowed,
KeyNotFound, LogFileNotFound,
NewAccountsNotAllowed,
NotificationServiceInUse,
NotificationServiceNotFound,
ReminderNotFound, TemplateNotFound,
@@ -170,12 +172,12 @@ def api_login(inputs: Dict[str, str]):
# Login successful
if user.admin and SERVER.revert_db_timer.is_alive():
logging.info('Timer for database import diffused')
LOGGER.info('Timer for database import diffused')
SERVER.revert_db_timer.cancel()
revert_db_import(swap=False)
elif user.admin and SERVER.revert_hosting_timer.is_alive():
logging.info('Timer for hosting changes diffused')
LOGGER.info('Timer for hosting changes diffused')
SERVER.revert_hosting_timer.cancel()
# Generate an API key until one
@@ -717,7 +719,7 @@ def api_admin_settings(inputs: Dict[str, Any]):
return return_api(get_admin_settings())
elif request.method == 'PUT':
logging.info(f'Submitting admin settings: {inputs}')
LOGGER.info(f'Submitting admin settings: {inputs}')
hosting_changes = any(
inputs[s] is not None
@@ -736,6 +738,19 @@ def api_admin_settings(inputs: Dict[str, Any]):
return return_api({})
@admin_api.route(
'/logs',
'Get the debug logs',
methods=['GET']
)
@endpoint_wrapper
def api_admin_logs():
file = get_debug_log_filepath()
if not exists(file):
raise LogFileNotFound
return send_file(file), 200
@admin_api.route(
'/users',
'Get all users or add one',

View File

@@ -158,6 +158,7 @@ h2 {
flex-wrap: wrap;
}
#download-logs-button,
#save-hosting-button,
#add-user-button,
#download-db-button,
@@ -173,6 +174,10 @@ h2 {
box-shadow: var(--default-shadow);
}
.database-container:has(#download-logs-button) {
margin: 0;
}
#save-hosting-button {
align-self: center;
}

View File

@@ -84,6 +84,18 @@ function submitSettings() {
});
};
function downloadLogFile() {
fetch(`${url_prefix}/api/admin/logs?api_key=${api_key}`)
.then(response => {
if (!response.ok) return Promise.reject(response.status)
window.location.href = `${url_prefix}/api/admin/logs?api_key=${api_key}`;
})
.catch(e => {
if (e === 404)
alert("No debug log file to download. Enable debug logging first.")
});
};
function submitHostingSettings() {
hosting_inputs.submit.innerText = 'Restarting';
const data = {
@@ -291,6 +303,7 @@ loadUsers();
document.querySelector('#logout-button').onclick = e => logout();
document.querySelector('#settings-form').action = 'javascript:submitSettings();';
document.querySelector('#download-logs-button').onclick = e => downloadLogFile();
hosting_inputs.form.action = 'javascript:submitHostingSettings();';
document.querySelector('#add-user-button').onclick = e => toggleAddUser();
document.querySelector('#add-user-form').action = 'javascript:addUser()';

View File

@@ -1,18 +1,16 @@
{
"name": "MIND",
"short_name": "MIND",
"start_url": "/",
"start_url": "/mind/",
"display": "standalone",
"background_color": "#1b1b1b",
"theme_color": "#6b6b6b",
"orientation": "portrait-primary",
"icons": [
{
"src": "/static/img/favicon.svg",
"src": "/mind/static/img/favicon.svg",
"type": "image/svg+xml",
"sizes": "64x64 32x32 24x24 16x16"
}
]
} }
]
}

View File

@@ -94,6 +94,9 @@
</tbody>
</table>
</div>
<div class="database-container">
<button id="download-logs-button" type="button">Download Debug Logs</button>
</div>
</form>
<form id="hosting-form">
<h2>Hosting</h2>