feat: show source directories in library stats and settings UI
Add active source root directories to the library stats endpoint and display them in the settings page. Hardcode container PHOTO_DIRS to /photos since the volume mount handles host path mapping. Add .env to .gitignore to prevent committing secrets. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -34,6 +34,7 @@ dist-ssr/
|
||||
.DS_Store
|
||||
|
||||
# Environment
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.database import get_db
|
||||
from app.models import Photo
|
||||
from app.models.folders import SourceRoot
|
||||
from app.models.user import User
|
||||
from app.dependencies import get_current_user
|
||||
|
||||
@@ -105,6 +106,13 @@ async def get_library_stats(
|
||||
|
||||
size = (await db.execute(select(func.sum(Photo.file_size)).where(owner))).scalar() or 0
|
||||
|
||||
# Source root directories (active ones only).
|
||||
roots = (
|
||||
await db.execute(
|
||||
select(SourceRoot.path).where(SourceRoot.is_active.is_(True)).order_by(SourceRoot.path)
|
||||
)
|
||||
).scalars().all()
|
||||
|
||||
return {
|
||||
"all_photos": all_photos_count,
|
||||
"rated": rated_count,
|
||||
@@ -116,6 +124,7 @@ async def get_library_stats(
|
||||
"total_videos": video_count,
|
||||
"total_size": size,
|
||||
"total_size_gb": round(size / (1024**3), 2) if size else 0,
|
||||
"source_dirs": roots,
|
||||
}
|
||||
|
||||
@router.post("/scan")
|
||||
|
||||
@@ -48,7 +48,7 @@ services:
|
||||
- REDIS_URL=redis://redis:6379
|
||||
- CELERY_BROKER_URL=redis://redis:6379
|
||||
- CELERY_RESULT_BACKEND=redis://redis:6379
|
||||
- PHOTO_DIRS=${PHOTO_DIRS:-/photos}
|
||||
- PHOTO_DIRS=/photos
|
||||
- ALLOWED_ORIGINS=${ALLOWED_ORIGINS:-*}
|
||||
- SECRET_KEY=${SECRET_KEY:-mulita-dev-secret-change-me}
|
||||
- ACCESS_TOKEN_EXPIRE_MINUTES=${ACCESS_TOKEN_EXPIRE_MINUTES:-60}
|
||||
@@ -104,7 +104,7 @@ services:
|
||||
- REDIS_URL=redis://redis:6379
|
||||
- CELERY_BROKER_URL=redis://redis:6379
|
||||
- CELERY_RESULT_BACKEND=redis://redis:6379
|
||||
- PHOTO_DIRS=${PHOTO_DIRS:-/photos}
|
||||
- PHOTO_DIRS=/photos
|
||||
- LOG_LEVEL=${LOG_LEVEL:-INFO}
|
||||
- TZ=${TZ:-UTC}
|
||||
# NullPool — see app/database.py for rationale.
|
||||
@@ -138,7 +138,7 @@ services:
|
||||
- REDIS_URL=redis://redis:6379
|
||||
- CELERY_BROKER_URL=redis://redis:6379
|
||||
- CELERY_RESULT_BACKEND=redis://redis:6379
|
||||
- PHOTO_DIRS=${PHOTO_DIRS:-/photos}
|
||||
- PHOTO_DIRS=/photos
|
||||
- LOG_LEVEL=${LOG_LEVEL:-INFO}
|
||||
- TZ=${TZ:-UTC}
|
||||
- MULITA_CELERY_WORKER=1
|
||||
@@ -170,7 +170,7 @@ services:
|
||||
- REDIS_URL=redis://redis:6379
|
||||
- CELERY_BROKER_URL=redis://redis:6379
|
||||
- CELERY_RESULT_BACKEND=redis://redis:6379
|
||||
- PHOTO_DIRS=${PHOTO_DIRS:-/photos}
|
||||
- PHOTO_DIRS=/photos
|
||||
- LOG_LEVEL=${LOG_LEVEL:-INFO}
|
||||
- TZ=${TZ:-UTC}
|
||||
- MULITA_CELERY_WORKER=1
|
||||
|
||||
@@ -258,6 +258,17 @@ export function SettingsPage() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{(libStats?.source_dirs?.length ?? 0) > 0 && (
|
||||
<div className="mt-3 text-xs text-text-muted">
|
||||
<span className="font-medium text-text">Source folders</span>
|
||||
<div className="mt-1 space-y-0.5">
|
||||
{libStats!.source_dirs.map((dir: string) => (
|
||||
<div key={dir} className="truncate font-mono" title={dir}>{dir}</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Inline scan progress. Rendered as a full-width sub-card
|
||||
when a scan is active so the user sees the same info
|
||||
they'd get from the floating widget without leaving
|
||||
|
||||
@@ -625,6 +625,7 @@ export interface LibraryStats {
|
||||
total_videos: number
|
||||
total_size: number
|
||||
total_size_gb: number
|
||||
source_dirs: string[]
|
||||
}
|
||||
|
||||
// Heaps API
|
||||
|
||||
Reference in New Issue
Block a user