From f984bced06d94c56ebf3c2a06f44ff4a6926de87 Mon Sep 17 00:00:00 2001 From: bread <104435263+bread-ovO@users.noreply.github.com> Date: Mon, 23 Mar 2026 09:33:37 +0800 Subject: [PATCH] feat(dashboard): add log and cache cleanup in settings (#6822) * feat(dashboard): add log and cache cleanup in settings * refactor: simplify storage cleaner log config handling * fix: Repair abnormal indentation * fix(storage): harden cleanup config handling Use typed config value access to avoid treating invalid values as enabled flags or log paths during storage cleanup. Also stop exposing raw backend exceptions in the dashboard storage status API and direct users to server logs for details. --------- Co-authored-by: RC-CHN <1051989940@qq.com> --- astrbot/core/utils/storage_cleaner.py | 271 ++++++++++++++++++ astrbot/dashboard/routes/stat.py | 30 ++ .../mdi-subset/materialdesignicons-subset.css | 18 +- .../materialdesignicons-webfont-subset.woff | Bin 16252 -> 16648 bytes .../materialdesignicons-webfont-subset.woff2 | Bin 13068 -> 13348 bytes .../components/shared/StorageCleanupPanel.vue | 241 ++++++++++++++++ .../i18n/locales/en-US/features/settings.json | 34 +++ .../i18n/locales/ru-RU/features/settings.json | 36 ++- .../i18n/locales/zh-CN/features/settings.json | 34 +++ dashboard/src/views/Settings.vue | 7 +- tests/test_storage_cleaner.py | 85 ++++++ 11 files changed, 753 insertions(+), 3 deletions(-) create mode 100644 astrbot/core/utils/storage_cleaner.py create mode 100644 dashboard/src/components/shared/StorageCleanupPanel.vue create mode 100644 tests/test_storage_cleaner.py diff --git a/astrbot/core/utils/storage_cleaner.py b/astrbot/core/utils/storage_cleaner.py new file mode 100644 index 000000000..134071dce --- /dev/null +++ b/astrbot/core/utils/storage_cleaner.py @@ -0,0 +1,271 @@ +from __future__ import annotations + +import os +from collections.abc import Iterable, Mapping +from dataclasses import dataclass +from pathlib import Path + +from astrbot import logger +from astrbot.core.utils.astrbot_path import get_astrbot_data_path, get_astrbot_temp_path + + +@dataclass(frozen=True) +class LogFileConfig: + path: Path + enabled: bool + + +class StorageCleaner: + TARGET_LOGS = "logs" + TARGET_CACHE = "cache" + VALID_TARGETS = {TARGET_LOGS, TARGET_CACHE, "all"} + + def __init__( + self, + config: Mapping[str, object], + *, + data_dir: Path | None = None, + temp_dir: Path | None = None, + ) -> None: + self._config = config + self._data_dir = data_dir or Path(get_astrbot_data_path()) + self._temp_dir = temp_dir or Path(get_astrbot_temp_path()) + + def get_status(self) -> dict: + logs = self._build_status(self.TARGET_LOGS) + cache = self._build_status(self.TARGET_CACHE) + return { + self.TARGET_LOGS: logs, + self.TARGET_CACHE: cache, + "total_bytes": logs["size_bytes"] + cache["size_bytes"], + } + + def cleanup(self, target: str = "all") -> dict: + normalized_target = (target or "all").strip().lower() + if normalized_target not in self.VALID_TARGETS: + raise ValueError(f"Unsupported cleanup target: {target}") + + targets = ( + [self.TARGET_LOGS, self.TARGET_CACHE] + if normalized_target == "all" + else [normalized_target] + ) + results: dict[str, dict] = {} + aggregates = { + "removed_bytes": 0, + "processed_files": 0, + "deleted_files": 0, + "truncated_files": 0, + "failed_files": 0, + } + + for target_name in targets: + result = self._cleanup_target(target_name) + results[target_name] = result + for key in aggregates: + aggregates[key] += result[key] + + status = self.get_status() + + return { + "target": normalized_target, + "results": results, + **aggregates, + "status": status, + } + + def _build_status(self, target: str) -> dict: + if target == self.TARGET_LOGS: + files = self._collect_log_files() + primary_path = self._data_dir / "logs" + elif target == self.TARGET_CACHE: + files = self._collect_cache_files() + primary_path = self._temp_dir + else: + raise ValueError(f"Unsupported cleanup target: {target}") + + size_bytes, file_count = self._summarize_files(files) + return { + "size_bytes": size_bytes, + "file_count": file_count, + "path": str(primary_path), + "exists": primary_path.exists(), + } + + def _cleanup_target(self, target: str) -> dict: + if target == self.TARGET_LOGS: + files = self._collect_log_files() + active_log_files = self._active_log_files() + elif target == self.TARGET_CACHE: + files = self._collect_cache_files() + active_log_files = set() + else: + raise ValueError(f"Unsupported cleanup target: {target}") + + removed_bytes = 0 + deleted_files = 0 + truncated_files = 0 + failed_files = 0 + + for file_path in sorted(files): + if not file_path.exists(): + continue + + try: + size = file_path.stat().st_size + except OSError as exc: + logger.warning("Failed to stat %s before cleanup: %s", file_path, exc) + failed_files += 1 + continue + + try: + if file_path in active_log_files: + file_path.write_bytes(b"") + truncated_files += 1 + else: + file_path.unlink() + deleted_files += 1 + removed_bytes += size + except OSError as exc: + logger.warning("Failed to clean %s: %s", file_path, exc) + failed_files += 1 + + if target == self.TARGET_CACHE: + self._cleanup_empty_dirs(self._temp_dir) + self._temp_dir.mkdir(parents=True, exist_ok=True) + + logger.info( + "Storage cleanup finished: target=%s removed_bytes=%s deleted_files=%s truncated_files=%s failed_files=%s", + target, + removed_bytes, + deleted_files, + truncated_files, + failed_files, + ) + + return { + "removed_bytes": removed_bytes, + "processed_files": deleted_files + truncated_files, + "deleted_files": deleted_files, + "truncated_files": truncated_files, + "failed_files": failed_files, + } + + def _collect_log_files(self) -> set[Path]: + files = set(self._iter_files(self._data_dir / "logs")) + for log_path in self._configured_log_paths(): + files.update(self._iter_log_family_files(log_path)) + return files + + def _collect_cache_files(self) -> set[Path]: + files = set(self._iter_files(self._temp_dir)) + files.update(self._data_dir.glob("plugins_custom_*.json")) + + for extra_file in ( + self._data_dir / "plugins.json", + self._data_dir / "sandbox_skills_cache.json", + ): + if extra_file.is_file(): + files.add(extra_file) + + return files + + def _log_file_configs(self) -> list[LogFileConfig]: + return [ + LogFileConfig( + path=self._resolve_log_path( + self._get_optional_str("log_file_path"), + default_relative_path="logs/astrbot.log", + ), + enabled=self._get_bool("log_file_enable", False), + ), + LogFileConfig( + path=self._resolve_log_path( + self._get_optional_str("trace_log_path"), + default_relative_path="logs/astrbot.trace.log", + ), + enabled=self._get_bool("trace_log_enable", False), + ), + ] + + def _get_optional_str(self, key: str) -> str | None: + value = self._config.get(key) + return value if isinstance(value, str) else None + + def _get_bool(self, key: str, default: bool = False) -> bool: + value = self._config.get(key, default) + return value if isinstance(value, bool) else default + + def _configured_log_paths(self) -> set[Path]: + return {config.path for config in self._log_file_configs()} + + def _active_log_files(self) -> set[Path]: + return {config.path for config in self._log_file_configs() if config.enabled} + + def _resolve_log_path( + self, + configured_path: str | None, + *, + default_relative_path: str, + ) -> Path: + path_value = configured_path or default_relative_path + path = Path(path_value) + if path.is_absolute(): + return path.resolve() + return (self._data_dir / path).resolve() + + def _iter_log_family_files(self, log_path: Path) -> set[Path]: + files: set[Path] = set() + parent_dir = log_path.parent + if log_path.is_file(): + files.add(log_path) + if not parent_dir.exists(): + return files + + suffix = log_path.suffix + stem = log_path.stem if suffix else log_path.name + pattern = f"{stem}.*{suffix}" if suffix else f"{stem}.*" + + for candidate in parent_dir.glob(pattern): + if candidate.is_file() and candidate != log_path: + files.add(candidate) + + return files + + @staticmethod + def _iter_files(path: Path) -> Iterable[Path]: + if path.is_file(): + yield path + return + if not path.exists(): + return + for child in path.rglob("*"): + if child.is_file(): + yield child + + @staticmethod + def _summarize_files(files: Iterable[Path]) -> tuple[int, int]: + total_size = 0 + file_count = 0 + for file_path in files: + if not file_path.exists() or not file_path.is_file(): + continue + try: + total_size += file_path.stat().st_size + file_count += 1 + except OSError as exc: + logger.debug("Skip %s during storage scan: %s", file_path, exc) + return total_size, file_count + + @staticmethod + def _cleanup_empty_dirs(root_dir: Path) -> None: + if not root_dir.exists(): + return + for dirpath, dirnames, filenames in os.walk(root_dir, topdown=False): + path = Path(dirpath) + if path == root_dir: + continue + try: + path.rmdir() + except OSError: + continue diff --git a/astrbot/dashboard/routes/stat.py b/astrbot/dashboard/routes/stat.py index 532238ac7..a6f7ff7f2 100644 --- a/astrbot/dashboard/routes/stat.py +++ b/astrbot/dashboard/routes/stat.py @@ -1,3 +1,4 @@ +import asyncio import os import re import threading @@ -17,6 +18,7 @@ from astrbot.core.db import BaseDatabase from astrbot.core.db.migration.helper import check_migration_needed_v4 from astrbot.core.utils.astrbot_path import get_astrbot_path from astrbot.core.utils.io import get_dashboard_version +from astrbot.core.utils.storage_cleaner import StorageCleaner from astrbot.core.utils.version_comparator import VersionComparator from .route import Response, Route, RouteContext @@ -39,10 +41,13 @@ class StatRoute(Route): "/stat/changelog": ("GET", self.get_changelog), "/stat/changelog/list": ("GET", self.list_changelog_versions), "/stat/first-notice": ("GET", self.get_first_notice), + "/stat/storage": ("GET", self.get_storage_status), + "/stat/storage/cleanup": ("POST", self.cleanup_storage), } self.db_helper = db_helper self.register_routes() self.core_lifecycle = core_lifecycle + self.storage_cleaner = StorageCleaner(self.config) async def restart_core(self): if DEMO_MODE: @@ -89,6 +94,31 @@ class StatRoute(Route): async def get_start_time(self): return Response().ok({"start_time": self.core_lifecycle.start_time}).__dict__ + async def get_storage_status(self): + try: + status = await asyncio.to_thread(self.storage_cleaner.get_status) + return Response().ok(status).__dict__ + except Exception: + logger.error("获取存储占用失败", exc_info=True) + return ( + Response().error("获取存储占用失败,请查看后端日志了解详情。").__dict__ + ) + + async def cleanup_storage(self): + try: + data = await request.get_json(silent=True) + target = "all" + if isinstance(data, dict): + target = str(data.get("target", "all")) + + result = await asyncio.to_thread(self.storage_cleaner.cleanup, target) + return Response().ok(result).__dict__ + except ValueError as e: + return Response().error(str(e)).__dict__ + except Exception: + logger.error("清理存储失败", exc_info=True) + return Response().error("清理存储失败,请查看后端日志了解详情。").__dict__ + async def get_stat(self): offset_sec = request.args.get("offset_sec", 86400) offset_sec = int(offset_sec) diff --git a/dashboard/src/assets/mdi-subset/materialdesignicons-subset.css b/dashboard/src/assets/mdi-subset/materialdesignicons-subset.css index 52a052cfa..6117190cf 100644 --- a/dashboard/src/assets/mdi-subset/materialdesignicons-subset.css +++ b/dashboard/src/assets/mdi-subset/materialdesignicons-subset.css @@ -1,4 +1,4 @@ -/* Auto-generated MDI subset – 231 icons */ +/* Auto-generated MDI subset – 235 icons */ /* Do not edit manually. Run: pnpm run subset-icons */ @font-face { @@ -112,6 +112,10 @@ content: "\F09D1"; } +.mdi-broom::before { + content: "\F00E2"; +} + .mdi-bug::before { content: "\F00E4"; } @@ -300,6 +304,10 @@ content: "\F1640"; } +.mdi-database-refresh-outline::before { + content: "\F1634"; +} + .mdi-delete::before { content: "\F01B4"; } @@ -308,6 +316,10 @@ content: "\F09E7"; } +.mdi-delete-sweep-outline::before { + content: "\F0C62"; +} + .mdi-dots-hexagon::before { content: "\F15FF"; } @@ -728,6 +740,10 @@ content: "\F0A66"; } +.mdi-qrcode::before { + content: "\F0432"; +} + .mdi-refresh::before { content: "\F0450"; } diff --git a/dashboard/src/assets/mdi-subset/materialdesignicons-webfont-subset.woff b/dashboard/src/assets/mdi-subset/materialdesignicons-webfont-subset.woff index 85c2ed9fdfa09200ceb1892ce611e8570eb76b5e..32e3eea05b3a960cb13f7b1c20aefe3fd0dea83c 100644 GIT binary patch delta 16396 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 Q|G|6)05G@$rjvs}g~(sQm;e9( diff --git a/dashboard/src/assets/mdi-subset/materialdesignicons-webfont-subset.woff2 b/dashboard/src/assets/mdi-subset/materialdesignicons-webfont-subset.woff2 index 376e47ce72445b88b80adb0605c7e6319dcebd22..4b578d1d5c0155a5fa50e2d6881a881d09b4433a 100644 GIT binary patch literal 13348 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 literal 13068 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

e}-wIw&OMNDf;Y`8XQ&b$2Ir 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 diff --git a/dashboard/src/components/shared/StorageCleanupPanel.vue b/dashboard/src/components/shared/StorageCleanupPanel.vue new file mode 100644 index 000000000..84aeaeb05 --- /dev/null +++ b/dashboard/src/components/shared/StorageCleanupPanel.vue @@ -0,0 +1,241 @@ + + + + + diff --git a/dashboard/src/i18n/locales/en-US/features/settings.json b/dashboard/src/i18n/locales/en-US/features/settings.json index 19232125f..b497659ee 100644 --- a/dashboard/src/i18n/locales/en-US/features/settings.json +++ b/dashboard/src/i18n/locales/en-US/features/settings.json @@ -42,6 +42,40 @@ "title": "Backup & Restore", "subtitle": "Export or import all AstrBot data for easy migration to a new server", "button": "Backup Manager" + }, + "cleanup": { + "title": "Log & Cache Cleanup", + "subtitle": "Review disk usage for logs and caches, then clean them from the UI without shell commands.", + "refresh": "Refresh Usage", + "cleanAll": "Clean All", + "panel": { + "title": "Cleanup Details", + "subtitle": "Current usage: {size}" + }, + "fileCount": "{count} files", + "confirm": "Clean {target} now?", + "targetNames": { + "cache": "cache", + "logs": "logs", + "all": "logs and cache" + }, + "targets": { + "cache": { + "title": "Cache", + "subtitle": "Remove temporary files, plugin market cache, and skill cache.", + "button": "Clean Cache" + }, + "logs": { + "title": "Logs", + "subtitle": "Remove rotated logs and truncate the current active log files.", + "button": "Clean Logs" + } + }, + "messages": { + "statusFailed": "Failed to load storage usage", + "cleanupSuccess": "Cleared {count} files and freed {size}", + "cleanupFailed": "Cleanup failed" + } } }, "sidebar": { diff --git a/dashboard/src/i18n/locales/ru-RU/features/settings.json b/dashboard/src/i18n/locales/ru-RU/features/settings.json index 29b826fff..d1100435f 100644 --- a/dashboard/src/i18n/locales/ru-RU/features/settings.json +++ b/dashboard/src/i18n/locales/ru-RU/features/settings.json @@ -42,6 +42,40 @@ "title": "Резервное копирование", "subtitle": "Важнейший инструмент для безопасного переноса данных между серверами.", "button": "Управление бэкапами" + }, + "cleanup": { + "title": "Очистка логов и кэша", + "subtitle": "Показывает текущий размер логов и кэша и позволяет очистить их прямо из WebUI.", + "refresh": "Обновить", + "cleanAll": "Очистить все", + "panel": { + "title": "Детали очистки", + "subtitle": "Текущий размер: {size}" + }, + "fileCount": "{count} файлов", + "confirm": "Очистить {target}?", + "targetNames": { + "cache": "кэш", + "logs": "логи", + "all": "логи и кэш" + }, + "targets": { + "cache": { + "title": "Кэш", + "subtitle": "Удаляет временные файлы, кэш каталога плагинов и кэш навыков.", + "button": "Очистить кэш" + }, + "logs": { + "title": "Логи", + "subtitle": "Удаляет старые файлы логов и очищает текущие активные логи.", + "button": "Очистить логи" + } + }, + "messages": { + "statusFailed": "Не удалось получить размер хранилища", + "cleanupSuccess": "Очищено {count} файлов, освобождено {size}", + "cleanupFailed": "Ошибка очистки" + } } }, "sidebar": { @@ -177,4 +211,4 @@ "copyFailed": "Ошибка копирования" } } -} \ No newline at end of file +} diff --git a/dashboard/src/i18n/locales/zh-CN/features/settings.json b/dashboard/src/i18n/locales/zh-CN/features/settings.json index 19c1c7c41..092b4a5d9 100644 --- a/dashboard/src/i18n/locales/zh-CN/features/settings.json +++ b/dashboard/src/i18n/locales/zh-CN/features/settings.json @@ -42,6 +42,40 @@ "title": "数据备份与恢复", "subtitle": "导出或导入 AstrBot 的所有数据,方便迁移到新服务器", "button": "备份管理" + }, + "cleanup": { + "title": "日志与缓存清理", + "subtitle": "查看当前日志和缓存占用,并一键清理。", + "refresh": "刷新占用", + "cleanAll": "全部清理", + "panel": { + "title": "清理详情", + "subtitle": "当前占用 {size}" + }, + "fileCount": "{count} 个文件", + "confirm": "确定要清理 {target} 吗?", + "targetNames": { + "cache": "缓存", + "logs": "日志", + "all": "日志和缓存" + }, + "targets": { + "cache": { + "title": "缓存", + "subtitle": "清理临时文件、插件市场缓存和技能缓存。", + "button": "清理缓存" + }, + "logs": { + "title": "日志", + "subtitle": "清理历史日志,并清空当前正在写入的日志文件内容。", + "button": "清理日志" + } + }, + "messages": { + "statusFailed": "加载存储占用失败", + "cleanupSuccess": "已清理 {count} 个文件,释放 {size}", + "cleanupFailed": "清理失败" + } } }, "sidebar": { diff --git a/dashboard/src/views/Settings.vue b/dashboard/src/views/Settings.vue index 8ec447dac..9ddb4e1fe 100644 --- a/dashboard/src/views/Settings.vue +++ b/dashboard/src/views/Settings.vue @@ -1,6 +1,6 @@