""" HoffDesk Dashboard — Dev Server Serves the static dashboard with mock data for Sprint 1 development. Run: python dev_server.py """ from fastapi import FastAPI from fastapi.responses import HTMLResponse, FileResponse from fastapi.staticfiles import StaticFiles from pathlib import Path from datetime import datetime, timezone, timedelta import json, random app = FastAPI() BASE = Path(__file__).parent CST = timezone(timedelta(hours=-5)) # --- Mock Data Generator --- def mock_today(): now = datetime.now(CST) return { "timestamp": now.isoformat(), "calendar": { "status": "ok", "events_count": 3, "events": [ { "uid": "mock-001", "summary": "Harper's Soccer Practice", "start": now.replace(hour=16, minute=0, second=0).isoformat(), "end": now.replace(hour=17, minute=0, second=0).isoformat(), "is_all_day": False, "location": "Green Bay Community Center", "description": None, "attendees": ["Harper"] }, { "uid": "mock-002", "summary": "Dentist — Sullivan", "start": now.replace(hour=10, minute=30, second=0).isoformat(), "end": now.replace(hour=11, minute=15, second=0).isoformat(), "is_all_day": False, "location": "Dr. Patel, 123 Main St", "description": "Regular checkup", "attendees": None }, { "uid": "mock-003", "summary": "Date Night 🍷", "start": now.replace(hour=19, minute=0, second=0).isoformat(), "end": now.replace(hour=21, minute=30, second=0).isoformat(), "is_all_day": False, "location": "Kōv — Ashwaubenon", "description": None, "attendees": ["Matt", "Aundrea"] } ] }, "weather": { "status": "ok", "location": { "name": "Green Bay, WI", "lat": 44.5133, "lon": -88.0133 }, "current": { "temperature_f": 52, "feels_like_f": 49, "condition": "Partly cloudy", "humidity_pct": 62, "wind_mph": 10, "updated_at": now.isoformat() }, "forecast": [ { "hour": f"{(now.hour + i) % 24:02d}:00", "temperature_f": 52 - i + random.randint(-2, 2), "condition": ["Partly cloudy", "Cloudy", "Clear", "Overcast"][i % 4], "precipitation_chance_pct": max(0, 15 - i * 3 + random.randint(0, 10)) } for i in range(1, 7) ] }, "health": { "overall_status": "healthy", "components": { "openclaw_gateway": {"status": "ok", "last_check": now.isoformat(), "latency_ms": 8}, "radicale_caldav": {"status": "ok", "last_check": now.isoformat(), "latency_ms": 14}, "ollama_local": {"status": "ok", "last_check": now.isoformat(), "latency_ms": 45}, "cloudflare_tunnel": {"status": "ok", "last_check": now.isoformat(), "latency_ms": 22} }, "disk_usage": { "total_gb": 476.9, "used_gb": 142.3, "free_gb": 334.6, "usage_pct": 29.8 } }, "meta": { "cache_ttl_seconds": 60, "data_freshness": { "calendar_seconds_ago": 12, "weather_seconds_ago": 180 } } } # --- Routes --- @app.get("/", response_class=HTMLResponse) async def dashboard(): return (BASE / "templates" / "index.html").read_text() @app.get("/api/today") async def api_today(): return mock_today() @app.get("/api/calendar/upcoming") async def api_calendar(hours: int = 24): data = mock_today() return {"status": "ok", "events": data["calendar"]["events"]} @app.get("/api/weather") async def api_weather(): data = mock_today() return {"status": "ok", "data": data["weather"]} @app.get("/api/health") async def api_health(): data = mock_today() return {"status": "ok", "overall": data["health"]} @app.get("/healthz") async def healthz(): return "ok" # --- API Endpoints for Events --- @app.get("/api/events-list", response_class=HTMLResponse) async def api_events_list(): """Return list view HTML fragment for events card.""" data = mock_today() events = data["calendar"]["events"] if not events: return '

No upcoming events

' html = '
' for ev in events: start = datetime.fromisoformat(ev["start"]) end = datetime.fromisoformat(ev["end"]) time_str = f"{start.strftime('%-I:%M %p')} – {end.strftime('%-I:%M %p')}" html += f'''
{time_str}
{ev["summary"]}
{f'
{ev["location"]}
' if ev.get("location") else ""}
''' html += '
' return html @app.get("/api/events-week", response_class=HTMLResponse) async def api_events_week(offset: int = 0): """Return week view HTML fragment for events card.""" now = datetime.now(CST) # Calculate week range based on offset (weeks) start_of_week = now - timedelta(days=now.weekday()) + timedelta(weeks=offset) days = [] # Mock events for the week mock_week_events = { 0: [{"title": "Soccer Practice", "time": "4:00 PM", "member": "harper", "all_day": False}], 1: [{"title": "Dentist", "time": "10:30 AM", "member": "sullivan", "all_day": False}], 2: [], 3: [{"title": "School Play", "time": "6:00 PM", "member": "harper", "all_day": False}], 4: [{"title": "Date Night", "time": "7:00 PM", "member": "matt", "all_day": False}], 5: [{"title": "Grocery Shopping", "time": "9:00 AM", "member": "aundrea", "all_day": False}], 6: [{"title": "Family Brunch", "time": "10:00 AM", "member": None, "all_day": False}], } day_names = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] html = '''
This Week
'''.format(offset - 1, offset + 1) for i in range(7): day_date = start_of_week + timedelta(days=i) day_num = day_date.day is_today = day_date.date() == now.date() events = mock_week_events.get(i, []) html += f'''
{day_names[i]}
{day_num}
''' for ev in events: member_class = ev.get("member") if ev.get("member") else "" html += f'''
{ev["title"]}
{ev["time"]}
''' html += '
' html += '''
''' return html # Static files app.mount("/static", StaticFiles(directory=str(BASE / "static")), name="static") if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8080)