fix(kanban): synchronize real Gitea issues & PRs with Whiteboard Kanban columns
This commit is contained in:
+57
-26
@@ -402,7 +402,9 @@
|
|||||||
<div class="infra-card">
|
<div class="infra-card">
|
||||||
<div class="infra-title">🐙 GITEA KANBAN & PRS</div>
|
<div class="infra-title">🐙 GITEA KANBAN & PRS</div>
|
||||||
<div>
|
<div>
|
||||||
<span class="infra-val" id="giteaPrs">2</span> <span style="font-size:14px; color:#94a3b8">open PRs</span> |
|
<span class="infra-val" id="giteaPrs">0</span> <span style="font-size:13px; color:#94a3b8">PRs</span> |
|
||||||
|
<span class="infra-val" id="giteaReview" style="color:#ec4899">0</span> <span style="font-size:13px; color:#94a3b8">rev</span> |
|
||||||
|
<span class="infra-val" id="giteaWip" style="color:#38bdf8">0</span> <span style="font-size:13px; color:#94a3b8">wip</span> |
|
||||||
<span class="badge-ok" id="giteaStatus">CONNECTED</span>
|
<span class="badge-ok" id="giteaStatus">CONNECTED</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -692,11 +694,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 3. Update Gitea HUD & Kanban
|
// 3. Update Gitea HUD & Kanban
|
||||||
document.getElementById('giteaPrs').innerText = data.gitea.open_prs_count;
|
document.getElementById('giteaPrs').innerText = data.gitea.open_prs_count || 0;
|
||||||
const gStatus = document.getElementById('giteaStatus');
|
const gStatus = document.getElementById('giteaStatus');
|
||||||
gStatus.innerText = data.gitea.connected ? 'CONNECTED' : 'DISCONNECTED';
|
gStatus.innerText = data.gitea.connected ? 'CONNECTED' : 'DISCONNECTED';
|
||||||
gStatus.className = data.gitea.connected ? 'badge-ok' : 'badge-busy';
|
gStatus.className = data.gitea.connected ? 'badge-ok' : 'badge-busy';
|
||||||
if (data.gitea.kanban_columns) kanbanState = data.gitea.kanban_columns;
|
if (data.gitea.kanban_columns) {
|
||||||
|
kanbanState = data.gitea.kanban_columns;
|
||||||
|
const revElem = document.getElementById('giteaReview');
|
||||||
|
if (revElem) revElem.innerText = kanbanState.review || 0;
|
||||||
|
const wipElem = document.getElementById('giteaWip');
|
||||||
|
if (wipElem) wipElem.innerText = kanbanState.in_progress || 0;
|
||||||
|
}
|
||||||
|
|
||||||
// 4. Update Agents
|
// 4. Update Agents
|
||||||
if (data.agents) {
|
if (data.agents) {
|
||||||
@@ -984,40 +992,63 @@
|
|||||||
ctx.fillText(f.label, f.x + 12, f.y + h + 24);
|
ctx.fillText(f.label, f.x + 12, f.y + h + 24);
|
||||||
|
|
||||||
} else if (f.type === 'WHITEBOARD') {
|
} else if (f.type === 'WHITEBOARD') {
|
||||||
// Gitea Kanban Board on the Wall!
|
// Realtime Gitea Kanban Board on the Wall!
|
||||||
const w = 110, h = 50;
|
const w = 120, h = 54;
|
||||||
ctx.fillStyle = "#ffffff";
|
ctx.fillStyle = "#ffffff";
|
||||||
ctx.fillRect(f.x, f.y, w, h);
|
ctx.fillRect(f.x, f.y, w, h);
|
||||||
ctx.strokeStyle = "#94a3b8";
|
ctx.strokeStyle = "#64748b";
|
||||||
ctx.lineWidth = 2;
|
ctx.lineWidth = 2;
|
||||||
ctx.strokeRect(f.x, f.y, w, h);
|
ctx.strokeRect(f.x, f.y, w, h);
|
||||||
|
|
||||||
// Kanban Columns: Ready | WIP | Review
|
// Header Bar
|
||||||
ctx.strokeStyle = "#cbd5e1";
|
ctx.fillStyle = "#1e293b";
|
||||||
|
ctx.fillRect(f.x, f.y, w, 12);
|
||||||
|
ctx.font = "8px 'Press Start 2P'";
|
||||||
|
ctx.fillStyle = "#facc15";
|
||||||
|
ctx.fillText("KANBAN BOARD", f.x + 8, f.y + 9);
|
||||||
|
|
||||||
|
// 4 Columns: Plan | WIP | Review | Done
|
||||||
|
const colW = w / 4;
|
||||||
|
const colColors = ["#94a3b8", "#38bdf8", "#ec4899", "#22c55e"];
|
||||||
|
const colHeaders = ["PLN", "WIP", "REV", "OK"];
|
||||||
|
const colCounts = [
|
||||||
|
kanbanState.planned || 0,
|
||||||
|
kanbanState.in_progress || 0,
|
||||||
|
kanbanState.review || 0,
|
||||||
|
kanbanState.done || 0
|
||||||
|
];
|
||||||
|
|
||||||
|
for (let c = 0; c < 4; c++) {
|
||||||
|
const cx = f.x + c * colW;
|
||||||
|
if (c > 0) {
|
||||||
|
ctx.strokeStyle = "#e2e8f0";
|
||||||
ctx.lineWidth = 1;
|
ctx.lineWidth = 1;
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.moveTo(f.x + 36, f.y); ctx.lineTo(f.x + 36, f.y + h);
|
ctx.moveTo(cx, f.y + 12); ctx.lineTo(cx, f.y + h);
|
||||||
ctx.moveTo(f.x + 72, f.y); ctx.lineTo(f.x + 72, f.y + h);
|
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
ctx.font = "8px 'Press Start 2P'";
|
// Column Header Label
|
||||||
|
ctx.font = "7px 'Press Start 2P'";
|
||||||
|
ctx.fillStyle = "#475569";
|
||||||
|
ctx.fillText(colHeaders[c], cx + 4, f.y + 21);
|
||||||
|
|
||||||
|
// Draw Mini Sticky Notes
|
||||||
|
const count = colCounts[c];
|
||||||
|
const stickiesToShow = Math.min(count, 3);
|
||||||
|
for (let s = 0; s < stickiesToShow; s++) {
|
||||||
|
ctx.fillStyle = colColors[c];
|
||||||
|
ctx.fillRect(cx + 4, f.y + 24 + s * 9, colW - 8, 7);
|
||||||
|
ctx.strokeStyle = "rgba(0,0,0,0.3)";
|
||||||
|
ctx.strokeRect(cx + 4, f.y + 24 + s * 9, colW - 8, 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Counter Badge if count > 0
|
||||||
|
if (count > 0) {
|
||||||
|
ctx.font = "10px 'VT323'";
|
||||||
ctx.fillStyle = "#0f172a";
|
ctx.fillStyle = "#0f172a";
|
||||||
ctx.fillText("RDY", f.x + 6, f.y + 10);
|
ctx.fillText(count.toString(), cx + colW - 10, f.y + 21);
|
||||||
ctx.fillText("WIP", f.x + 42, f.y + 10);
|
|
||||||
ctx.fillText("REV", f.x + 78, f.y + 10);
|
|
||||||
|
|
||||||
// Colored Sticky notes based on actual kanban counts
|
|
||||||
for (let i = 0; i < Math.min(kanbanState.ready, 3); i++) {
|
|
||||||
ctx.fillStyle = "#facc15"; // Yellow ready sticky
|
|
||||||
ctx.fillRect(f.x + 8, f.y + 14 + i * 11, 20, 8);
|
|
||||||
}
|
}
|
||||||
for (let i = 0; i < Math.min(kanbanState.in_progress, 2); i++) {
|
|
||||||
ctx.fillStyle = "#38bdf8"; // Blue wip sticky
|
|
||||||
ctx.fillRect(f.x + 44, f.y + 14 + i * 11, 20, 8);
|
|
||||||
}
|
|
||||||
for (let i = 0; i < Math.min(kanbanState.review, 3); i++) {
|
|
||||||
ctx.fillStyle = "#f43f5e"; // Pink review sticky
|
|
||||||
ctx.fillRect(f.x + 80, f.y + 14 + i * 11, 20, 8);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (f.type === 'BULLMQ_STATION') {
|
} else if (f.type === 'BULLMQ_STATION') {
|
||||||
|
|||||||
@@ -149,11 +149,12 @@ class RealtimeRPGHandler(http.server.SimpleHTTPRequestHandler):
|
|||||||
status["gitea"]["active_ci_jobs"] = len(ci_containers)
|
status["gitea"]["active_ci_jobs"] = len(ci_containers)
|
||||||
status["gitea"]["ci_status"] = "BUILDING" if len(ci_containers) > 0 else "PASSING"
|
status["gitea"]["ci_status"] = "BUILDING" if len(ci_containers) > 0 else "PASSING"
|
||||||
|
|
||||||
req = urllib.request.Request(
|
# Fetch Open Pull Requests
|
||||||
|
req_prs = urllib.request.Request(
|
||||||
f"{GITEA_BASE_URL}/repos/openclaw/{target_project}/pulls?state=open",
|
f"{GITEA_BASE_URL}/repos/openclaw/{target_project}/pulls?state=open",
|
||||||
headers={'Authorization': f'token {GITEA_TOKEN}'}
|
headers={'Authorization': f'token {GITEA_TOKEN}'}
|
||||||
)
|
)
|
||||||
with urllib.request.urlopen(req, timeout=2) as resp:
|
with urllib.request.urlopen(req_prs, timeout=2) as resp:
|
||||||
prs = json.loads(resp.read().decode())
|
prs = json.loads(resp.read().decode())
|
||||||
status["gitea"]["open_prs_count"] = len(prs)
|
status["gitea"]["open_prs_count"] = len(prs)
|
||||||
for p in prs:
|
for p in prs:
|
||||||
@@ -163,10 +164,51 @@ class RealtimeRPGHandler(http.server.SimpleHTTPRequestHandler):
|
|||||||
"user": p.get("user", {}).get("username"),
|
"user": p.get("user", {}).get("username"),
|
||||||
"created_at": p.get("created_at")
|
"created_at": p.get("created_at")
|
||||||
})
|
})
|
||||||
status["gitea"]["kanban_columns"]["review"] = len(prs)
|
|
||||||
status["gitea"]["kanban_columns"]["ready"] = max(1, len(prs))
|
# Fetch All Issues for Real Kanban Board Sync
|
||||||
status["gitea"]["kanban_columns"]["planned"] = 3
|
req_issues = urllib.request.Request(
|
||||||
status["gitea"]["kanban_columns"]["done"] = 12
|
f"{GITEA_BASE_URL}/repos/openclaw/{target_project}/issues?state=all&limit=50",
|
||||||
|
headers={'Authorization': f'token {GITEA_TOKEN}'}
|
||||||
|
)
|
||||||
|
with urllib.request.urlopen(req_issues, timeout=2) as resp:
|
||||||
|
issues = json.loads(resp.read().decode())
|
||||||
|
kanban = {"planned": 0, "ready": 0, "in_progress": 0, "review": 0, "done": 0}
|
||||||
|
kanban_items = {"planned": [], "ready": [], "in_progress": [], "review": [], "done": []}
|
||||||
|
|
||||||
|
for iss in issues:
|
||||||
|
labels = [l.get("name", "").lower() for l in iss.get("labels", [])]
|
||||||
|
is_closed = iss.get("state") == "closed"
|
||||||
|
is_pr = bool(iss.get("pull_request"))
|
||||||
|
|
||||||
|
item_meta = {
|
||||||
|
"number": iss.get("number"),
|
||||||
|
"title": iss.get("title"),
|
||||||
|
"is_pr": is_pr,
|
||||||
|
"state": iss.get("state")
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_closed:
|
||||||
|
kanban["done"] += 1
|
||||||
|
if len(kanban_items["done"]) < 5: kanban_items["done"].append(item_meta)
|
||||||
|
elif any("review" in l for l in labels) or is_pr:
|
||||||
|
kanban["review"] += 1
|
||||||
|
if len(kanban_items["review"]) < 5: kanban_items["review"].append(item_meta)
|
||||||
|
elif any("in-progress" in l or "in_progress" in l for l in labels) or ("coder-tasks" in latest_active_tasks):
|
||||||
|
kanban["in_progress"] += 1
|
||||||
|
if len(kanban_items["in_progress"]) < 5: kanban_items["in_progress"].append(item_meta)
|
||||||
|
elif any("ready" in l or "board/active" in l for l in labels):
|
||||||
|
kanban["ready"] += 1
|
||||||
|
if len(kanban_items["ready"]) < 5: kanban_items["ready"].append(item_meta)
|
||||||
|
else:
|
||||||
|
kanban["planned"] += 1
|
||||||
|
if len(kanban_items["planned"]) < 5: kanban_items["planned"].append(item_meta)
|
||||||
|
|
||||||
|
# Ensure active BullMQ coder task reflects in Kanban WIP if not already
|
||||||
|
if status["bullmq"]["total_active"] > 0 and kanban["in_progress"] == 0:
|
||||||
|
kanban["in_progress"] = status["bullmq"]["total_active"]
|
||||||
|
|
||||||
|
status["gitea"]["kanban_columns"] = kanban
|
||||||
|
status["gitea"]["kanban_items"] = kanban_items
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
status["gitea"]["connected"] = False
|
status["gitea"]["connected"] = False
|
||||||
status["gitea"]["error"] = str(e)
|
status["gitea"]["error"] = str(e)
|
||||||
|
|||||||
Reference in New Issue
Block a user