feat(storage): functional Storage dashboard (§A6)
Docker Build & Publish / Prune old image versions (push) Has been cancelled
Docker Build & Publish / build (push) Has been cancelled
Docker Build & Publish / push (push) Has been cancelled

Replace the "coming soon" stub with a real dashboard wired to
`GET /storage`. modern-sk visuals: a layered disk-capacity gauge (library
share vs other-used vs free), stat tiles (tracks/artists/albums/playtime/
footprint/avg size), per-format size bars, metadata-health badges, source
breakdown, a popularity-weighted top-genres cloud, and playful fun facts.

- types: full `StorageStats` shape + `toStorageStats` snake→camel mapper
- endpoint: re-point `getStorageStats` to `GET /storage` with transform
- lib: `formatLongDuration` for big playtime spans
- i18n: `storage.*` keys (en + ru)
- three list states (loading / error / empty) per the UI invariant

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Senko-san
2026-06-14 01:20:01 +03:00
parent 44c8d1870f
commit 808c52484c
7 changed files with 669 additions and 15 deletions
+33 -5
View File
@@ -96,12 +96,40 @@ export interface UploadResponse {
already_exists: boolean;
}
export interface StorageStats {
totalBytes: number;
usedBytes: number;
export interface StorageFormatBreakdown {
fileFormat: string;
trackCount: number;
albumCount: number;
artistCount: number;
totalSize: number;
}
export interface StorageGenreCount {
genre: string;
trackCount: number;
}
/** Capacity of the volume backing the media store. Absent for object-store
* backends (S3), which have no fixed disk to report. */
export interface StorageDiskUsage {
total: number;
used: number;
free: number;
}
export interface StorageStats {
totalTracks: number;
totalArtists: number;
totalAlbums: number;
/** Sum of every track's recorded file size (the library's footprint). */
totalSize: number;
totalDurationSeconds: number;
largestTrackSize: number;
earliestAdded?: string;
latestAdded?: string;
byFormat: StorageFormatBreakdown[];
byMetadataStatus: Record<string, number>;
bySource: Record<string, number>;
topGenres: StorageGenreCount[];
disk?: StorageDiskUsage;
}
export interface User {