feat: admin maintenance page (#1096)

* fix build typo

* generate types

* setup maintenance api for common cleanup actions

* admin maintenance page

* remove duplicate use-with-caution
This commit is contained in:
Hayden
2022-03-24 22:17:38 -08:00
committed by GitHub
parent ffb3b45ac2
commit 4ef649231b
12 changed files with 376 additions and 2 deletions

28
mealie/pkgs/img/static.py Normal file
View File

@@ -0,0 +1,28 @@
NOT_WEBP = {
".jpg",
".jpeg",
".jpe",
".jif",
".jfif",
".jfi",
".png",
".gif",
".tiff",
".tif",
".psd",
".raw",
".arw",
".cr2",
".nrw",
".k25",
".bmp",
".dib",
".heif",
".heic",
".ind",
".jp2",
".svg",
".svgz",
".ai",
".eps",
}

View File

@@ -1,3 +1,7 @@
import os
from pathlib import Path
def pretty_size(size: int) -> str:
"""
Pretty size takes in a integer value of a file size and returns the most applicable
@@ -13,3 +17,17 @@ def pretty_size(size: int) -> str:
return f"{round(size / 1024 / 1024 / 1024, 2)} GB"
else:
return f"{round(size / 1024 / 1024 / 1024 / 1024, 2)} TB"
def get_dir_size(path: Path | str) -> int:
"""
Get the size of a directory
"""
total_size = os.path.getsize(path)
for item in os.listdir(path):
itempath = os.path.join(path, item)
if os.path.isfile(itempath):
total_size += os.path.getsize(itempath)
elif os.path.isdir(itempath):
total_size += get_dir_size(itempath)
return total_size

View File

@@ -5,6 +5,7 @@ from . import (
admin_backups,
admin_email,
admin_log,
admin_maintenance,
admin_management_groups,
admin_management_users,
admin_server_tasks,
@@ -18,4 +19,5 @@ router.include_router(admin_management_users.router)
router.include_router(admin_management_groups.router)
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)
router.include_router(admin_backups.router, tags=["Admin: Backups"])
router.include_router(admin_maintenance.router, tags=["Admin: Maintenance"])

View File

@@ -0,0 +1,108 @@
import contextlib
import os
import shutil
import uuid
from pathlib import Path
from fastapi import APIRouter, HTTPException
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.response import ErrorResponse, SuccessResponse
router = APIRouter(prefix="/maintenance")
def clean_images(root_dir: Path, dry_run: bool) -> int:
cleaned_images = 0
for recipe_dir in root_dir.iterdir():
image_dir = recipe_dir.joinpath("images")
if not image_dir.exists():
continue
for image in image_dir.iterdir():
if image.is_dir():
continue
if image.suffix != ".webp":
if not dry_run:
image.unlink()
cleaned_images += 1
return cleaned_images
def clean_recipe_folders(root_dir: Path, dry_run: bool) -> int:
cleaned_dirs = 0
for recipe_dir in root_dir.iterdir():
if recipe_dir.is_dir():
# Attemp to convert the folder name to a UUID
try:
uuid.UUID(recipe_dir.name)
continue
except ValueError:
if not dry_run:
shutil.rmtree(recipe_dir)
cleaned_dirs += 1
return cleaned_dirs
@controller(router)
class AdminMaintenanceController(BaseAdminController):
@router.get("", response_model=MaintenanceSummary)
def get_maintenance_summary(self):
"""
Get the maintenance summary
"""
log_file_size = 0
with contextlib.suppress(FileNotFoundError):
log_file_size = os.path.getsize(LOGGER_FILE)
return MaintenanceSummary(
data_dir_size=fs_stats.pretty_size(fs_stats.get_dir_size(self.deps.folders.DATA_DIR)),
log_file_size=fs_stats.pretty_size(log_file_size),
cleanable_images=clean_images(self.deps.folders.RECIPE_DATA_DIR, dry_run=True),
cleanable_dirs=clean_recipe_folders(self.deps.folders.RECIPE_DATA_DIR, dry_run=True),
)
@router.post("/clean/images", response_model=SuccessResponse)
def clean_images(self):
"""
Purges all the images from the filesystem that aren't .webp
"""
try:
cleaned_images = clean_images(self.deps.folders.RECIPE_DATA_DIR, dry_run=False)
return SuccessResponse.respond(f"{cleaned_images} Images cleaned")
except Exception as e:
raise HTTPException(status_code=500, detail=ErrorResponse.respond("Failed to clean images")) from e
@router.post("/clean/recipe-folders", response_model=SuccessResponse)
def clean_recipe_folders(self):
"""
Deletes all the recipe folders that don't have names that are valid UUIDs
"""
try:
cleaned_dirs = clean_recipe_folders(self.deps.folders.RECIPE_DATA_DIR, dry_run=False)
return SuccessResponse.respond(f"{cleaned_dirs} Recipe folders removed")
except Exception as e:
raise HTTPException(status_code=500, detail=ErrorResponse.respond("Failed to clean directories")) from e
@router.post("/clean/logs", response_model=SuccessResponse)
def clean_logs(self):
"""
Purges the logs
"""
try:
with contextlib.suppress(FileNotFoundError):
os.remove(LOGGER_FILE)
LOGGER_FILE.touch()
return SuccessResponse.respond("Logs cleaned")
except Exception as e:
raise HTTPException(status_code=500, detail=ErrorResponse.respond("Failed to clean logs")) from e

View File

@@ -1,6 +1,7 @@
# GENERATED CODE - DO NOT MODIFY BY HAND
from .about import *
from .backup import *
from .maintenance import *
from .migration import *
from .restore import *
from .settings import *

View File

@@ -0,0 +1,8 @@
from fastapi_camelcase import CamelModel
class MaintenanceSummary(CamelModel):
data_dir_size: str
log_file_size: str
cleanable_images: int
cleanable_dirs: int