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
|
.DS_Store
|
||||||
|
|
||||||
# Environment
|
# Environment
|
||||||
|
.env
|
||||||
.env.local
|
.env.local
|
||||||
.env.*.local
|
.env.*.local
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|||||||
|
|
||||||
from app.database import get_db
|
from app.database import get_db
|
||||||
from app.models import Photo
|
from app.models import Photo
|
||||||
|
from app.models.folders import SourceRoot
|
||||||
from app.models.user import User
|
from app.models.user import User
|
||||||
from app.dependencies import get_current_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
|
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 {
|
return {
|
||||||
"all_photos": all_photos_count,
|
"all_photos": all_photos_count,
|
||||||
"rated": rated_count,
|
"rated": rated_count,
|
||||||
@@ -116,6 +124,7 @@ async def get_library_stats(
|
|||||||
"total_videos": video_count,
|
"total_videos": video_count,
|
||||||
"total_size": size,
|
"total_size": size,
|
||||||
"total_size_gb": round(size / (1024**3), 2) if size else 0,
|
"total_size_gb": round(size / (1024**3), 2) if size else 0,
|
||||||
|
"source_dirs": roots,
|
||||||
}
|
}
|
||||||
|
|
||||||
@router.post("/scan")
|
@router.post("/scan")
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ services:
|
|||||||
- REDIS_URL=redis://redis:6379
|
- REDIS_URL=redis://redis:6379
|
||||||
- CELERY_BROKER_URL=redis://redis:6379
|
- CELERY_BROKER_URL=redis://redis:6379
|
||||||
- CELERY_RESULT_BACKEND=redis://redis:6379
|
- CELERY_RESULT_BACKEND=redis://redis:6379
|
||||||
- PHOTO_DIRS=${PHOTO_DIRS:-/photos}
|
- PHOTO_DIRS=/photos
|
||||||
- ALLOWED_ORIGINS=${ALLOWED_ORIGINS:-*}
|
- ALLOWED_ORIGINS=${ALLOWED_ORIGINS:-*}
|
||||||
- SECRET_KEY=${SECRET_KEY:-mulita-dev-secret-change-me}
|
- SECRET_KEY=${SECRET_KEY:-mulita-dev-secret-change-me}
|
||||||
- ACCESS_TOKEN_EXPIRE_MINUTES=${ACCESS_TOKEN_EXPIRE_MINUTES:-60}
|
- ACCESS_TOKEN_EXPIRE_MINUTES=${ACCESS_TOKEN_EXPIRE_MINUTES:-60}
|
||||||
@@ -104,7 +104,7 @@ services:
|
|||||||
- REDIS_URL=redis://redis:6379
|
- REDIS_URL=redis://redis:6379
|
||||||
- CELERY_BROKER_URL=redis://redis:6379
|
- CELERY_BROKER_URL=redis://redis:6379
|
||||||
- CELERY_RESULT_BACKEND=redis://redis:6379
|
- CELERY_RESULT_BACKEND=redis://redis:6379
|
||||||
- PHOTO_DIRS=${PHOTO_DIRS:-/photos}
|
- PHOTO_DIRS=/photos
|
||||||
- LOG_LEVEL=${LOG_LEVEL:-INFO}
|
- LOG_LEVEL=${LOG_LEVEL:-INFO}
|
||||||
- TZ=${TZ:-UTC}
|
- TZ=${TZ:-UTC}
|
||||||
# NullPool — see app/database.py for rationale.
|
# NullPool — see app/database.py for rationale.
|
||||||
@@ -138,7 +138,7 @@ services:
|
|||||||
- REDIS_URL=redis://redis:6379
|
- REDIS_URL=redis://redis:6379
|
||||||
- CELERY_BROKER_URL=redis://redis:6379
|
- CELERY_BROKER_URL=redis://redis:6379
|
||||||
- CELERY_RESULT_BACKEND=redis://redis:6379
|
- CELERY_RESULT_BACKEND=redis://redis:6379
|
||||||
- PHOTO_DIRS=${PHOTO_DIRS:-/photos}
|
- PHOTO_DIRS=/photos
|
||||||
- LOG_LEVEL=${LOG_LEVEL:-INFO}
|
- LOG_LEVEL=${LOG_LEVEL:-INFO}
|
||||||
- TZ=${TZ:-UTC}
|
- TZ=${TZ:-UTC}
|
||||||
- MULITA_CELERY_WORKER=1
|
- MULITA_CELERY_WORKER=1
|
||||||
@@ -170,7 +170,7 @@ services:
|
|||||||
- REDIS_URL=redis://redis:6379
|
- REDIS_URL=redis://redis:6379
|
||||||
- CELERY_BROKER_URL=redis://redis:6379
|
- CELERY_BROKER_URL=redis://redis:6379
|
||||||
- CELERY_RESULT_BACKEND=redis://redis:6379
|
- CELERY_RESULT_BACKEND=redis://redis:6379
|
||||||
- PHOTO_DIRS=${PHOTO_DIRS:-/photos}
|
- PHOTO_DIRS=/photos
|
||||||
- LOG_LEVEL=${LOG_LEVEL:-INFO}
|
- LOG_LEVEL=${LOG_LEVEL:-INFO}
|
||||||
- TZ=${TZ:-UTC}
|
- TZ=${TZ:-UTC}
|
||||||
- MULITA_CELERY_WORKER=1
|
- MULITA_CELERY_WORKER=1
|
||||||
|
|||||||
@@ -258,6 +258,17 @@ export function SettingsPage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</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
|
{/* Inline scan progress. Rendered as a full-width sub-card
|
||||||
when a scan is active so the user sees the same info
|
when a scan is active so the user sees the same info
|
||||||
they'd get from the floating widget without leaving
|
they'd get from the floating widget without leaving
|
||||||
|
|||||||
@@ -625,6 +625,7 @@ export interface LibraryStats {
|
|||||||
total_videos: number
|
total_videos: number
|
||||||
total_size: number
|
total_size: number
|
||||||
total_size_gb: number
|
total_size_gb: number
|
||||||
|
source_dirs: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Heaps API
|
// Heaps API
|
||||||
|
|||||||
Reference in New Issue
Block a user