diff --git a/Dockerfile b/Dockerfile index aece266..d6527ce 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,6 +22,7 @@ ENV PYTHONUNBUFFERED 1 # default build args ARG VERSION=0.1.0 \ APP_DIR=/app \ + DATA_DIR=/app/data \ SRC_DIR=./logs_collector \ SCRIPTS_DIR=./scripts \ WEB_PORT=8000 \ @@ -34,17 +35,20 @@ ARG VERSION=0.1.0 \ COPY --from=base /usr/local/lib/python3.10/site-packages/ /usr/local/lib/python3.10/site-packages/ COPY --from=base /usr/local/bin/ /usr/local/bin/ -# add curl and createa user to avoid running container as root +# add curl and createa user to avoid running container as root && +# create storage dir RUN apk add --no-cache --upgrade curl && \ addgroup --system ${USER_GROUP} --gid ${APP_GID} && \ - adduser --system --uid ${APP_UID} --ingroup ${USER_GROUP} ${USER_NAME} + adduser --system --uid ${APP_UID} --ingroup ${USER_GROUP} ${USER_NAME} && \ + mkdir -p ${APP_DIR}/data && \ + chown -R ${USER_NAME}:${USER_GROUP} ${DATA_DIR} # switch to user USER ${USER_NAME} # copy src and entrypoint.sh to app dir COPY --chown=${USER_NAME}:${USER_GROUP} ${SRC_DIR} ${APP_DIR} -COPY --chown=${USER_NAME}:${USER_GROUP} ${SCRIPTS_DIR}/entrypoint.sh ${APP_DIR}/ +COPY --chown=${USER_NAME}:${USER_GROUP} ${SCRIPTS_DIR}/entrypoint.sh ${APP_DIR} # set workdir WORKDIR ${APP_DIR} diff --git a/docker-compose.yaml b/docker-compose.yaml index e562fb3..7a605a1 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -13,6 +13,7 @@ services: - SRC_DIR=${SRC_DIR} - SCRIPTS_DIR=${SCRIPTS_DIR} - APP_DIR=${APP_DIR} + - DATA_DIR=${DATA_DIR} - WEB_PORT=${WEB_PORT} - USER_NAME=${USER_NAME} - USER_GROUP=${USER_GROUP} diff --git a/logs_collector/collector/api/serializers.py b/logs_collector/collector/api/serializers.py index ad06a23..ca09798 100644 --- a/logs_collector/collector/api/serializers.py +++ b/logs_collector/collector/api/serializers.py @@ -66,3 +66,4 @@ class StorageInfoSerializer(serializers.Serializer): used = serializers.IntegerField(read_only=True) free = serializers.IntegerField(read_only=True) used_percent = serializers.IntegerField(read_only=True) + status = serializers.CharField(read_only=True) diff --git a/logs_collector/collector/api/views.py b/logs_collector/collector/api/views.py index abda5a3..cc489b3 100644 --- a/logs_collector/collector/api/views.py +++ b/logs_collector/collector/api/views.py @@ -171,4 +171,4 @@ class StorageInfo(views.APIView): summary='Show storage space in bytes' ) def get(self, request): - return Response(get_mount_fs_info(settings.MEDIA_ROOT)) + return Response(get_mount_fs_info(settings.DATA_DIR)) diff --git a/logs_collector/collector/context_processors.py b/logs_collector/collector/context_processors.py index bf49479..88b207b 100644 --- a/logs_collector/collector/context_processors.py +++ b/logs_collector/collector/context_processors.py @@ -13,4 +13,4 @@ def metadata(request): def storage_info(request): - return {'storage': get_mount_fs_info(settings.MEDIA_ROOT)} + return {'storage': get_mount_fs_info(settings.DATA_DIR)} diff --git a/logs_collector/collector/utils/helpers.py b/logs_collector/collector/utils/helpers.py index 526a1f0..6fac1d0 100644 --- a/logs_collector/collector/utils/helpers.py +++ b/logs_collector/collector/utils/helpers.py @@ -1,7 +1,8 @@ import shutil +import pathlib -def logs_dir_path(instance, filename): +def logs_dir_path(instance, filename: str) -> str: """ file will be uploaded to MEDIA_ROOT/view/ @@ -30,9 +31,31 @@ def sizify(value: int) -> str: return f'{round(value, 1)} {ext}' -def get_mount_fs_info(path): - mount_info = shutil.disk_usage(path)._asdict() - mount_info['used_percent'] = round( - mount_info['used'] / mount_info['total'] * 100 - ) +def get_mount_fs_info(path: type[pathlib.PosixPath]) -> dict: + """ + Get directory information for storing uploaded files. + Includes information total/used/free space on mount device + + Args: + path (pathlib.PosixPath): path to storage dir + + Returns: + dict: storage mount info + """ + mount_info: dict = {} + try: + mount_info = shutil.disk_usage(path)._asdict() + mount_info['used_percent'] = round( + mount_info['used'] / mount_info['total'] * 100, + ) + mount_info['status'] = 'mount' + except Exception as error: # expected FileNotFoundError + mount_info = { + 'total': 0, + 'used': 0, + 'free': 0, + 'used_percent': 0, + 'status': 'error', + 'traceback': f'{error}' + } return mount_info diff --git a/logs_collector/logs_collector/settings.py b/logs_collector/logs_collector/settings.py index 1a74118..0219d2a 100644 --- a/logs_collector/logs_collector/settings.py +++ b/logs_collector/logs_collector/settings.py @@ -4,9 +4,19 @@ from datetime import timedelta from . import __version__, __status__ + +# █▀█ █▀█ █▀█ ▀█▀ ▀ +# █▀▄ █▄█ █▄█ ░█░ ▄ +# -- -- -- -- -- -- + # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent + +# █▀▀ █▄░█ █░█ ▀ +# ██▄ █░▀█ ▀▄▀ ▄ +# -- -- -- -- -- + # Set default environ variables: env = environ.Env( # set casting default value @@ -14,7 +24,7 @@ env = environ.Env( ENVIRONMENT=(str, __status__), DEBUG=(bool, False), SECRET_KEY=(str, 'j9QGbvM9Z4otb47'), - SQLITE_URL=(str, f'sqlite:///{BASE_DIR / "data/db.sqlite3"}'), + DATA_DIR=(str, BASE_DIR / 'data'), CSRF_TRUSTED_ORIGINS=(list, []), ALLOWED_HOSTS=(list, ['*']), TZ=(str, 'UTC'), @@ -23,7 +33,13 @@ env = environ.Env( # Read .env file if exist: environ.Env.read_env(BASE_DIR / '.env') + +# █▀▀ █▀█ █▀█ █▀▀ ▀ +# █▄▄ █▄█ █▀▄ ██▄ ▄ +# -- -- -- -- -- - + VERSION = env('VERSION') + ENVIRONMENT = env('ENVIRONMENT') # SECURITY WARNING: keep the secret key used in production secret! @@ -99,12 +115,6 @@ TEMPLATES = [ WSGI_APPLICATION = 'logs_collector.wsgi.application' -# Database -# https://docs.djangoproject.com/en/4.2/ref/settings/#databases -DATABASES = { - 'default': env.db_url('SQLITE_URL') -} - # Password validation # https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators @@ -134,6 +144,11 @@ USE_I18N = True USE_TZ = True + +# █▀ ▀█▀ ▄▀█ ▀█▀ █ █▀▀ ▀ +# ▄█ ░█░ █▀█ ░█░ █ █▄▄ ▄ +# -- -- -- -- -- -- -- - + # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/4.2/howto/static-files/ # Whitenoise: @@ -141,12 +156,19 @@ USE_TZ = True STATIC_URL = 'static/' STATIC_ROOT = BASE_DIR / 'static' -# Default primary key field type -# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field -DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' +# █▀▄ ▄▀█ ▀█▀ ▄▀█ ▀ +# █▄▀ █▀█ ░█░ █▀█ ▄ +# -- -- -- -- -- -- -MEDIA_ROOT = BASE_DIR / 'data/archives' +# Build paths inside the project for db and storage. +DATA_DIR = Path(env('DATA_DIR')) + +# Create DATA_DIR ignore if exist: +Path(DATA_DIR).mkdir(parents=True, exist_ok=True) + +# Custom file storage path +MEDIA_ROOT = DATA_DIR / 'archives' STORAGES = { "default": { @@ -161,6 +183,29 @@ STORAGES = { }, } + +# █▀▄ ▄▀█ ▀█▀ ▄▀█ █▄▄ ▄▀█ █▀ █▀▀ ▀ +# █▄▀ █▀█ ░█░ █▀█ █▄█ █▀█ ▄█ ██▄ ▄ +# -- -- -- -- -- -- -- -- -- -- -- + +# Database +# https://docs.djangoproject.com/en/4.2/ref/settings/#databases +DATABASES = { + 'default': env.db_url( + 'DB_URL', + default=f'sqlite:///{DATA_DIR / "db.sqlite3"}' + ) +} + +# Default primary key field type +# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + + +# █▀▀ ▀▄▀ ▀█▀ █▀▀ █▄░█ ▀█▀ █ █▀█ █▄░█ █▀ ▀ +# ██▄ █░█ ░█░ ██▄ █░▀█ ░█░ █ █▄█ █░▀█ ▄█ ▄ +# -- -- -- -- -- -- -- -- -- -- -- -- -- - + # django-crispy-forms and crispy-bootstrap5 # https://django-crispy-forms.readthedocs.io/en/latest/ CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5" @@ -196,7 +241,6 @@ if DEBUG: ).append('rest_framework.renderers.BrowsableAPIRenderer') # https://drf-spectacular.readthedocs.io/en/latest/readme.html -# TODO: set environ vars config! SPECTACULAR_SETTINGS = { 'TITLE': 'Logs collector API', 'DESCRIPTION': 'Collector of archives with log files for further analysis', @@ -249,6 +293,11 @@ SIMPLE_JWT = { "SLIDING_TOKEN_REFRESH_SERIALIZER": "rest_framework_simplejwt.serializers.TokenRefreshSlidingSerializer", # noqa:E501 } + +# ▄▀█ █░█ ▀█▀ █░█ ▀ +# █▀█ █▄█ ░█░ █▀█ ▄ +# -- -- -- -- -- -- + LOGIN_URL = 'two_factor:login' LOGIN_REDIRECT_URL = 'collector:index' LOGOUT_REDIRECT_URL = 'two_factor:login' diff --git a/logs_collector/templates/includes/storage.html b/logs_collector/templates/includes/storage.html index c6c2439..cd5f16c 100644 --- a/logs_collector/templates/includes/storage.html +++ b/logs_collector/templates/includes/storage.html @@ -5,8 +5,18 @@ class="nav-link me-1 bi bi-sd-card" aria-current="page" data-bs-toggle="tooltip" + data-bs-html="true" data-bs-placement="bottom" - data-bs-title="Storage used: {{ storage.used_percent }}%" + data-bs-title=" + STORAGE +
+ Used: {{ storage.used_percent }}% +
+ Status: + + {{ storage.status }} + + " >