mirror of
https://github.com/Casvt/MIND.git
synced 2026-02-19 11:54:46 -05:00
Improved logging setup with debug log files
This commit is contained in:
5
MIND.py
5
MIND.py
@@ -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)
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) + ').'
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=}'
|
||||
)
|
||||
|
||||
|
||||
@@ -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]:
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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()';
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
} }
|
||||
]
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user