Merge branch 'ralph/dashboard-realtime' — SSE realtime + story rollback
Server-Sent Events (TODO P3): - GET /api/ralph/stream — signature-based change detection (poll FS 2s, emit doar la diff), heartbeat 30s, X-Accel-Buffering:no - HTTPServer → ThreadingHTTPServer (altfel SSE blochează toate endpoint-urile) - ralph.html: EventSource cu fallback permanent la polling 5s când CLOSED. Badge: 🟢 Live / ⏱ Polling / Offline Story rollback (TODO P3): - POST /api/ralph/<slug>/rollback — git revert --no-edit HEAD; fallback git reset --hard HEAD~1 doar la conflict - Decrementează passes pe ultima story complete; clears failed/blocked/retries (atomic temp+rename) - Slug strict regex ^[A-Za-z0-9_-]{1,64}$ + reject path traversal explicit - Buton ↩️ pe card-uri running; confirm dialog înainte de execuție - Response: {success, message, reverted_commit, story_reverted, method} Tests: 39/39 pe test_dashboard_ralph_endpoint (era 19; +20 cazuri noi). # Conflicts: # dashboard/api.py # dashboard/handlers/ralph.py
This commit is contained in:
@@ -7,7 +7,7 @@ server bootstrap.
|
||||
"""
|
||||
import json
|
||||
import sys
|
||||
from http.server import HTTPServer, SimpleHTTPRequestHandler
|
||||
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
|
||||
from pathlib import Path
|
||||
|
||||
# Make dashboard/ importable for the handler submodules (constants,
|
||||
@@ -165,6 +165,8 @@ class TaskBoardHandler(
|
||||
self.handle_ralph_status()
|
||||
elif self.path == '/api/ralph/usage' or self.path.startswith('/api/ralph/usage?'):
|
||||
self.handle_ralph_usage()
|
||||
elif self.path == '/api/ralph/stream' or self.path.startswith('/api/ralph/stream?'):
|
||||
self.handle_ralph_stream()
|
||||
elif self.path.startswith('/api/ralph/'):
|
||||
# /api/ralph/<slug>/log or /api/ralph/<slug>/prd
|
||||
parts = self.path.split('?', 1)[0].split('/')
|
||||
@@ -239,11 +241,18 @@ class TaskBoardHandler(
|
||||
self.handle_eco_git_commit()
|
||||
elif self.path == '/api/eco/restart-taskboard':
|
||||
self.handle_eco_restart_taskboard()
|
||||
elif self.path.startswith('/api/ralph/') and self.path.endswith('/stop'):
|
||||
elif self.path.startswith('/api/ralph/'):
|
||||
# /api/ralph/<slug>/{stop,rollback}
|
||||
parts = self.path.split('?', 1)[0].split('/')
|
||||
if len(parts) >= 5:
|
||||
slug = parts[3]
|
||||
self.handle_ralph_stop(slug)
|
||||
action = parts[4]
|
||||
if action == 'stop':
|
||||
self.handle_ralph_stop(slug)
|
||||
elif action == 'rollback':
|
||||
self.handle_ralph_rollback(slug)
|
||||
else:
|
||||
self.send_error(404)
|
||||
else:
|
||||
self.send_error(404)
|
||||
else:
|
||||
@@ -270,5 +279,8 @@ if __name__ == '__main__':
|
||||
os.chdir(KANBAN_DIR)
|
||||
|
||||
print(f"Starting Echo Task Board API on port {port}")
|
||||
httpd = HTTPServer(('0.0.0.0', port), TaskBoardHandler)
|
||||
# ThreadingHTTPServer permite SSE long-lived (/api/ralph/stream) fără să
|
||||
# blocheze celelalte request-uri.
|
||||
httpd = ThreadingHTTPServer(('0.0.0.0', port), TaskBoardHandler)
|
||||
httpd.daemon_threads = True
|
||||
httpd.serve_forever()
|
||||
|
||||
Reference in New Issue
Block a user