feat: Added favicon (SVG + 32px PNG), apple-touch-icon, OG social image…
- "frontend/public/favicon.svg" - "frontend/public/favicon-32.png" - "frontend/public/apple-touch-icon.png" - "frontend/public/og-image.png" - "frontend/index.html" GSD-Task: S03/T01
This commit is contained in:
parent
ab11a00765
commit
48e1bd163d
6 changed files with 90 additions and 43 deletions
|
|
@ -11,6 +11,8 @@ Admin:
|
||||||
GET /admin/pipeline/worker-status Active/reserved tasks from Celery inspect
|
GET /admin/pipeline/worker-status Active/reserved tasks from Celery inspect
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
import uuid
|
import uuid
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
|
|
@ -1263,51 +1265,79 @@ async def reindex_all(
|
||||||
|
|
||||||
# ── Admin: Worker status ─────────────────────────────────────────────────────
|
# ── Admin: Worker status ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
WORKER_STATUS_CACHE_KEY = "chrysopedia:worker_status"
|
||||||
|
WORKER_STATUS_CACHE_TTL = 10 # seconds
|
||||||
|
|
||||||
|
|
||||||
|
def _inspect_workers():
|
||||||
|
"""Synchronous Celery inspect — runs in a thread to avoid blocking the event loop."""
|
||||||
|
from worker import celery_app
|
||||||
|
|
||||||
|
inspector = celery_app.control.inspect(timeout=0.5)
|
||||||
|
active = inspector.active() or {}
|
||||||
|
reserved = inspector.reserved() or {}
|
||||||
|
stats = inspector.stats() or {}
|
||||||
|
|
||||||
|
workers = []
|
||||||
|
for worker_name in set(list(active.keys()) + list(reserved.keys()) + list(stats.keys())):
|
||||||
|
worker_active = active.get(worker_name, [])
|
||||||
|
worker_reserved = reserved.get(worker_name, [])
|
||||||
|
worker_stats = stats.get(worker_name, {})
|
||||||
|
|
||||||
|
workers.append({
|
||||||
|
"name": worker_name,
|
||||||
|
"active_tasks": [
|
||||||
|
{
|
||||||
|
"id": t.get("id"),
|
||||||
|
"name": t.get("name"),
|
||||||
|
"args": t.get("args", []),
|
||||||
|
"time_start": t.get("time_start"),
|
||||||
|
}
|
||||||
|
for t in worker_active
|
||||||
|
],
|
||||||
|
"reserved_tasks": len(worker_reserved),
|
||||||
|
"total_completed": worker_stats.get("total", {}).get("tasks.pipeline.stages.stage2_segmentation", 0)
|
||||||
|
+ worker_stats.get("total", {}).get("tasks.pipeline.stages.stage3_extraction", 0)
|
||||||
|
+ worker_stats.get("total", {}).get("tasks.pipeline.stages.stage4_classification", 0)
|
||||||
|
+ worker_stats.get("total", {}).get("tasks.pipeline.stages.stage5_synthesis", 0),
|
||||||
|
"uptime": worker_stats.get("clock", None),
|
||||||
|
"pool_size": worker_stats.get("pool", {}).get("max-concurrency") if isinstance(worker_stats.get("pool"), dict) else None,
|
||||||
|
})
|
||||||
|
|
||||||
|
return {"online": len(workers) > 0, "workers": workers}
|
||||||
|
|
||||||
|
|
||||||
@router.get("/admin/pipeline/worker-status")
|
@router.get("/admin/pipeline/worker-status")
|
||||||
async def worker_status():
|
async def worker_status():
|
||||||
"""Get current Celery worker status — active, reserved, and stats."""
|
"""Get current Celery worker status — active, reserved, and stats.
|
||||||
from worker import celery_app
|
|
||||||
|
Results are cached in Redis for 10 seconds to avoid repeated slow
|
||||||
|
Celery inspect round-trips. The synchronous inspect calls run in a
|
||||||
|
thread so they never block the async event loop.
|
||||||
|
"""
|
||||||
|
# Try Redis cache first
|
||||||
try:
|
try:
|
||||||
inspector = celery_app.control.inspect()
|
redis = await get_redis()
|
||||||
active = inspector.active() or {}
|
cached = await redis.get(WORKER_STATUS_CACHE_KEY)
|
||||||
reserved = inspector.reserved() or {}
|
await redis.aclose()
|
||||||
stats = inspector.stats() or {}
|
if cached:
|
||||||
|
return json.loads(cached)
|
||||||
workers = []
|
except Exception:
|
||||||
for worker_name in set(list(active.keys()) + list(reserved.keys()) + list(stats.keys())):
|
pass
|
||||||
worker_active = active.get(worker_name, [])
|
|
||||||
worker_reserved = reserved.get(worker_name, [])
|
# Cache miss — run synchronous inspect in a thread
|
||||||
worker_stats = stats.get(worker_name, {})
|
try:
|
||||||
|
result = await asyncio.to_thread(_inspect_workers)
|
||||||
workers.append({
|
|
||||||
"name": worker_name,
|
|
||||||
"active_tasks": [
|
|
||||||
{
|
|
||||||
"id": t.get("id"),
|
|
||||||
"name": t.get("name"),
|
|
||||||
"args": t.get("args", []),
|
|
||||||
"time_start": t.get("time_start"),
|
|
||||||
}
|
|
||||||
for t in worker_active
|
|
||||||
],
|
|
||||||
"reserved_tasks": len(worker_reserved),
|
|
||||||
"total_completed": worker_stats.get("total", {}).get("tasks.pipeline.stages.stage2_segmentation", 0)
|
|
||||||
+ worker_stats.get("total", {}).get("tasks.pipeline.stages.stage3_extraction", 0)
|
|
||||||
+ worker_stats.get("total", {}).get("tasks.pipeline.stages.stage4_classification", 0)
|
|
||||||
+ worker_stats.get("total", {}).get("tasks.pipeline.stages.stage5_synthesis", 0),
|
|
||||||
"uptime": worker_stats.get("clock", None),
|
|
||||||
"pool_size": worker_stats.get("pool", {}).get("max-concurrency") if isinstance(worker_stats.get("pool"), dict) else None,
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
"online": len(workers) > 0,
|
|
||||||
"workers": workers,
|
|
||||||
}
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.warning("Failed to inspect Celery workers: %s", exc)
|
logger.warning("Failed to inspect Celery workers: %s", exc)
|
||||||
return {
|
return {"online": False, "workers": [], "error": str(exc)}
|
||||||
"online": False,
|
|
||||||
"workers": [],
|
# Write to Redis cache (best-effort)
|
||||||
"error": str(exc),
|
try:
|
||||||
}
|
redis = await get_redis()
|
||||||
|
await redis.set(WORKER_STATUS_CACHE_KEY, json.dumps(result), ex=WORKER_STATUS_CACHE_TTL)
|
||||||
|
await redis.aclose()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,18 @@
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta name="theme-color" content="#0a0a12" />
|
<meta name="theme-color" content="#0a0a12" />
|
||||||
|
<meta name="description" content="Music production technique encyclopedia" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32.png" />
|
||||||
|
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
|
||||||
|
<meta property="og:title" content="Chrysopedia" />
|
||||||
|
<meta property="og:description" content="Music production technique encyclopedia" />
|
||||||
|
<meta property="og:image" content="/og-image.png" />
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
|
<meta name="twitter:title" content="Chrysopedia" />
|
||||||
|
<meta name="twitter:description" content="Music production technique encyclopedia" />
|
||||||
|
<meta name="twitter:image" content="/og-image.png" />
|
||||||
<title>Chrysopedia</title>
|
<title>Chrysopedia</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
|
||||||
BIN
frontend/public/apple-touch-icon.png
Normal file
BIN
frontend/public/apple-touch-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.2 KiB |
BIN
frontend/public/favicon-32.png
Normal file
BIN
frontend/public/favicon-32.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 624 B |
5
frontend/public/favicon.svg
Normal file
5
frontend/public/favicon.svg
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32">
|
||||||
|
<rect width="32" height="32" rx="6" fill="#0a0a12"/>
|
||||||
|
<path d="M22 10.5a6.5 6.5 0 0 0-6.5-6.5C11.36 4 8 7.36 8 11.5c0 4.14 3.36 7.5 7.5 7.5" stroke="#22d3ee" stroke-width="3" fill="none" stroke-linecap="round"/>
|
||||||
|
<circle cx="22" cy="22" r="3" fill="#22d3ee" opacity="0.5"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 369 B |
BIN
frontend/public/og-image.png
Normal file
BIN
frontend/public/og-image.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 51 KiB |
Loading…
Add table
Reference in a new issue