#!/usr/bin/env python3 """ Durable Kanban Web Server — interactive, 2-row layout, filters, collapsible details. Replaces the basic kanban-web-server.py for production use. Key features: - 2-row layout: Active Lanes (todo/ready/running) top, Archive (blocked/done) bottom - Dropdown filter by assignee profile - Checkbox to hide DONE tasks - Expandable task body (collapsible details) - Auto-restart on crash (durable loop) - Silent logging, ignores ConnectionResetError Usage: python3 scripts/kanban-durable-server.py --board nv-office --port 9120 """ import sqlite3, json, http.server, socketserver, urllib.parse, os, sys, time, argparse KANBAN_ROOT = os.path.expanduser("~/.hermes/kanban/boards") HTML = """ NV-Office · Kanban

📋 NV-Office Kanban

🔥 Đang chạy (Active Lanes)
📦 Đã đóng & Blocked (Archive / Blocker)
""" class KanbanHandler(http.server.BaseHTTPRequestHandler): board_path = None def log_message(self, fmt, *args): pass def do_GET(self): try: parsed = urllib.parse.urlparse(self.path) if parsed.path == "/api/tasks": self.send_response(200) self.send_header("Content-Type", "application/json; charset=utf-8") self.send_header("Access-Control-Allow-Origin", "*") self.end_headers() db = os.path.join(self.board_path, "kanban.db") if self.board_path else None db = db if (db and os.path.exists(db)) else os.path.expanduser("~/.hermes/kanban.db") data = [] if os.path.exists(db): conn = sqlite3.connect(db) rows = conn.execute("SELECT id, title, body, status, assignee, priority, created_at, completed_at FROM tasks").fetchall() conn.close() data = [dict(zip(["id","title","body","status","assignee","priority","created_at","completed_at"], r)) for r in rows] self.wfile.write(json.dumps(data, ensure_ascii=False).encode()) else: self.send_response(200) self.send_header("Content-Type", "text/html; charset=utf-8") self.end_headers() self.wfile.write(HTML.encode()) except ConnectionResetError: pass except Exception as e: print(f"Error: {e}", file=sys.stderr) def main(): parser = argparse.ArgumentParser(description="Durable Kanban Web Server") parser.add_argument("--board", "-b", default="nv-office") parser.add_argument("--port", "-p", type=int, default=9120) args = parser.parse_args() KanbanHandler.board_path = os.path.join(KANBAN_ROOT, args.board) print(f"Board: {args.board} Port: {args.port} Path: {KanbanHandler.board_path}") while True: try: socketserver.TCPServer.allow_reuse_address = True with socketserver.TCPServer(("0.0.0.0", args.port), KanbanHandler) as httpd: httpd.serve_forever() except Exception as e: print(f"Restarting: {e}", file=sys.stderr) time.sleep(1) if __name__ == "__main__": main()