Source code for management.views.logging

"""Logging setting specific views."""

from __future__ import annotations

import datetime
import io
import os
import re
import tarfile
import zipfile
from pathlib import Path
from typing import TYPE_CHECKING, cast

from django.db import models
from django.http import Http404, HttpResponse
from django.shortcuts import render
from django.utils.translation import gettext as _
from django.views.generic import TemplateView, View
from django.views.generic.base import RedirectView
from django.views.generic.list import ListView

from trustpoint.logger import LoggerMixin
from trustpoint.page_context import PageContextMixin
from trustpoint.settings import DATE_FORMAT, LOG_DIR_PATH
from trustpoint.views.base import SortableTableFromListMixin

if TYPE_CHECKING:
    from typing import Any

    from django.http import HttpRequest


[docs] class IndexView(RedirectView): """Index view."""
[docs] permanent = True
[docs] pattern_name = 'management:language'
[docs] def language(request: HttpRequest) -> HttpResponse: """Handle language Configuration. Returns: HTTPResponse """ context = {'page_category': 'management', 'page_name': 'language'} return render(request, 'management/language.html', context=context)
# ------------------------------------------------------- Logging ------------------------------------------------------
[docs] class LoggingFilesTableView(PageContextMixin, LoggerMixin, SortableTableFromListMixin, ListView[models.Model]): """View to display all log files in the log directory in a table."""
[docs] http_method_names = ('get',)
[docs] template_name = 'management/logging/logging_files.html'
[docs] context_object_name = 'log_files'
[docs] default_sort_param = 'filename'
[docs] paginate_by = None
[docs] page_category = 'management'
[docs] page_name = 'logging'
@staticmethod
[docs] def _get_first_and_last_entry_date( log_file_path: Path, ) -> tuple[None | datetime.datetime, None | datetime.datetime]: log_file = log_file_path.read_text(encoding='utf-8', errors='backslashreplace') date_regex = re.compile(r'\b\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\b') matches = re.findall(date_regex, log_file) if matches: first_date = datetime.datetime.strptime(matches[0], DATE_FORMAT).replace(tzinfo=datetime.UTC) last_date = datetime.datetime.strptime(matches[-1], DATE_FORMAT).replace(tzinfo=datetime.UTC) else: first_date = None last_date = None return first_date, last_date
@classmethod
[docs] def _get_log_file_data(cls, log_filename: str) -> dict[str, str]: log_file_path = LOG_DIR_PATH / Path(log_filename) if not log_file_path.exists() or not log_file_path.is_file(): return {} first_date, last_date = cls._get_first_and_last_entry_date(log_file_path) if isinstance(first_date, datetime.datetime): created_at = first_date.strftime(f'{DATE_FORMAT} UTC') else: created_at = _('None') if isinstance(last_date, datetime.datetime): # noqa: SIM108 updated_at = last_date.strftime(f'{DATE_FORMAT} UTC') else: updated_at = _('None') return {'filename': log_filename, 'created_at': created_at, 'updated_at': updated_at}
[docs] def get_queryset(self) -> list[dict[str, str]]: # type: ignore[override] """Gets a queryset of all valid Trustpoint log files in the log directory.""" all_files = os.listdir(LOG_DIR_PATH) valid_log_files = [f for f in all_files if re.compile(r'^trustpoint\.log(?:\.\d+)?$').match(f)] self.queryset = [self._get_log_file_data(log_file_name) for log_file_name in valid_log_files] # type: ignore[assignment] return cast('list[dict[str, str]]', self.queryset)
[docs] class LoggingFilesDetailsView(PageContextMixin, LoggerMixin, TemplateView): """Log file detail view, allows to view the content of a single log file without download."""
[docs] http_method_names = ('get',)
[docs] template_name = 'management/logging/logging_files_details.html'
[docs] log_directory = LOG_DIR_PATH
[docs] page_category = 'settings'
[docs] page_name = 'logging'
[docs] def get_context_data(self, **kwargs: Any) -> dict[str, Any]: """Get the context data for the view.""" context = super().get_context_data(**kwargs) log_filename = self.kwargs.get('filename') log_file_path = LOG_DIR_PATH / Path(log_filename) if not log_file_path.exists() or not log_file_path.is_file(): context['log_content'] = 'Log-File not found.' else: context['log_content'] = log_file_path.read_text(encoding='utf-8', errors='backslashreplace') return context
[docs] class LoggingFilesDownloadView(PageContextMixin, LoggerMixin, TemplateView): """View to download a single log file"""
[docs] http_method_names = ('get',)
[docs] page_category = 'settings'
[docs] page_name = 'logging'
[docs] def get(self, *_args: Any, **kwargs: Any) -> HttpResponse: """The HTTP GET method for the view.""" filename = kwargs.get('filename') log_file_path = LOG_DIR_PATH / Path(filename) if not log_file_path.exists() or not log_file_path.is_file(): exc_msg = 'Log file not found.' raise Http404(exc_msg) response = HttpResponse( log_file_path.read_text(encoding='utf-8', errors='backslashreplace'), content_type='text/plain' ) response['Content-Disposition'] = f'attachment; filename={filename}' return response
[docs] class LoggingFilesDownloadMultipleView(PageContextMixin, LoggerMixin, View): """View to download multiple log files as a single archive."""
[docs] http_method_names = ('get',)
[docs] page_category = 'settings'
[docs] page_name = 'logging'
@classmethod
[docs] def get(cls, *_args: Any, **kwargs: Any) -> HttpResponse: """The HTTP GET method for the view.""" archive_format = kwargs.get('archive_format') filenames = kwargs.get('filenames') # These should never happen, due to the regex in the urls.py (re_path). ---------------------------------------- if not archive_format or not filenames: exc_msg = 'Log files not found.' raise Http404(exc_msg) if archive_format not in ['zip', 'tar.gz']: exc_msg = 'Invalid archive format specified.' raise Http404(exc_msg) # -------------------------------------------------------------------------------------------------------------- filenames = [filename for filename in filenames.split('/') if filename] file_collection = [(filename, (LOG_DIR_PATH / Path(filename)).read_bytes()) for filename in filenames] if archive_format.lower() == 'zip': bytes_io = io.BytesIO() zip_file = zipfile.ZipFile(bytes_io, 'w') for filename, data in file_collection: zip_file.writestr(filename, data) zip_file.close() response = HttpResponse(bytes_io.getvalue(), content_type='application/zip') response['Content-Disposition'] = 'attachment; filename=trustpoint-logs.zip' return response bytes_io = io.BytesIO() with tarfile.open(fileobj=bytes_io, mode='w:gz') as tar: for filename, data in file_collection: file_io_bytes = io.BytesIO(data) file_io_bytes_info = tarfile.TarInfo(filename) file_io_bytes_info.size = len(data) tar.addfile(file_io_bytes_info, file_io_bytes) response = HttpResponse(bytes_io.getvalue(), content_type='application/gzip') response['Content-Disposition'] = 'attachment; filename=trustpoint-logs.tar.gz' return response