Modify: settings - add DATA_DIR logs_collector storage_info use DATA_DIR as storage root

This commit is contained in:
Stepan Zhukovsky 2023-09-11 13:53:03 +09:00
parent 5893920d69
commit 57a758f93e
8 changed files with 112 additions and 24 deletions

View File

@ -22,6 +22,7 @@ ENV PYTHONUNBUFFERED 1
# default build args # default build args
ARG VERSION=0.1.0 \ ARG VERSION=0.1.0 \
APP_DIR=/app \ APP_DIR=/app \
DATA_DIR=/app/data \
SRC_DIR=./logs_collector \ SRC_DIR=./logs_collector \
SCRIPTS_DIR=./scripts \ SCRIPTS_DIR=./scripts \
WEB_PORT=8000 \ 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/lib/python3.10/site-packages/ /usr/local/lib/python3.10/site-packages/
COPY --from=base /usr/local/bin/ /usr/local/bin/ 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 && \ RUN apk add --no-cache --upgrade curl && \
addgroup --system ${USER_GROUP} --gid ${APP_GID} && \ 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 # switch to user
USER ${USER_NAME} USER ${USER_NAME}
# copy src and entrypoint.sh to app dir # copy src and entrypoint.sh to app dir
COPY --chown=${USER_NAME}:${USER_GROUP} ${SRC_DIR} ${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 # set workdir
WORKDIR ${APP_DIR} WORKDIR ${APP_DIR}

View File

@ -13,6 +13,7 @@ services:
- SRC_DIR=${SRC_DIR} - SRC_DIR=${SRC_DIR}
- SCRIPTS_DIR=${SCRIPTS_DIR} - SCRIPTS_DIR=${SCRIPTS_DIR}
- APP_DIR=${APP_DIR} - APP_DIR=${APP_DIR}
- DATA_DIR=${DATA_DIR}
- WEB_PORT=${WEB_PORT} - WEB_PORT=${WEB_PORT}
- USER_NAME=${USER_NAME} - USER_NAME=${USER_NAME}
- USER_GROUP=${USER_GROUP} - USER_GROUP=${USER_GROUP}

View File

@ -66,3 +66,4 @@ class StorageInfoSerializer(serializers.Serializer):
used = serializers.IntegerField(read_only=True) used = serializers.IntegerField(read_only=True)
free = serializers.IntegerField(read_only=True) free = serializers.IntegerField(read_only=True)
used_percent = serializers.IntegerField(read_only=True) used_percent = serializers.IntegerField(read_only=True)
status = serializers.CharField(read_only=True)

View File

@ -171,4 +171,4 @@ class StorageInfo(views.APIView):
summary='Show storage space in bytes' summary='Show storage space in bytes'
) )
def get(self, request): def get(self, request):
return Response(get_mount_fs_info(settings.MEDIA_ROOT)) return Response(get_mount_fs_info(settings.DATA_DIR))

View File

@ -13,4 +13,4 @@ def metadata(request):
def storage_info(request): def storage_info(request):
return {'storage': get_mount_fs_info(settings.MEDIA_ROOT)} return {'storage': get_mount_fs_info(settings.DATA_DIR)}

View File

@ -1,7 +1,8 @@
import shutil import shutil
import pathlib
def logs_dir_path(instance, filename): def logs_dir_path(instance, filename: str) -> str:
""" """
file will be uploaded to file will be uploaded to
MEDIA_ROOT/view/<filename> MEDIA_ROOT/view/<filename>
@ -30,9 +31,31 @@ def sizify(value: int) -> str:
return f'{round(value, 1)} {ext}' return f'{round(value, 1)} {ext}'
def get_mount_fs_info(path): def get_mount_fs_info(path: type[pathlib.PosixPath]) -> dict:
mount_info = shutil.disk_usage(path)._asdict() """
mount_info['used_percent'] = round( Get directory information for storing uploaded files.
mount_info['used'] / mount_info['total'] * 100 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 return mount_info

View File

@ -4,9 +4,19 @@ from datetime import timedelta
from . import __version__, __status__ from . import __version__, __status__
# █▀█ █▀█ █▀█ ▀█▀ ▀
# █▀▄ █▄█ █▄█ ░█░ ▄
# -- -- -- -- -- --
# Build paths inside the project like this: BASE_DIR / 'subdir'. # Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent BASE_DIR = Path(__file__).resolve().parent.parent
# █▀▀ █▄░█ █░█ ▀
# ██▄ █░▀█ ▀▄▀ ▄
# -- -- -- -- --
# Set default environ variables: # Set default environ variables:
env = environ.Env( env = environ.Env(
# set casting default value # set casting default value
@ -14,7 +24,7 @@ env = environ.Env(
ENVIRONMENT=(str, __status__), ENVIRONMENT=(str, __status__),
DEBUG=(bool, False), DEBUG=(bool, False),
SECRET_KEY=(str, 'j9QGbvM9Z4otb47'), SECRET_KEY=(str, 'j9QGbvM9Z4otb47'),
SQLITE_URL=(str, f'sqlite:///{BASE_DIR / "data/db.sqlite3"}'), DATA_DIR=(str, BASE_DIR / 'data'),
CSRF_TRUSTED_ORIGINS=(list, []), CSRF_TRUSTED_ORIGINS=(list, []),
ALLOWED_HOSTS=(list, ['*']), ALLOWED_HOSTS=(list, ['*']),
TZ=(str, 'UTC'), TZ=(str, 'UTC'),
@ -23,7 +33,13 @@ env = environ.Env(
# Read .env file if exist: # Read .env file if exist:
environ.Env.read_env(BASE_DIR / '.env') environ.Env.read_env(BASE_DIR / '.env')
# █▀▀ █▀█ █▀█ █▀▀ ▀
# █▄▄ █▄█ █▀▄ ██▄ ▄
# -- -- -- -- -- -
VERSION = env('VERSION') VERSION = env('VERSION')
ENVIRONMENT = env('ENVIRONMENT') ENVIRONMENT = env('ENVIRONMENT')
# SECURITY WARNING: keep the secret key used in production secret! # SECURITY WARNING: keep the secret key used in production secret!
@ -99,12 +115,6 @@ TEMPLATES = [
WSGI_APPLICATION = 'logs_collector.wsgi.application' 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 # Password validation
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators # https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
@ -134,6 +144,11 @@ USE_I18N = True
USE_TZ = True USE_TZ = True
# █▀ ▀█▀ ▄▀█ ▀█▀ █ █▀▀ ▀
# ▄█ ░█░ █▀█ ░█░ █ █▄▄ ▄
# -- -- -- -- -- -- -- -
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.2/howto/static-files/ # https://docs.djangoproject.com/en/4.2/howto/static-files/
# Whitenoise: # Whitenoise:
@ -141,12 +156,19 @@ USE_TZ = True
STATIC_URL = 'static/' STATIC_URL = 'static/'
STATIC_ROOT = BASE_DIR / '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 = { STORAGES = {
"default": { "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 # django-crispy-forms and crispy-bootstrap5
# https://django-crispy-forms.readthedocs.io/en/latest/ # https://django-crispy-forms.readthedocs.io/en/latest/
CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5" CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5"
@ -196,7 +241,6 @@ if DEBUG:
).append('rest_framework.renderers.BrowsableAPIRenderer') ).append('rest_framework.renderers.BrowsableAPIRenderer')
# https://drf-spectacular.readthedocs.io/en/latest/readme.html # https://drf-spectacular.readthedocs.io/en/latest/readme.html
# TODO: set environ vars config!
SPECTACULAR_SETTINGS = { SPECTACULAR_SETTINGS = {
'TITLE': 'Logs collector API', 'TITLE': 'Logs collector API',
'DESCRIPTION': 'Collector of archives with log files for further analysis', '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 "SLIDING_TOKEN_REFRESH_SERIALIZER": "rest_framework_simplejwt.serializers.TokenRefreshSlidingSerializer", # noqa:E501
} }
# ▄▀█ █░█ ▀█▀ █░█ ▀
# █▀█ █▄█ ░█░ █▀█ ▄
# -- -- -- -- -- --
LOGIN_URL = 'two_factor:login' LOGIN_URL = 'two_factor:login'
LOGIN_REDIRECT_URL = 'collector:index' LOGIN_REDIRECT_URL = 'collector:index'
LOGOUT_REDIRECT_URL = 'two_factor:login' LOGOUT_REDIRECT_URL = 'two_factor:login'

View File

@ -5,8 +5,18 @@
class="nav-link me-1 bi bi-sd-card" class="nav-link me-1 bi bi-sd-card"
aria-current="page" aria-current="page"
data-bs-toggle="tooltip" data-bs-toggle="tooltip"
data-bs-html="true"
data-bs-placement="bottom" data-bs-placement="bottom"
data-bs-title="Storage used: {{ storage.used_percent }}%" data-bs-title="
<span><u>STORAGE</u><span>
<br>
Used: {{ storage.used_percent }}%
<br>
Status:
<span class={% if storage.status == 'error' %}text-danger{% else %}text-success{% endif %}>
{{ storage.status }}
<span>
"
> >
</i> </i>
<div <div