cc-dashboard/src/main.py
Vadym Samoilenko 36118cb759 feat: replace Planka with in-app Kanban + add OMG page
- Remove Planka: docker-compose services, apache /board/ proxy, env vars, custom CSS dir
- Add Kanban board at /tasks: 4 columns (To Do / Doing / Testing / Done),
  native HTML5 drag-and-drop, card modal (TaskForm reuse), per-column "+" button
- Add 'testing' status to Task model validator and frontend union type
- Add GET /api/tasks/{id} endpoint (was missing, frontend already called it)
- Enrich DevOps clone: live-fetches description, AC, assignee, iteration,
  comments and attachments from ADO; renders as Markdown in task.notes
- Add /omg page: standalone project/client/job# registry with inline editing
  and create/edit/delete dialog; backed by new omg_entries table (migration 0008)
- Add omg router to main.py; add OMG + Tasks to sidebar and router
- Fix dead /planner link on Dashboard -> /tasks

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 14:09:36 +01:00

122 lines
3.3 KiB
Python

import logging
from contextlib import asynccontextmanager
from pathlib import Path
import structlog
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse, JSONResponse
from fastapi.staticfiles import StaticFiles
from src.config import settings
STATIC_DIR = Path("src/static")
def _ensure_static_dir() -> None:
"""Create src/static with a stub index.html if the frontend hasn't been built yet."""
STATIC_DIR.mkdir(parents=True, exist_ok=True)
idx = STATIC_DIR / "index.html"
if not idx.exists():
idx.write_text(
"<html><body><h2>Frontend not built. Run: cd web && npm run build</h2></body></html>"
)
from src.middleware.logging import LoggingMiddleware
from src.routers import admin, auth, dashboard, events, ingest, keys, projects
from src.routers import calendar, tasks, manual_entries, budgets, tags, devops, exports, reports, omg
from src.services.scheduler import scheduler, setup_scheduler
BASE = settings.BASE_PATH
def _configure_logging() -> None:
shared_processors = [
structlog.contextvars.merge_contextvars,
structlog.stdlib.add_log_level,
structlog.processors.TimeStamper(fmt="iso"),
]
if settings.LOG_FORMAT == "json":
renderer = structlog.processors.JSONRenderer()
else:
renderer = structlog.dev.ConsoleRenderer(colors=True)
structlog.configure(
processors=shared_processors + [renderer],
wrapper_class=structlog.make_filtering_bound_logger(logging.INFO),
context_class=dict,
logger_factory=structlog.PrintLoggerFactory(),
cache_logger_on_first_use=True,
)
@asynccontextmanager
async def lifespan(app: FastAPI):
_configure_logging()
_ensure_static_dir()
setup_scheduler()
scheduler.start()
yield
scheduler.shutdown()
app = FastAPI(
title=settings.APP_TITLE,
docs_url=f"{BASE}/docs" if settings.DEBUG else None,
redoc_url=None,
root_path=BASE,
lifespan=lifespan,
)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
app.add_middleware(LoggingMiddleware)
for router in [
auth.router,
keys.router,
admin.router,
ingest.router,
dashboard.router,
events.router,
projects.router,
calendar.router,
tasks.router,
manual_entries.router,
budgets.router,
tags.router,
devops.router,
exports.router,
reports.router,
omg.router,
]:
app.include_router(router)
@app.get("/healthz", include_in_schema=False)
async def health():
return {"status": "ok"}
app.mount("/static", StaticFiles(directory="src/static"), name="static")
_NO_CACHE = {"Cache-Control": "no-cache, no-store, must-revalidate"}
@app.get("/", include_in_schema=False)
@app.get("", include_in_schema=False)
async def spa_root():
return FileResponse("src/static/index.html", headers=_NO_CACHE)
@app.get("/{path:path}", include_in_schema=False)
async def spa_fallback(path: str, request: Request):
if path.startswith("api/") or path.startswith("static/"):
from fastapi import HTTPException
raise HTTPException(status_code=404)
return FileResponse("src/static/index.html", headers=_NO_CACHE)