diff --git a/apps/admin/services.py b/apps/admin/services.py index 76a1cc2..cc72d47 100644 --- a/apps/admin/services.py +++ b/apps/admin/services.py @@ -447,15 +447,9 @@ class FileService: page = max(page, 1) size = min(max(size, 1), 100) keyword = keyword.strip().lower() - status = self._normalize_file_view_preset_choice( - status, self.VIEW_PRESET_STATUS_VALUES - ) - file_type = self._normalize_file_view_preset_choice( - file_type, self.VIEW_PRESET_TYPE_VALUES - ) - health = self._normalize_file_view_preset_choice( - health, self.VIEW_PRESET_HEALTH_VALUES - ) + status = status.strip().lower() + file_type = file_type.strip().lower() + health = health.strip().lower() sort_by = self._normalize_sort_by(sort_by) reverse = sort_order.strip().lower() != "asc" @@ -496,27 +490,8 @@ class FileService: key=lambda item: self._get_sort_value(item, sort_by), reverse=reverse, ) - view_summary = self._build_file_view_summary( - files=enriched_files, - all_file_count=len(all_files), - filters={ - "keyword": keyword, - "status": status or "all", - "type": file_type or "all", - "health": health or "all", - "sortBy": sort_by, - "sort_by": sort_by, - "sortOrder": "desc" if reverse else "asc", - "sort_order": "desc" if reverse else "asc", - }, - ) offset = (page - 1) * size - return ( - enriched_files[offset : offset + size], - len(enriched_files), - summary, - view_summary, - ) + return enriched_files[offset : offset + size], len(enriched_files), summary def _empty_health_summary(self) -> dict[str, int]: return { @@ -553,189 +528,6 @@ class FileService: if "never_retrieved" in reasons: summary["neverRetrievedCount"] += 1 - def _build_file_view_summary( - self, - files: list[dict[str, Any]], - all_file_count: int, - filters: dict[str, Any], - ) -> dict[str, Any]: - view_counts = { - "totalFiles": len(files), - "activeCount": 0, - "expiredCount": 0, - "textCount": 0, - "fileCount": 0, - "chunkedCount": 0, - **self._empty_health_summary(), - "storageUsed": sum(item.get("size") or 0 for item in files), - "usedCount": sum(item.get("usedCount") or 0 for item in files), - } - - for item in files: - if item.get("isExpired"): - view_counts["expiredCount"] += 1 - else: - view_counts["activeCount"] += 1 - if item.get("isText"): - view_counts["textCount"] += 1 - else: - view_counts["fileCount"] += 1 - if item.get("isChunked"): - view_counts["chunkedCount"] += 1 - self._accumulate_health_summary(view_counts, item) - - cards = self._build_file_view_summary_cards(view_counts) - actions = self._build_file_view_summary_actions(cards) - strongest_severity = self._get_strongest_summary_severity(cards) - active_filter_count = sum( - [ - 1 if str(filters.get("keyword") or "").strip() else 0, - 1 if str(filters.get("status") or "all").strip() != "all" else 0, - 1 if str(filters.get("type") or "all").strip() != "all" else 0, - 1 if str(filters.get("health") or "all").strip() != "all" else 0, - ] - ) - - return { - "total": len(files), - "filteredTotal": len(files), - "filtered_total": len(files), - "allTotal": max(int(all_file_count or 0), 0), - "all_total": max(int(all_file_count or 0), 0), - "activeFilterCount": active_filter_count, - "active_filter_count": active_filter_count, - "hasFilters": active_filter_count > 0, - "has_filters": active_filter_count > 0, - "strongestSeverity": strongest_severity, - "strongest_severity": strongest_severity, - "filters": filters, - "summary": view_counts, - "healthSummary": view_counts, - "health_summary": view_counts, - "cards": cards, - "items": cards, - "actions": actions, - } - - def _build_file_view_summary_cards( - self, summary: dict[str, Any] - ) -> list[dict[str, Any]]: - candidates = [ - { - "key": "storage_issue", - "severity": "danger", - "priority": 100, - "count": summary["storageIssueCount"], - "health": "storage_issue", - }, - { - "key": "expired", - "severity": "danger" if summary["expiredCount"] >= 10 else "warning", - "priority": 90, - "count": summary["expiredCount"], - "health": "expired", - }, - { - "key": "expiring_soon", - "severity": "warning", - "priority": 80, - "count": summary["expiringSoonCount"], - "health": "expiring_soon", - }, - { - "key": "never_retrieved", - "severity": "neutral", - "priority": 60, - "count": summary["neverRetrievedCount"], - "health": "never_retrieved", - }, - { - "key": "permanent", - "severity": "neutral", - "priority": 40, - "count": summary["permanentCount"], - "health": "permanent", - }, - ] - cards = [ - self._build_file_view_summary_item( - key=item["key"], - severity=item["severity"], - priority=item["priority"], - count=item["count"], - health=item["health"], - ) - for item in candidates - if item["count"] > 0 - ] - - if not cards: - key = "empty" if summary["totalFiles"] == 0 else "healthy" - cards.append( - self._build_file_view_summary_item( - key=key, - severity="success" if summary["totalFiles"] > 0 else "neutral", - priority=10, - count=summary["totalFiles"], - health="healthy" if summary["totalFiles"] > 0 else "all", - ) - ) - - cards.sort(key=lambda item: (-item["priority"], item["key"])) - return cards[:4] - - def _build_file_view_summary_actions( - self, cards: list[dict[str, Any]] - ) -> list[dict[str, Any]]: - action_key_map = { - "storage_issue": "inspect_storage", - "expired": "cleanup_expired", - "expiring_soon": "extend_expiring", - "never_retrieved": "review_never_retrieved", - "permanent": "review_permanent", - "healthy": "monitor", - "empty": "clear_filters", - } - return [ - { - **item, - "sourceKey": item["key"], - "source_key": item["key"], - "suggestedAction": action_key_map.get(item["key"], "review"), - "suggested_action": action_key_map.get(item["key"], "review"), - } - for item in cards - if item["severity"] != "success" - ] - - def _build_file_view_summary_item( - self, - key: str, - severity: str, - priority: int, - count: int, - health: str, - ) -> dict[str, Any]: - return { - "key": key, - "severity": severity, - "priority": priority, - "count": max(int(count or 0), 0), - "actionType": "filter", - "action_type": "filter", - "health": health, - "targetHealth": health, - "target_health": health, - } - - def _get_strongest_summary_severity(self, items: list[dict[str, Any]]) -> str: - severity_order = {"danger": 3, "warning": 2, "neutral": 1, "success": 0} - return max( - (item["severity"] for item in items), - key=lambda severity: severity_order.get(severity, 0), - default="success", - ) - async def build_file_health_summary( self, file_codes: list[FileCodes], now: Optional[datetime] = None ) -> dict[str, int]: @@ -747,418 +539,6 @@ class FileService: self._accumulate_health_summary(summary, item) return summary - async def get_dashboard_maintenance_queue(self) -> dict[str, Any]: - file_codes = await FileCodes.all() - now = await get_now() - today_start = now.replace(hour=0, minute=0, second=0, microsecond=0) - today_size = sum( - file_code.size - for file_code in file_codes - if file_code.created_at and file_code.created_at >= today_start - ) - expired_count = 0 - for file_code in file_codes: - if await file_code.is_expired(): - expired_count += 1 - - health_summary = await self.build_file_health_summary(file_codes, now=now) - return self.build_dashboard_maintenance_queue( - health_summary=health_summary, - total_files=len(file_codes), - expired_count=expired_count, - today_size=today_size, - upload_size_limit=settings.uploadSize, - open_upload=settings.openUpload, - enable_chunk=settings.enableChunk, - max_save_seconds=settings.max_save_seconds, - ) - - def build_dashboard_operational_insights( - self, - health_summary: dict[str, int], - total_files: int, - expired_count: int, - today_size: int, - upload_size_limit: int, - open_upload: int, - enable_chunk: int, - max_save_seconds: int, - ) -> list[dict[str, Any]]: - def to_int(value: Any) -> int: - try: - return int(value or 0) - except (TypeError, ValueError): - return 0 - - total_files = to_int(total_files) - expired_count = to_int(expired_count) - today_size = to_int(today_size) - upload_size_limit = to_int(upload_size_limit) - open_upload = to_int(open_upload) - enable_chunk = to_int(enable_chunk) - max_save_seconds = to_int(max_save_seconds) - insights = [] - - if health_summary.get("storageIssueCount", 0) > 0: - insights.append( - self._build_dashboard_operational_insight( - key="storage_issue", - severity="danger", - priority=100, - count=health_summary["storageIssueCount"], - action_type="file_queue", - health="storage_issue", - ) - ) - - if expired_count > 0: - insights.append( - self._build_dashboard_operational_insight( - key="expired_cleanup", - severity="danger" if expired_count >= 10 else "warning", - priority=90, - count=expired_count, - action_type="file_queue", - health="expired", - ) - ) - - if health_summary.get("expiringSoonCount", 0) > 0: - insights.append( - self._build_dashboard_operational_insight( - key="expiring_soon", - severity="warning", - priority=80, - count=health_summary["expiringSoonCount"], - action_type="file_queue", - health="expiring_soon", - ) - ) - - never_retrieved_count = health_summary.get("neverRetrievedCount", 0) - if total_files > 0 and never_retrieved_count >= max(3, total_files // 5): - insights.append( - self._build_dashboard_operational_insight( - key="never_retrieved", - severity="neutral", - priority=60, - count=never_retrieved_count, - action_type="file_queue", - health="never_retrieved", - ) - ) - - if open_upload and max_save_seconds <= 0: - insights.append( - self._build_dashboard_operational_insight( - key="guest_upload_retention", - severity="warning", - priority=50, - count=1, - action_type="settings", - ) - ) - - if ( - upload_size_limit > 0 - and today_size >= upload_size_limit - and not enable_chunk - ): - insights.append( - self._build_dashboard_operational_insight( - key="chunking_disabled", - severity="neutral", - priority=40, - count=1, - action_type="settings", - ) - ) - - if not insights: - insights.append( - self._build_dashboard_operational_insight( - key="healthy", - severity="success", - priority=10, - count=total_files, - action_type="file_queue", - health="healthy", - ) - ) - - insights.sort(key=lambda item: (-item["priority"], item["key"])) - return insights[:4] - - def build_dashboard_maintenance_queue( - self, - health_summary: dict[str, int], - total_files: int, - expired_count: int, - today_size: int, - upload_size_limit: int, - open_upload: int, - enable_chunk: int, - max_save_seconds: int, - ) -> dict[str, Any]: - def to_int(value: Any) -> int: - try: - return int(value or 0) - except (TypeError, ValueError): - return 0 - - total_files = to_int(total_files) - expired_count = to_int(expired_count) - today_size = to_int(today_size) - upload_size_limit = to_int(upload_size_limit) - open_upload = to_int(open_upload) - enable_chunk = to_int(enable_chunk) - max_save_seconds = to_int(max_save_seconds) - items = [] - - if health_summary.get("storageIssueCount", 0) > 0: - items.append( - self._build_dashboard_maintenance_item( - key="storage_issue", - severity="danger", - category="storage", - priority=100, - count=health_summary["storageIssueCount"], - action_type="file_queue", - health="storage_issue", - suggested_action="inspect_storage", - ) - ) - - if expired_count > 0: - items.append( - self._build_dashboard_maintenance_item( - key="expired_cleanup", - severity="danger" if expired_count >= 10 else "warning", - category="retention", - priority=90, - count=expired_count, - action_type="file_queue", - health="expired", - suggested_action="cleanup_or_extend", - ) - ) - - if health_summary.get("expiringSoonCount", 0) > 0: - items.append( - self._build_dashboard_maintenance_item( - key="expiring_soon", - severity="warning", - category="retention", - priority=80, - count=health_summary["expiringSoonCount"], - action_type="file_queue", - health="expiring_soon", - suggested_action="extend_expiration", - ) - ) - - never_retrieved_count = health_summary.get("neverRetrievedCount", 0) - if never_retrieved_count > 0: - items.append( - self._build_dashboard_maintenance_item( - key="never_retrieved", - severity="neutral", - category="adoption", - priority=60, - count=never_retrieved_count, - action_type="file_queue", - health="never_retrieved", - suggested_action="review_usage", - ) - ) - - permanent_count = health_summary.get("permanentCount", 0) - if permanent_count > 0: - items.append( - self._build_dashboard_maintenance_item( - key="permanent_review", - severity="neutral", - category="retention", - priority=45, - count=permanent_count, - action_type="file_queue", - health="permanent", - suggested_action="review_retention", - ) - ) - - if open_upload and max_save_seconds <= 0: - items.append( - self._build_dashboard_maintenance_item( - key="guest_upload_retention", - severity="warning", - category="settings", - priority=70, - count=1, - action_type="settings", - suggested_action="set_retention_limit", - ) - ) - - if ( - upload_size_limit > 0 - and today_size >= upload_size_limit - and not enable_chunk - ): - items.append( - self._build_dashboard_maintenance_item( - key="chunking_disabled", - severity="neutral", - category="settings", - priority=40, - count=1, - action_type="settings", - suggested_action="enable_chunking", - ) - ) - - if not items: - items.append( - self._build_dashboard_maintenance_item( - key="healthy", - severity="success", - category="system", - priority=10, - count=total_files, - action_type="file_queue", - health="healthy", - suggested_action="monitor", - ) - ) - - items.sort(key=lambda item: (-item["priority"], item["key"])) - summary = self._build_dashboard_maintenance_summary(items) - return { - "items": items, - "maintenanceItems": items, - "maintenance_items": items, - "summary": summary, - "maintenanceSummary": summary, - "maintenance_summary": summary, - } - - def _build_dashboard_maintenance_item( - self, - key: str, - severity: str, - category: str, - priority: int, - count: int, - action_type: str, - suggested_action: str, - health: Optional[str] = None, - ) -> dict[str, Any]: - action = { - "type": action_type, - "actionType": action_type, - "action_type": action_type, - "suggestedAction": suggested_action, - "suggested_action": suggested_action, - } - if health: - action.update( - { - "health": health, - "targetHealth": health, - "target_health": health, - } - ) - - return { - "key": key, - "severity": severity, - "category": category, - "priority": priority, - "count": max(int(count or 0), 0), - "action": action, - "actionType": action_type, - "action_type": action_type, - "suggestedAction": suggested_action, - "suggested_action": suggested_action, - "targetHealth": health, - "target_health": health, - } - - def _build_dashboard_maintenance_summary( - self, items: list[dict[str, Any]] - ) -> dict[str, Any]: - severity_order = {"danger": 3, "warning": 2, "neutral": 1, "success": 0} - strongest_severity = max( - (item["severity"] for item in items), - key=lambda severity: severity_order.get(severity, 0), - default="success", - ) - actionable_items = [item for item in items if item["severity"] != "success"] - file_queue_count = sum( - item["count"] - for item in actionable_items - if item.get("actionType") == "file_queue" - ) - settings_count = sum( - item["count"] - for item in actionable_items - if item.get("actionType") == "settings" - ) - return { - "total": len(items), - "actionableCount": len(actionable_items), - "actionable_count": len(actionable_items), - "dangerCount": sum(1 for item in items if item["severity"] == "danger"), - "danger_count": sum(1 for item in items if item["severity"] == "danger"), - "warningCount": sum(1 for item in items if item["severity"] == "warning"), - "warning_count": sum(1 for item in items if item["severity"] == "warning"), - "successCount": sum(1 for item in items if item["severity"] == "success"), - "success_count": sum(1 for item in items if item["severity"] == "success"), - "neutralCount": sum(1 for item in items if item["severity"] == "neutral"), - "neutral_count": sum(1 for item in items if item["severity"] == "neutral"), - "fileQueueCount": file_queue_count, - "file_queue_count": file_queue_count, - "settingsCount": settings_count, - "settings_count": settings_count, - "strongestSeverity": strongest_severity, - "strongest_severity": strongest_severity, - } - - def _build_dashboard_operational_insight( - self, - key: str, - severity: str, - priority: int, - count: int, - action_type: str, - health: Optional[str] = None, - ) -> dict[str, Any]: - action = { - "type": action_type, - "actionType": action_type, - "action_type": action_type, - } - if health: - action.update( - { - "health": health, - "targetHealth": health, - "target_health": health, - } - ) - - return { - "key": key, - "severity": severity, - "priority": priority, - "count": max(int(count or 0), 0), - "action": action, - "actionType": action_type, - "action_type": action_type, - "targetHealth": health, - "target_health": health, - } - async def _build_admin_file_item( self, file_code: FileCodes, now: Optional[datetime] = None ) -> dict[str, Any]: diff --git a/apps/admin/views.py b/apps/admin/views.py index f0474b0..3ebf30c 100644 --- a/apps/admin/views.py +++ b/apps/admin/views.py @@ -99,32 +99,11 @@ async def dashboard(file_service: FileService = Depends(get_file_service)): today_codes = FileCodes.filter(created_at__gte=today_start) yesterday_file_codes = await yesterday_codes today_file_codes = await today_codes - today_size = sum([code.size for code in today_file_codes]) expired_count = 0 for file_code in all_codes: if await file_code.is_expired(): expired_count += 1 health_summary = await file_service.build_file_health_summary(all_codes, now=now) - operational_insights = file_service.build_dashboard_operational_insights( - health_summary=health_summary, - total_files=len(all_codes), - expired_count=expired_count, - today_size=today_size, - upload_size_limit=settings.uploadSize, - open_upload=settings.openUpload, - enable_chunk=settings.enableChunk, - max_save_seconds=settings.max_save_seconds, - ) - maintenance_queue = file_service.build_dashboard_maintenance_queue( - health_summary=health_summary, - total_files=len(all_codes), - expired_count=expired_count, - today_size=today_size, - upload_size_limit=settings.uploadSize, - open_upload=settings.openUpload, - enable_chunk=settings.enableChunk, - max_save_seconds=settings.max_save_seconds, - ) text_count = sum(1 for file_code in all_codes if file_code.text is not None) chunked_count = sum(1 for file_code in all_codes if file_code.is_chunked) @@ -149,7 +128,7 @@ async def dashboard(file_service: FileService = Depends(get_file_service)): "yesterdayCount": len(yesterday_file_codes), "yesterdaySize": str(sum([code.size for code in yesterday_file_codes])), "todayCount": len(today_file_codes), - "todaySize": str(today_size), + "todaySize": str(sum([code.size for code in today_file_codes])), "activeCount": len(all_codes) - expired_count, "expiredCount": expired_count, "textCount": text_count, @@ -163,15 +142,6 @@ async def dashboard(file_service: FileService = Depends(get_file_service)): "maxSaveSeconds": settings.max_save_seconds, **health_summary, "healthSummary": health_summary, - "operationalInsights": operational_insights, - "operational_insights": operational_insights, - "insights": operational_insights, - "maintenanceQueue": maintenance_queue, - "maintenance_queue": maintenance_queue, - "maintenanceItems": maintenance_queue["items"], - "maintenance_items": maintenance_queue["items"], - "maintenanceSummary": maintenance_queue["summary"], - "maintenance_summary": maintenance_queue["summary"], "topSuffixes": [ {"suffix": suffix, "count": count} for suffix, count in suffix_counter.most_common(8) @@ -186,13 +156,6 @@ async def dashboard(file_service: FileService = Depends(get_file_service)): ) -@admin_api.get("/dashboard/maintenance-queue") -async def dashboard_maintenance_queue( - file_service: FileService = Depends(get_file_service), -): - return APIResponse(detail=await file_service.get_dashboard_maintenance_queue()) - - @admin_api.get("/activities") async def admin_activities( limit: int = 20, @@ -374,7 +337,7 @@ async def file_list( ): page = max(page, 1) size = min(max(size, 1), 100) - files, total, summary, view_summary = await file_service.list_files( + files, total, summary = await file_service.list_files( page, size, keyword, @@ -391,12 +354,6 @@ async def file_list( "data": files, "total": total, "summary": summary, - "viewSummary": view_summary, - "view_summary": view_summary, - "currentViewSummary": view_summary, - "current_view_summary": view_summary, - "actionSummary": view_summary, - "action_summary": view_summary, } )