mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-12-11 04:45:22 -05:00
feat: admin maintenance and analytics stubs (#1107)
* add tail log viewer routes * add log viewer * add _mealie to ignore directories * add detailed breakdown of storage * generate types * add dialog to view breakdown * cleanup mobile UI * move migrations page * spelling * init analytics page * move route up * add remove temp files function * analytics API client * stub out analytics pages * generate types * stub out analytics routes * update names * ignore types * temporary remove analytics from sidebar
This commit is contained in:
@@ -2,6 +2,7 @@ from mealie.routes._base.routers import AdminAPIRouter
|
||||
|
||||
from . import (
|
||||
admin_about,
|
||||
admin_analytics,
|
||||
admin_backups,
|
||||
admin_email,
|
||||
admin_log,
|
||||
@@ -15,9 +16,10 @@ router = AdminAPIRouter(prefix="/admin")
|
||||
|
||||
router.include_router(admin_about.router, tags=["Admin: About"])
|
||||
router.include_router(admin_log.router, tags=["Admin: Log"])
|
||||
router.include_router(admin_management_users.router)
|
||||
router.include_router(admin_management_groups.router)
|
||||
router.include_router(admin_management_users.router, tags=["Admin: Manage Users"])
|
||||
router.include_router(admin_management_groups.router, tags=["Admin: Manage Groups"])
|
||||
router.include_router(admin_email.router, tags=["Admin: Email"])
|
||||
router.include_router(admin_server_tasks.router, tags=["Admin: Server Tasks"])
|
||||
router.include_router(admin_backups.router, tags=["Admin: Backups"])
|
||||
router.include_router(admin_maintenance.router, tags=["Admin: Maintenance"])
|
||||
router.include_router(admin_analytics.router, tags=["Admin: Analytics"])
|
||||
|
||||
20
mealie/routes/admin/admin_analytics.py
Normal file
20
mealie/routes/admin/admin_analytics.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from functools import cached_property
|
||||
|
||||
from fastapi import APIRouter
|
||||
|
||||
from mealie.routes._base import BaseAdminController, controller
|
||||
from mealie.schema.analytics.analytics import MealieAnalytics
|
||||
from mealie.services.analytics.service_analytics import AnalyticsService
|
||||
|
||||
router = APIRouter(prefix="/analytics")
|
||||
|
||||
|
||||
@controller(router)
|
||||
class AdminAboutController(BaseAdminController):
|
||||
@cached_property
|
||||
def service(self) -> AnalyticsService:
|
||||
return AnalyticsService(self.repos)
|
||||
|
||||
@router.get("", response_model=MealieAnalytics)
|
||||
def get_analytics(self):
|
||||
return self.service.calculate_analytics()
|
||||
@@ -10,6 +10,7 @@ from mealie.core.root_logger import LOGGER_FILE
|
||||
from mealie.pkgs.stats import fs_stats
|
||||
from mealie.routes._base import BaseAdminController, controller
|
||||
from mealie.schema.admin import MaintenanceSummary
|
||||
from mealie.schema.admin.maintenance import MaintenanceLogs, MaintenanceStorageDetails
|
||||
from mealie.schema.response import ErrorResponse, SuccessResponse
|
||||
|
||||
router = APIRouter(prefix="/maintenance")
|
||||
@@ -54,6 +55,16 @@ def clean_recipe_folders(root_dir: Path, dry_run: bool) -> int:
|
||||
return cleaned_dirs
|
||||
|
||||
|
||||
def tail_log(log_file: Path, n: int) -> list[str]:
|
||||
try:
|
||||
with open(log_file, "r") as f:
|
||||
lines = f.readlines()
|
||||
except FileNotFoundError:
|
||||
return ["no log file found"]
|
||||
|
||||
return lines[-n:]
|
||||
|
||||
|
||||
@controller(router)
|
||||
class AdminMaintenanceController(BaseAdminController):
|
||||
@router.get("", response_model=MaintenanceSummary)
|
||||
@@ -72,6 +83,21 @@ class AdminMaintenanceController(BaseAdminController):
|
||||
cleanable_dirs=clean_recipe_folders(self.deps.folders.RECIPE_DATA_DIR, dry_run=True),
|
||||
)
|
||||
|
||||
@router.get("/logs", response_model=MaintenanceLogs)
|
||||
def get_logs(self, lines: int = 200):
|
||||
|
||||
return MaintenanceLogs(logs=tail_log(LOGGER_FILE, lines))
|
||||
|
||||
@router.get("/storage", response_model=MaintenanceStorageDetails)
|
||||
def get_storage_details(self):
|
||||
return MaintenanceStorageDetails(
|
||||
temp_dir_size=fs_stats.pretty_size(fs_stats.get_dir_size(self.deps.folders.TEMP_DIR)),
|
||||
backups_dir_size=fs_stats.pretty_size(fs_stats.get_dir_size(self.deps.folders.BACKUP_DIR)),
|
||||
groups_dir_size=fs_stats.pretty_size(fs_stats.get_dir_size(self.deps.folders.GROUPS_DIR)),
|
||||
recipes_dir_size=fs_stats.pretty_size(fs_stats.get_dir_size(self.deps.folders.RECIPE_DATA_DIR)),
|
||||
user_dir_size=fs_stats.pretty_size(fs_stats.get_dir_size(self.deps.folders.USER_DIR)),
|
||||
)
|
||||
|
||||
@router.post("/clean/images", response_model=SuccessResponse)
|
||||
def clean_images(self):
|
||||
"""
|
||||
@@ -83,6 +109,16 @@ class AdminMaintenanceController(BaseAdminController):
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=ErrorResponse.respond("Failed to clean images")) from e
|
||||
|
||||
@router.post("/clean/temp", response_model=SuccessResponse)
|
||||
def clean_temp(self):
|
||||
try:
|
||||
shutil.rmtree(self.deps.folders.TEMP_DIR)
|
||||
self.deps.folders.TEMP_DIR.mkdir(parents=True, exist_ok=True)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=ErrorResponse.respond("Failed to clean temp")) from e
|
||||
|
||||
return SuccessResponse.respond("'.temp' directory cleaned")
|
||||
|
||||
@router.post("/clean/recipe-folders", response_model=SuccessResponse)
|
||||
def clean_recipe_folders(self):
|
||||
"""
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
# GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
from .mealie_model import *
|
||||
from .types import *
|
||||
|
||||
@@ -6,3 +6,15 @@ class MaintenanceSummary(MealieModel):
|
||||
log_file_size: str
|
||||
cleanable_images: int
|
||||
cleanable_dirs: int
|
||||
|
||||
|
||||
class MaintenanceStorageDetails(MealieModel):
|
||||
temp_dir_size: str
|
||||
backups_dir_size: str
|
||||
groups_dir_size: str
|
||||
recipes_dir_size: str
|
||||
user_dir_size: str
|
||||
|
||||
|
||||
class MaintenanceLogs(MealieModel):
|
||||
logs: list[str]
|
||||
|
||||
2
mealie/schema/analytics/__init__.py
Normal file
2
mealie/schema/analytics/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
# GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
from .analytics import *
|
||||
19
mealie/schema/analytics/analytics.py
Normal file
19
mealie/schema/analytics/analytics.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from pydantic import UUID4
|
||||
|
||||
from .._mealie import MealieModel
|
||||
|
||||
|
||||
class MealieAnalytics(MealieModel):
|
||||
installation_id: UUID4
|
||||
version: str
|
||||
database_type: str
|
||||
|
||||
using_email: bool
|
||||
using_ldap: bool
|
||||
|
||||
api_tokens: int
|
||||
users: int
|
||||
groups: int
|
||||
recipes: int
|
||||
shopping_lists: int
|
||||
cookbooks: int
|
||||
@@ -11,5 +11,5 @@ from .recipe_nutrition import *
|
||||
from .recipe_settings import *
|
||||
from .recipe_share_token import * # type: ignore
|
||||
from .recipe_step import *
|
||||
from .recipe_tool import *
|
||||
from .recipe_tool import * # type: ignore
|
||||
from .request_helpers import *
|
||||
|
||||
33
mealie/services/analytics/service_analytics.py
Normal file
33
mealie/services/analytics/service_analytics.py
Normal file
@@ -0,0 +1,33 @@
|
||||
import uuid
|
||||
|
||||
from mealie.core.settings.static import APP_VERSION
|
||||
from mealie.repos.repository_factory import AllRepositories
|
||||
from mealie.schema.analytics.analytics import MealieAnalytics
|
||||
from mealie.services._base_service import BaseService
|
||||
|
||||
|
||||
class AnalyticsService(BaseService):
|
||||
def __init__(self, repos: AllRepositories):
|
||||
self.repos = repos
|
||||
super().__init__()
|
||||
|
||||
def _databate_type(self) -> str:
|
||||
return "sqlite" if "sqlite" in self.settings.DB_URL else "postgres" # type: ignore
|
||||
|
||||
def calculate_analytics(self) -> MealieAnalytics:
|
||||
return MealieAnalytics(
|
||||
# Site Wide Analytics
|
||||
installation_id=uuid.uuid4(),
|
||||
version=APP_VERSION,
|
||||
database_type=self._databate_type(),
|
||||
# Optional Configs
|
||||
using_ldap=self.settings.LDAP_ENABLED,
|
||||
using_email=self.settings.SMTP_ENABLE,
|
||||
# Stats
|
||||
api_tokens=0,
|
||||
users=0,
|
||||
groups=0,
|
||||
recipes=0,
|
||||
shopping_lists=0,
|
||||
cookbooks=0,
|
||||
)
|
||||
Reference in New Issue
Block a user