feat: Projects tab, 4-tab dashboard, full knowledge browser
All checks were successful
Build and Deploy DevOpsDash / build-image (push) Successful in 8s
All checks were successful
Build and Deploy DevOpsDash / build-image (push) Successful in 8s
- Add projects router (Redis projects + workcontexts merged)
- Register projects router in main.py
- Extend knowledge router: howtos, agents, skills via MCP proxy
- Extend mcp_client: list/get howtos, agents, skills
- Rewrite dashboard.html: 4 tabs (Taskz/Worklog/Projects/Knowledge)
- Taskz: sidebar board list + Kanban columns with task modal
- Worklog: context/days picker + standup button
- Projects: context filter sidebar + work context display
- Knowledge: 6 sub-tabs (docs/howtos/agents/skills/adrs/memories)
with markdown rendering via marked.js
This commit is contained in:
61
app/routers/projects.py
Normal file
61
app/routers/projects.py
Normal file
@@ -0,0 +1,61 @@
|
||||
"""Projects router — registered git repos + work contexts from Redis."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter
|
||||
|
||||
from app.redis_client import get_redis, list_projects, list_workcontexts, PROJECT_KEY_PREFIX, WORKCONTEXT_KEY_PREFIX
|
||||
|
||||
router = APIRouter(prefix="/api/v1", tags=["projects"])
|
||||
|
||||
|
||||
@router.get("/projects")
|
||||
def api_list_projects(context: Optional[str] = None, q: Optional[str] = None):
|
||||
r = get_redis()
|
||||
projects = list_projects(r, context=context)
|
||||
|
||||
# Merge in work contexts (keyed by path)
|
||||
wc_keys = r.keys(f"{WORKCONTEXT_KEY_PREFIX}*")
|
||||
import json
|
||||
work_contexts: dict = {}
|
||||
for key in wc_keys:
|
||||
raw = r.get(key)
|
||||
if raw:
|
||||
try:
|
||||
path = key[len(WORKCONTEXT_KEY_PREFIX):]
|
||||
work_contexts[path] = json.loads(raw)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Attach work context and apply search filter
|
||||
result = []
|
||||
for p in projects:
|
||||
path = p.get("path", "")
|
||||
p["work_context"] = work_contexts.get(path)
|
||||
if q and q.lower() not in (p.get("name", "") + p.get("path", "") + " ".join(p.get("tags", []))).lower():
|
||||
continue
|
||||
result.append(p)
|
||||
|
||||
# Sort: projects with recent work context first
|
||||
result.sort(
|
||||
key=lambda p: (
|
||||
p["work_context"]["saved_at"] if p.get("work_context") else "",
|
||||
),
|
||||
reverse=True,
|
||||
)
|
||||
|
||||
# Context breakdown
|
||||
contexts = {}
|
||||
for p in result:
|
||||
c = p.get("context", "unknown")
|
||||
contexts[c] = contexts.get(c, 0) + 1
|
||||
|
||||
return {"projects": result, "total": len(result), "contexts": contexts}
|
||||
|
||||
|
||||
@router.get("/workcontexts")
|
||||
def api_list_workcontexts():
|
||||
r = get_redis()
|
||||
return {"contexts": list_workcontexts(r)}
|
||||
Reference in New Issue
Block a user