#!/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
🔥 Đ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()