feat(admin): DB-driven storage sum + grouped error log viewer

Storage:
- The dashboard storage card used to walk Storage::disk('public')'s
  videos / thumbnails / avatars / images directories. NAS is primary
  storage, so for every file that's been pushed-and-purged (most of
  them, per CLAUDE.md), the disk walk reports 0. Replace with a
  DB-driven estimate: SUM(videos.size) for video bytes plus a 120 KB
  conservative per-file estimate × (thumbnails + avatars + banners +
  post images). Fast (3 count queries + one SUM), works regardless
  of local-cache state, and now reflects content that lives on NAS.

Error logs:
- Flat view used to render every line as its own equal-weight <div>,
  so a stack trace of 70 frames buried the next real event. Group
  lines by header: a line that matches .(ERROR|WARNING|INFO|DEBUG|
  CRITICAL|…): starts a new group; subsequent lines belong to its
  stack trace. Each group collapses by default behind a caret the
  admin can expand. Stack-trace lines preserve their natural #0 → #N
  order inside the group even when the file is reversed newest-first.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
ghassanandClaude Opus 4.7 committed 2026-10-21 12:02:03 +03:00
1 parent 05ccb45fe4
commit 5adafc7616
2 files changed
+74 -15

No files matched your search

+18 -7
View File
@@ -188,16 +188,27 @@ class SuperAdminController extends Controller
$recentVideos = Video::with('user')->latest()->take(5)->get();
// ── Storage ───────────────────────────────────────────────
$disk = Storage::disk('public');
// NAS is the primary store; walking the local disk here would miss
// every file that's been pushed to NAS and purged locally. We use
// the DB-captured file sizes instead: videos.size is set at upload
// time, and thumbnail / avatar counts × a rough per-file average
// give us a usable picture. True NAS-side bytes could be queried
// with `smbclient du`, but that's slow and locks the dashboard; the
// DB figure is accurate for everything uploaded through this app.
$sizeVideos = (int) Video::sum('size'); // bytes
$thumbnailCount = Video::whereNotNull('thumbnail')->count();
$avatarCount = User::whereNotNull('avatar')->count();
$bannerCount = User::whereNotNull('banner')->count();
$postImageCount = \DB::table('post_images')->count();
$sizeVideos = $this->dirSize($disk, 'videos');
$sizeThumbnails = $this->dirSize($disk, 'thumbnails');
$sizeAvatars = $this->dirSize($disk, 'avatars');
$sizeImages = $this->dirSize($disk, 'images');
$totalPublicSize = $sizeVideos + $sizeThumbnails + $sizeAvatars + $sizeImages;
// Images are variable but ~120 KB is a solid conservative estimate
// for a web-sized JPEG/PNG upload.
$sizeImages = ($thumbnailCount + $avatarCount + $bannerCount + $postImageCount) * 120 * 1024;
$sizeThumbnails = $thumbnailCount * 120 * 1024;
$sizeAvatars = $avatarCount * 120 * 1024;
$totalPublicSize = $sizeVideos + $sizeImages;
$videosUsagePercent = $totalPublicSize > 0 ? round(($sizeVideos / $totalPublicSize) * 100, 1) : 0;
// Convert to MB
$toMb = fn($b) => round($b / 1024 / 1024, 1);
$storage = [
'videos' => $toMb($sizeVideos),