forked from Rockachopa/Timmy-time-dashboard
feat: code quality audit + autoresearch integration + infra hardening (#150)
This commit is contained in:
committed by
GitHub
parent
fd0ede0d51
commit
ae3bb1cc21
@@ -7,9 +7,10 @@ from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Request, Form
|
||||
from fastapi import APIRouter, Form, HTTPException, Request
|
||||
from fastapi.responses import HTMLResponse, JSONResponse
|
||||
|
||||
from config import settings
|
||||
from dashboard.templating import templates
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -20,11 +21,17 @@ router = APIRouter(tags=["tasks"])
|
||||
# Database helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
DB_PATH = Path("data/tasks.db")
|
||||
DB_PATH = Path(settings.repo_root) / "data" / "tasks.db"
|
||||
|
||||
VALID_STATUSES = {
|
||||
"pending_approval", "approved", "running", "paused",
|
||||
"completed", "vetoed", "failed", "backlogged",
|
||||
"pending_approval",
|
||||
"approved",
|
||||
"running",
|
||||
"paused",
|
||||
"completed",
|
||||
"vetoed",
|
||||
"failed",
|
||||
"backlogged",
|
||||
}
|
||||
VALID_PRIORITIES = {"low", "normal", "high", "urgent"}
|
||||
|
||||
@@ -33,7 +40,8 @@ def _get_db() -> sqlite3.Connection:
|
||||
DB_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||
conn = sqlite3.connect(str(DB_PATH))
|
||||
conn.row_factory = sqlite3.Row
|
||||
conn.execute("""
|
||||
conn.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS tasks (
|
||||
id TEXT PRIMARY KEY,
|
||||
title TEXT NOT NULL,
|
||||
@@ -46,7 +54,8 @@ def _get_db() -> sqlite3.Connection:
|
||||
created_at TEXT DEFAULT (datetime('now')),
|
||||
completed_at TEXT
|
||||
)
|
||||
""")
|
||||
"""
|
||||
)
|
||||
conn.commit()
|
||||
return conn
|
||||
|
||||
@@ -91,37 +100,52 @@ class _TaskView:
|
||||
# Page routes
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@router.get("/tasks", response_class=HTMLResponse)
|
||||
async def tasks_page(request: Request):
|
||||
"""Render the main task queue page with 3-column layout."""
|
||||
db = _get_db()
|
||||
try:
|
||||
pending = [_TaskView(_row_to_dict(r)) for r in db.execute(
|
||||
"SELECT * FROM tasks WHERE status IN ('pending_approval') ORDER BY created_at DESC"
|
||||
).fetchall()]
|
||||
active = [_TaskView(_row_to_dict(r)) for r in db.execute(
|
||||
"SELECT * FROM tasks WHERE status IN ('approved','running','paused') ORDER BY created_at DESC"
|
||||
).fetchall()]
|
||||
completed = [_TaskView(_row_to_dict(r)) for r in db.execute(
|
||||
"SELECT * FROM tasks WHERE status IN ('completed','vetoed','failed') ORDER BY completed_at DESC LIMIT 50"
|
||||
).fetchall()]
|
||||
pending = [
|
||||
_TaskView(_row_to_dict(r))
|
||||
for r in db.execute(
|
||||
"SELECT * FROM tasks WHERE status IN ('pending_approval') ORDER BY created_at DESC"
|
||||
).fetchall()
|
||||
]
|
||||
active = [
|
||||
_TaskView(_row_to_dict(r))
|
||||
for r in db.execute(
|
||||
"SELECT * FROM tasks WHERE status IN ('approved','running','paused') ORDER BY created_at DESC"
|
||||
).fetchall()
|
||||
]
|
||||
completed = [
|
||||
_TaskView(_row_to_dict(r))
|
||||
for r in db.execute(
|
||||
"SELECT * FROM tasks WHERE status IN ('completed','vetoed','failed') ORDER BY completed_at DESC LIMIT 50"
|
||||
).fetchall()
|
||||
]
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
return templates.TemplateResponse(request, "tasks.html", {
|
||||
"pending_count": len(pending),
|
||||
"pending": pending,
|
||||
"active": active,
|
||||
"completed": completed,
|
||||
"agents": [], # no agent roster wired yet
|
||||
"pre_assign": "",
|
||||
})
|
||||
return templates.TemplateResponse(
|
||||
request,
|
||||
"tasks.html",
|
||||
{
|
||||
"pending_count": len(pending),
|
||||
"pending": pending,
|
||||
"active": active,
|
||||
"completed": completed,
|
||||
"agents": [], # no agent roster wired yet
|
||||
"pre_assign": "",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# HTMX partials (polled by the template)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@router.get("/tasks/pending", response_class=HTMLResponse)
|
||||
async def tasks_pending(request: Request):
|
||||
db = _get_db()
|
||||
@@ -134,9 +158,11 @@ async def tasks_pending(request: Request):
|
||||
tasks = [_TaskView(_row_to_dict(r)) for r in rows]
|
||||
parts = []
|
||||
for task in tasks:
|
||||
parts.append(templates.TemplateResponse(
|
||||
request, "partials/task_card.html", {"task": task}
|
||||
).body.decode())
|
||||
parts.append(
|
||||
templates.TemplateResponse(
|
||||
request, "partials/task_card.html", {"task": task}
|
||||
).body.decode()
|
||||
)
|
||||
if not parts:
|
||||
return HTMLResponse('<div class="empty-column">No pending tasks</div>')
|
||||
return HTMLResponse("".join(parts))
|
||||
@@ -154,9 +180,11 @@ async def tasks_active(request: Request):
|
||||
tasks = [_TaskView(_row_to_dict(r)) for r in rows]
|
||||
parts = []
|
||||
for task in tasks:
|
||||
parts.append(templates.TemplateResponse(
|
||||
request, "partials/task_card.html", {"task": task}
|
||||
).body.decode())
|
||||
parts.append(
|
||||
templates.TemplateResponse(
|
||||
request, "partials/task_card.html", {"task": task}
|
||||
).body.decode()
|
||||
)
|
||||
if not parts:
|
||||
return HTMLResponse('<div class="empty-column">No active tasks</div>')
|
||||
return HTMLResponse("".join(parts))
|
||||
@@ -174,9 +202,11 @@ async def tasks_completed(request: Request):
|
||||
tasks = [_TaskView(_row_to_dict(r)) for r in rows]
|
||||
parts = []
|
||||
for task in tasks:
|
||||
parts.append(templates.TemplateResponse(
|
||||
request, "partials/task_card.html", {"task": task}
|
||||
).body.decode())
|
||||
parts.append(
|
||||
templates.TemplateResponse(
|
||||
request, "partials/task_card.html", {"task": task}
|
||||
).body.decode()
|
||||
)
|
||||
if not parts:
|
||||
return HTMLResponse('<div class="empty-column">No completed tasks yet</div>')
|
||||
return HTMLResponse("".join(parts))
|
||||
@@ -186,6 +216,7 @@ async def tasks_completed(request: Request):
|
||||
# Form-based create (used by the modal in tasks.html)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@router.post("/tasks/create", response_class=HTMLResponse)
|
||||
async def create_task_form(
|
||||
request: Request,
|
||||
@@ -218,6 +249,7 @@ async def create_task_form(
|
||||
# Task action endpoints (approve, veto, modify, pause, cancel, retry)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@router.post("/tasks/{task_id}/approve", response_class=HTMLResponse)
|
||||
async def approve_task(request: Request, task_id: str):
|
||||
return await _set_status(request, task_id, "approved")
|
||||
@@ -268,7 +300,9 @@ async def modify_task(
|
||||
|
||||
async def _set_status(request: Request, task_id: str, new_status: str):
|
||||
"""Helper to update status and return refreshed task card."""
|
||||
completed_at = datetime.utcnow().isoformat() if new_status in ("completed", "vetoed", "failed") else None
|
||||
completed_at = (
|
||||
datetime.utcnow().isoformat() if new_status in ("completed", "vetoed", "failed") else None
|
||||
)
|
||||
db = _get_db()
|
||||
try:
|
||||
db.execute(
|
||||
@@ -289,6 +323,7 @@ async def _set_status(request: Request, task_id: str, new_status: str):
|
||||
# JSON API (for programmatic access / Timmy's tool calls)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@router.post("/api/tasks", response_class=JSONResponse, status_code=201)
|
||||
async def api_create_task(request: Request):
|
||||
"""Create a task via JSON API."""
|
||||
@@ -345,7 +380,9 @@ async def api_update_status(task_id: str, request: Request):
|
||||
if not new_status or new_status not in VALID_STATUSES:
|
||||
raise HTTPException(422, f"Invalid status. Must be one of: {VALID_STATUSES}")
|
||||
|
||||
completed_at = datetime.utcnow().isoformat() if new_status in ("completed", "vetoed", "failed") else None
|
||||
completed_at = (
|
||||
datetime.utcnow().isoformat() if new_status in ("completed", "vetoed", "failed") else None
|
||||
)
|
||||
db = _get_db()
|
||||
try:
|
||||
db.execute(
|
||||
@@ -379,6 +416,7 @@ async def api_delete_task(task_id: str):
|
||||
# Queue status (polled by the chat panel every 10 seconds)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@router.get("/api/queue/status", response_class=JSONResponse)
|
||||
async def queue_status(assigned_to: str = "default"):
|
||||
"""Return queue status for the chat panel's agent status indicator."""
|
||||
@@ -396,14 +434,18 @@ async def queue_status(assigned_to: str = "default"):
|
||||
db.close()
|
||||
|
||||
if running:
|
||||
return JSONResponse({
|
||||
"is_working": True,
|
||||
"current_task": {"id": running["id"], "title": running["title"]},
|
||||
"tasks_ahead": 0,
|
||||
})
|
||||
return JSONResponse(
|
||||
{
|
||||
"is_working": True,
|
||||
"current_task": {"id": running["id"], "title": running["title"]},
|
||||
"tasks_ahead": 0,
|
||||
}
|
||||
)
|
||||
|
||||
return JSONResponse({
|
||||
"is_working": False,
|
||||
"current_task": None,
|
||||
"tasks_ahead": ahead["cnt"] if ahead else 0,
|
||||
})
|
||||
return JSONResponse(
|
||||
{
|
||||
"is_working": False,
|
||||
"current_task": None,
|
||||
"tasks_ahead": ahead["cnt"] if ahead else 0,
|
||||
}
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user