@yemi33/minions 0.1.12 → 0.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +59 -0
- package/dashboard/js/command-center.js +377 -0
- package/dashboard/js/command-history.js +70 -0
- package/dashboard/js/command-input.js +268 -0
- package/dashboard/js/command-parser.js +129 -0
- package/dashboard/js/detail-panel.js +98 -0
- package/dashboard/js/live-stream.js +69 -0
- package/dashboard/js/modal-qa.js +309 -0
- package/dashboard/js/modal.js +131 -0
- package/dashboard/js/refresh.js +59 -0
- package/dashboard/js/render-agents.js +44 -0
- package/dashboard/js/render-dispatch.js +171 -0
- package/dashboard/js/render-inbox.js +163 -0
- package/dashboard/js/render-kb.js +125 -0
- package/dashboard/js/render-other.js +181 -0
- package/dashboard/js/render-plans.js +536 -0
- package/dashboard/js/render-prd.js +688 -0
- package/dashboard/js/render-prs.js +94 -0
- package/dashboard/js/render-schedules.js +158 -0
- package/dashboard/js/render-skills.js +89 -0
- package/dashboard/js/render-work-items.js +219 -0
- package/dashboard/js/settings.js +155 -0
- package/dashboard/js/state.js +84 -0
- package/dashboard/js/utils.js +39 -0
- package/dashboard/layout.html +123 -0
- package/dashboard/pages/engine.html +12 -0
- package/dashboard/pages/home.html +31 -0
- package/dashboard/pages/inbox.html +17 -0
- package/dashboard/pages/plans.html +4 -0
- package/dashboard/pages/prd.html +5 -0
- package/dashboard/pages/prs.html +4 -0
- package/dashboard/pages/schedule.html +10 -0
- package/dashboard/pages/work.html +5 -0
- package/dashboard/styles.css +598 -0
- package/dashboard-build.js +52 -0
- package/dashboard.js +44 -1
- package/package.json +1 -1
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// render-prs.js — PR tracker rendering functions extracted from dashboard.html
|
|
2
|
+
|
|
3
|
+
let allPrs = [];
|
|
4
|
+
let prPage = 0;
|
|
5
|
+
const PR_PER_PAGE = 3;
|
|
6
|
+
|
|
7
|
+
function prRow(pr) {
|
|
8
|
+
// Minions review (agent) state — separate from ADO human review
|
|
9
|
+
const sq = pr.minionsReview || {};
|
|
10
|
+
const reviewSource = sq.status || pr.reviewStatus || 'pending';
|
|
11
|
+
const reviewClass = reviewSource === 'approved' ? 'approved' : (reviewSource === 'changes-requested' || reviewSource === 'rejected') ? 'rejected' : reviewSource === 'waiting' ? 'building' : 'draft';
|
|
12
|
+
const reviewLabel = sq.status === 'waiting' ? 'reviewing (minions)' : sq.status ? sq.status + ' (minions)' : (pr.reviewStatus || 'pending');
|
|
13
|
+
const buildClass = pr.buildStatus === 'passing' ? 'build-pass' : pr.buildStatus === 'failing' ? 'build-fail' : pr.buildStatus === 'running' ? 'building' : 'no-build';
|
|
14
|
+
const buildLabel = pr.buildStatus || 'none';
|
|
15
|
+
const statusClass = pr.status === 'merged' ? 'merged' : pr.status === 'abandoned' ? 'rejected' : pr.status === 'active' ? 'active' : 'draft';
|
|
16
|
+
const statusLabel = pr.status || 'active';
|
|
17
|
+
const url = pr.url || '#';
|
|
18
|
+
const prId = pr.id || '—';
|
|
19
|
+
return '<tr>' +
|
|
20
|
+
'<td><span class="pr-id">' + escHtml(String(prId)) + '</span></td>' +
|
|
21
|
+
'<td><a class="pr-title" href="' + escHtml(url) + '" target="_blank">' + escHtml(pr.title || 'Untitled') + '</a></td>' +
|
|
22
|
+
'<td><span class="pr-agent">' + escHtml(pr.agent || '—') + '</span></td>' +
|
|
23
|
+
'<td><span class="pr-branch">' + escHtml(pr.branch || '—') + '</span></td>' +
|
|
24
|
+
'<td><span class="pr-badge ' + reviewClass + '">' + escHtml(reviewLabel) + '</span></td>' +
|
|
25
|
+
'<td>' + (sq.reviewer && sq.status !== 'waiting' ? '<span class="pr-agent" title="' + escHtml(sq.note || '') + '">' + escHtml(sq.reviewer) + '</span>' : sq.reviewer && sq.status === 'waiting' ? '<span class="pr-agent" style="color:var(--muted)" title="Vote pending confirmation">' + escHtml(sq.reviewer) + '…</span>' : '<span style="color:var(--muted);font-size:11px">—</span>') + '</td>' +
|
|
26
|
+
'<td><span class="pr-badge ' + buildClass + '">' + escHtml(buildLabel) + '</span></td>' +
|
|
27
|
+
'<td><span class="pr-badge ' + statusClass + '">' + escHtml(statusLabel) + '</span></td>' +
|
|
28
|
+
'<td><span class="pr-date">' + escHtml(pr.created || '—') + '</span></td>' +
|
|
29
|
+
'</tr>';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function prTableHtml(rows) {
|
|
33
|
+
return '<div class="pr-table-wrap"><table class="pr-table"><thead><tr>' +
|
|
34
|
+
'<th>PR</th><th>Title</th><th>Agent</th><th>Branch</th><th>Review</th><th>Signed Off By</th><th>Build</th><th>Status</th><th>Created</th><th></th>' +
|
|
35
|
+
'</tr></thead><tbody>' + rows + '</tbody></table></div>';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function renderPrs(prs) {
|
|
39
|
+
allPrs = prs;
|
|
40
|
+
const el = document.getElementById('pr-content');
|
|
41
|
+
const count = document.getElementById('pr-count');
|
|
42
|
+
count.textContent = prs.length;
|
|
43
|
+
if (!prs.length) {
|
|
44
|
+
el.innerHTML = '<p class="pr-empty">No pull requests yet. PRs created by agents will appear here with review, build, and merge status.</p>';
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const totalPages = Math.ceil(prs.length / PR_PER_PAGE);
|
|
48
|
+
if (prPage >= totalPages) prPage = totalPages - 1;
|
|
49
|
+
const start = prPage * PR_PER_PAGE;
|
|
50
|
+
const pagePrs = prs.slice(start, start + PR_PER_PAGE);
|
|
51
|
+
const rows = pagePrs.map(prRow).join('');
|
|
52
|
+
|
|
53
|
+
let pager = '';
|
|
54
|
+
if (prs.length > PR_PER_PAGE) {
|
|
55
|
+
pager = '<div class="pr-pager">' +
|
|
56
|
+
'<span class="pr-page-info">Showing ' + (start+1) + ' to ' + Math.min(start+PR_PER_PAGE, prs.length) + ' of ' + prs.length + '</span>' +
|
|
57
|
+
'<div class="pr-pager-btns">' +
|
|
58
|
+
'<button class="pr-pager-btn ' + (prPage === 0 ? 'disabled' : '') + '" onclick="prPrev()">Prev</button>' +
|
|
59
|
+
'<button class="pr-pager-btn ' + (prPage >= totalPages-1 ? 'disabled' : '') + '" onclick="prNext()">Next</button>' +
|
|
60
|
+
'<button class="pr-pager-btn see-all" onclick="openAllPrs()">See all ' + prs.length + ' PRs</button>' +
|
|
61
|
+
'</div>' +
|
|
62
|
+
'</div>';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
el.innerHTML = prTableHtml(rows) + pager;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function prPrev() { if (prPage > 0) { prPage--; renderPrs(allPrs); } }
|
|
69
|
+
function prNext() { const totalPages = Math.ceil(allPrs.length / PR_PER_PAGE); if (prPage < totalPages-1) { prPage++; renderPrs(allPrs); } }
|
|
70
|
+
|
|
71
|
+
function openAllPrs() {
|
|
72
|
+
const modalEl = document.querySelector('#modal .modal');
|
|
73
|
+
if (modalEl) modalEl.classList.add('modal-wide');
|
|
74
|
+
document.getElementById('modal-title').textContent = 'All Pull Requests (' + allPrs.length + ')';
|
|
75
|
+
document.getElementById('modal-body').innerHTML = prTableHtml(allPrs.map(prRow).join(''));
|
|
76
|
+
document.getElementById('modal-body').style.fontFamily = "'Segoe UI', system-ui, sans-serif";
|
|
77
|
+
document.getElementById('modal-body').style.whiteSpace = 'normal';
|
|
78
|
+
document.getElementById('modal').classList.add('open');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function openModal(i) {
|
|
82
|
+
const item = inboxData[i];
|
|
83
|
+
if (!item) return;
|
|
84
|
+
document.getElementById('modal-title').textContent = item.name;
|
|
85
|
+
document.getElementById('modal-body').innerHTML =
|
|
86
|
+
'<div style="margin-bottom:12px"><button class="pr-pager-btn" style="font-size:10px;padding:3px 10px" onclick="promoteToKB(\'' + escHtml(item.name) + '\')">Add to Knowledge Base</button></div>' +
|
|
87
|
+
'<pre style="white-space:pre-wrap;word-wrap:break-word;margin:0;font-family:Consolas,monospace;font-size:12px;line-height:1.7;color:var(--muted)">' + escHtml(item.content) + '</pre>';
|
|
88
|
+
_modalDocContext = { title: item.name, content: item.content, selection: '' };
|
|
89
|
+
_modalFilePath = 'notes/inbox/' + item.name; showModalQa();
|
|
90
|
+
// Clear notification badge when opening this document
|
|
91
|
+
const card = findCardForFile(_modalFilePath);
|
|
92
|
+
if (card) clearNotifBadge(card);
|
|
93
|
+
document.getElementById('modal').classList.add('open');
|
|
94
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
// render-schedules.js — Schedule rendering functions extracted from dashboard.html
|
|
2
|
+
|
|
3
|
+
function renderSchedules(schedules) {
|
|
4
|
+
const el = document.getElementById('scheduled-content');
|
|
5
|
+
const countEl = document.getElementById('scheduled-count');
|
|
6
|
+
countEl.textContent = schedules.length;
|
|
7
|
+
if (!schedules.length) {
|
|
8
|
+
el.innerHTML = '<p class="empty">No scheduled tasks. Add one to automate recurring work.</p>';
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
let html = '<div class="pr-table-wrap"><table class="pr-table"><thead><tr><th>ID</th><th>Title</th><th>Cron</th><th>Type</th><th>Project</th><th>Agent</th><th>Enabled</th><th>Last Run</th><th></th></tr></thead><tbody>';
|
|
12
|
+
for (const s of schedules) {
|
|
13
|
+
const enabledBadge = s.enabled
|
|
14
|
+
? '<span class="pr-badge approved">enabled</span>'
|
|
15
|
+
: '<span class="pr-badge rejected">disabled</span>';
|
|
16
|
+
const lastRun = s._lastRun ? timeAgo(s._lastRun) : 'never';
|
|
17
|
+
const typeBadge = '<span class="dispatch-type ' + escHtml(s.type || 'implement') + '">' + escHtml(s.type || 'implement') + '</span>';
|
|
18
|
+
html += '<tr>' +
|
|
19
|
+
'<td><span class="pr-id">' + escHtml(s.id || '') + '</span></td>' +
|
|
20
|
+
'<td style="max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="' + escHtml(s.title || '') + '">' + escHtml(s.title || '') + '</td>' +
|
|
21
|
+
'<td><code style="font-size:10px;color:var(--blue)">' + escHtml(s.cron || '') + '</code></td>' +
|
|
22
|
+
'<td>' + typeBadge + '</td>' +
|
|
23
|
+
'<td><span style="font-size:10px;color:var(--muted)">' + escHtml(s.project || '') + '</span></td>' +
|
|
24
|
+
'<td><span class="pr-agent">' + escHtml(s.agent || 'auto') + '</span></td>' +
|
|
25
|
+
'<td>' + enabledBadge + '</td>' +
|
|
26
|
+
'<td><span class="pr-date">' + escHtml(lastRun) + '</span></td>' +
|
|
27
|
+
'<td style="white-space:nowrap">' +
|
|
28
|
+
'<button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:' + (s.enabled ? 'var(--yellow)' : 'var(--green)') + ';border-color:' + (s.enabled ? 'var(--yellow)' : 'var(--green)') + ';margin-right:4px" onclick="event.stopPropagation();toggleScheduleEnabled(\'' + escHtml(s.id) + '\',' + !s.enabled + ')" title="' + (s.enabled ? 'Disable' : 'Enable') + '">' + (s.enabled ? '⏸' : '▶') + '</button>' +
|
|
29
|
+
'<button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:var(--blue);border-color:var(--blue);margin-right:4px" onclick="event.stopPropagation();openEditScheduleModal(\'' + escHtml(s.id) + '\')" title="Edit">✎</button>' +
|
|
30
|
+
'<button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:var(--red);border-color:var(--red)" onclick="event.stopPropagation();deleteSchedule(\'' + escHtml(s.id) + '\')" title="Delete">✕</button>' +
|
|
31
|
+
'</td>' +
|
|
32
|
+
'</tr>';
|
|
33
|
+
}
|
|
34
|
+
html += '</tbody></table></div>';
|
|
35
|
+
el.innerHTML = html;
|
|
36
|
+
window._lastSchedules = schedules;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function _scheduleFormHtml(sched, isEdit) {
|
|
40
|
+
const types = ['implement', 'test', 'explore', 'ask', 'review', 'fix'];
|
|
41
|
+
const priorities = ['high', 'medium', 'low'];
|
|
42
|
+
const typeOpts = types.map(t => '<option value="' + t + '"' + ((sched.type || 'implement') === t ? ' selected' : '') + '>' + t + '</option>').join('');
|
|
43
|
+
const priOpts = priorities.map(p => '<option value="' + p + '"' + ((sched.priority || 'medium') === p ? ' selected' : '') + '>' + p + '</option>').join('');
|
|
44
|
+
const projOpts = '<option value="">Any</option>' + cmdProjects.map(p => '<option value="' + escHtml(p.name) + '"' + (sched.project === p.name ? ' selected' : '') + '>' + escHtml(p.name) + '</option>').join('');
|
|
45
|
+
const agentOpts = '<option value="">Auto</option>' + cmdAgents.map(a => '<option value="' + escHtml(a.id) + '"' + (sched.agent === a.id ? ' selected' : '') + '>' + escHtml(a.name) + '</option>').join('');
|
|
46
|
+
|
|
47
|
+
const inputStyle = 'display:block;width:100%;margin-top:4px;padding:6px 8px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);font-size:var(--text-md);font-family:inherit';
|
|
48
|
+
|
|
49
|
+
return '<div style="display:flex;flex-direction:column;gap:12px;font-family:inherit">' +
|
|
50
|
+
(isEdit ? '' :
|
|
51
|
+
'<label style="color:var(--text);font-size:var(--text-md)">ID (unique slug)' +
|
|
52
|
+
'<input id="sched-edit-id" value="' + escHtml(sched.id || '') + '" placeholder="e.g. nightly-tests" style="' + inputStyle + '">' +
|
|
53
|
+
'</label>') +
|
|
54
|
+
'<label style="color:var(--text);font-size:var(--text-md)">Title' +
|
|
55
|
+
'<input id="sched-edit-title" value="' + escHtml(sched.title || '') + '" style="' + inputStyle + '">' +
|
|
56
|
+
'</label>' +
|
|
57
|
+
'<label style="color:var(--text);font-size:var(--text-md)">Cron <span style="font-size:10px;color:var(--muted)">(minute hour dayOfWeek)</span>' +
|
|
58
|
+
'<input id="sched-edit-cron" value="' + escHtml(sched.cron || '') + '" placeholder="0 2 *" style="' + inputStyle + '">' +
|
|
59
|
+
'</label>' +
|
|
60
|
+
'<div style="display:flex;gap:12px">' +
|
|
61
|
+
'<label style="color:var(--text);font-size:var(--text-md);flex:1">Type' +
|
|
62
|
+
'<select id="sched-edit-type" style="' + inputStyle + '">' + typeOpts + '</select>' +
|
|
63
|
+
'</label>' +
|
|
64
|
+
'<label style="color:var(--text);font-size:var(--text-md);flex:1">Priority' +
|
|
65
|
+
'<select id="sched-edit-priority" style="' + inputStyle + '">' + priOpts + '</select>' +
|
|
66
|
+
'</label>' +
|
|
67
|
+
'</div>' +
|
|
68
|
+
'<div style="display:flex;gap:12px">' +
|
|
69
|
+
'<label style="color:var(--text);font-size:var(--text-md);flex:1">Project' +
|
|
70
|
+
'<select id="sched-edit-project" style="' + inputStyle + '">' + projOpts + '</select>' +
|
|
71
|
+
'</label>' +
|
|
72
|
+
'<label style="color:var(--text);font-size:var(--text-md);flex:1">Agent' +
|
|
73
|
+
'<select id="sched-edit-agent" style="' + inputStyle + '">' + agentOpts + '</select>' +
|
|
74
|
+
'</label>' +
|
|
75
|
+
'</div>' +
|
|
76
|
+
'<label style="color:var(--text);font-size:var(--text-md)">Description' +
|
|
77
|
+
'<textarea id="sched-edit-desc" rows="3" style="' + inputStyle + ';resize:vertical">' + escHtml(sched.description || '') + '</textarea>' +
|
|
78
|
+
'</label>' +
|
|
79
|
+
'<div style="display:flex;justify-content:flex-end;gap:8px;margin-top:8px">' +
|
|
80
|
+
'<button onclick="closeModal()" class="pr-pager-btn" style="padding:6px 16px;font-size:var(--text-md)">Cancel</button>' +
|
|
81
|
+
'<button onclick="submitSchedule(' + isEdit + ')" style="padding:6px 16px;font-size:var(--text-md);background:var(--blue);color:#fff;border:none;border-radius:var(--radius-sm);cursor:pointer">' + (isEdit ? 'Save' : 'Create') + '</button>' +
|
|
82
|
+
'</div>' +
|
|
83
|
+
'</div>';
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function openCreateScheduleModal() {
|
|
87
|
+
document.getElementById('modal-title').textContent = 'New Scheduled Task';
|
|
88
|
+
document.getElementById('modal-body').style.whiteSpace = 'normal';
|
|
89
|
+
document.getElementById('modal-body').style.fontFamily = '';
|
|
90
|
+
document.getElementById('modal-body').innerHTML = _scheduleFormHtml({}, false);
|
|
91
|
+
document.getElementById('modal').classList.add('open');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function openEditScheduleModal(id) {
|
|
95
|
+
const sched = (window._lastSchedules || []).find(s => s.id === id);
|
|
96
|
+
if (!sched) return;
|
|
97
|
+
document.getElementById('modal-title').textContent = 'Edit Schedule: ' + id;
|
|
98
|
+
document.getElementById('modal-body').style.whiteSpace = 'normal';
|
|
99
|
+
document.getElementById('modal-body').style.fontFamily = '';
|
|
100
|
+
document.getElementById('modal-body').innerHTML = _scheduleFormHtml(sched, true);
|
|
101
|
+
window._editScheduleId = id;
|
|
102
|
+
document.getElementById('modal').classList.add('open');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function submitSchedule(isEdit) {
|
|
106
|
+
const title = document.getElementById('sched-edit-title').value.trim();
|
|
107
|
+
const cron = document.getElementById('sched-edit-cron').value.trim();
|
|
108
|
+
const type = document.getElementById('sched-edit-type').value;
|
|
109
|
+
const priority = document.getElementById('sched-edit-priority').value;
|
|
110
|
+
const project = document.getElementById('sched-edit-project').value;
|
|
111
|
+
const agent = document.getElementById('sched-edit-agent').value;
|
|
112
|
+
const description = document.getElementById('sched-edit-desc').value;
|
|
113
|
+
const id = isEdit ? window._editScheduleId : (document.getElementById('sched-edit-id') ? document.getElementById('sched-edit-id').value.trim() : '');
|
|
114
|
+
|
|
115
|
+
if (!id) { alert('ID is required'); return; }
|
|
116
|
+
if (!title) { alert('Title is required'); return; }
|
|
117
|
+
if (!cron) { alert('Cron expression is required'); return; }
|
|
118
|
+
|
|
119
|
+
const payload = { id, title, cron, type, priority, project: project || undefined, agent: agent || undefined, description: description || undefined, enabled: true };
|
|
120
|
+
const url = isEdit ? '/api/schedules/update' : '/api/schedules';
|
|
121
|
+
try {
|
|
122
|
+
const res = await fetch(url, {
|
|
123
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
124
|
+
body: JSON.stringify(payload)
|
|
125
|
+
});
|
|
126
|
+
if (res.ok) { closeModal(); refresh(); showToast('cmd-toast', isEdit ? 'Schedule updated' : 'Schedule created', true); } else {
|
|
127
|
+
const d = await res.json().catch(() => ({}));
|
|
128
|
+
alert((isEdit ? 'Update' : 'Create') + ' failed: ' + (d.error || 'unknown'));
|
|
129
|
+
}
|
|
130
|
+
} catch (e) { alert('Error: ' + e.message); }
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function toggleScheduleEnabled(id, enabled) {
|
|
134
|
+
try {
|
|
135
|
+
const res = await fetch('/api/schedules/update', {
|
|
136
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
137
|
+
body: JSON.stringify({ id, enabled })
|
|
138
|
+
});
|
|
139
|
+
if (res.ok) { refresh(); } else {
|
|
140
|
+
const d = await res.json().catch(() => ({}));
|
|
141
|
+
alert('Toggle failed: ' + (d.error || 'unknown'));
|
|
142
|
+
}
|
|
143
|
+
} catch (e) { alert('Toggle error: ' + e.message); }
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async function deleteSchedule(id) {
|
|
147
|
+
if (!confirm('Delete scheduled task "' + id + '"?')) return;
|
|
148
|
+
try {
|
|
149
|
+
const res = await fetch('/api/schedules/delete', {
|
|
150
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
151
|
+
body: JSON.stringify({ id })
|
|
152
|
+
});
|
|
153
|
+
if (res.ok) { refresh(); showToast('cmd-toast', 'Schedule deleted', true); } else {
|
|
154
|
+
const d = await res.json().catch(() => ({}));
|
|
155
|
+
alert('Delete failed: ' + (d.error || 'unknown'));
|
|
156
|
+
}
|
|
157
|
+
} catch (e) { alert('Delete error: ' + e.message); }
|
|
158
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// render-skills.js — Skills rendering functions extracted from dashboard.html
|
|
2
|
+
|
|
3
|
+
let _skillsTab = 'all';
|
|
4
|
+
let _skillsPage = 0;
|
|
5
|
+
const SKILLS_PER_PAGE = 5;
|
|
6
|
+
|
|
7
|
+
function renderSkills(skills) {
|
|
8
|
+
const el = document.getElementById('skills-list');
|
|
9
|
+
const countEl = document.getElementById('skills-count');
|
|
10
|
+
countEl.textContent = skills.length;
|
|
11
|
+
if (!skills.length) {
|
|
12
|
+
el.innerHTML = '<p class="empty">No skills yet. Agents create these when they discover repeatable workflows.</p>';
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const sourceIcon = (s) => s === 'claude-code' ? '⚡' : s === 'plugin' ? '🔌' : s?.startsWith('project:') ? '📁' : '🔧';
|
|
17
|
+
const sourceLabel = (s) => s === 'plugin' ? 'plugin' : s?.startsWith('project:') ? s.replace('project:', '') : 'global';
|
|
18
|
+
|
|
19
|
+
// Group by source
|
|
20
|
+
const groups = {};
|
|
21
|
+
for (const r of skills) {
|
|
22
|
+
const key = r.source === 'plugin' ? 'plugins' : r.source?.startsWith('project:') ? r.source.replace('project:', '') : 'global';
|
|
23
|
+
if (!groups[key]) groups[key] = [];
|
|
24
|
+
groups[key].push(r);
|
|
25
|
+
}
|
|
26
|
+
const groupKeys = Object.keys(groups).sort((a, b) => a === 'global' ? -1 : b === 'global' ? 1 : a.localeCompare(b));
|
|
27
|
+
|
|
28
|
+
// Tab bar
|
|
29
|
+
const tabs = [{ key: 'all', label: 'All (' + skills.length + ')' }];
|
|
30
|
+
for (const k of groupKeys) tabs.push({ key: k, label: k + ' (' + groups[k].length + ')' });
|
|
31
|
+
|
|
32
|
+
let html = '<div style="display:flex;gap:4px;margin-bottom:8px;flex-wrap:wrap">';
|
|
33
|
+
for (const t of tabs) {
|
|
34
|
+
const active = _skillsTab === t.key;
|
|
35
|
+
html += '<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px;' +
|
|
36
|
+
(active ? 'background:var(--green);color:#fff;border-color:var(--green)' : '') +
|
|
37
|
+
'" onclick="_skillsTab=\'' + escHtml(t.key) + '\';_skillsPage=0;renderSkills(window._lastSkills)">' + escHtml(t.label) + '</button>';
|
|
38
|
+
}
|
|
39
|
+
html += '</div>';
|
|
40
|
+
|
|
41
|
+
// Filter by tab
|
|
42
|
+
const filtered = _skillsTab === 'all' ? skills : (groups[_skillsTab] || []);
|
|
43
|
+
|
|
44
|
+
// Paginate
|
|
45
|
+
const totalPages = Math.ceil(filtered.length / SKILLS_PER_PAGE);
|
|
46
|
+
if (_skillsPage >= totalPages) _skillsPage = Math.max(0, totalPages - 1);
|
|
47
|
+
const page = filtered.slice(_skillsPage * SKILLS_PER_PAGE, (_skillsPage + 1) * SKILLS_PER_PAGE);
|
|
48
|
+
|
|
49
|
+
html += '<div style="display:flex;flex-direction:column;gap:6px">';
|
|
50
|
+
for (const r of page) {
|
|
51
|
+
const autoTag = r.autoGenerated ? '<span style="font-size:8px;background:rgba(63,185,80,0.15);color:var(--green);padding:1px 4px;border-radius:3px;margin-left:4px">auto</span>' : '';
|
|
52
|
+
html += '<div class="inbox-item" onclick="openSkill(\'' + escHtml(r.file) + '\',\'' + escHtml(r.source || 'claude-code') + '\',\'' + escHtml(r.dir || '') + '\')" style="border-left-color:var(--green)">' +
|
|
53
|
+
'<div class="inbox-name"><span style="color:var(--green);font-weight:600">' + sourceIcon(r.source) + ' ' + escHtml(r.name) + '</span>' + autoTag +
|
|
54
|
+
'<span style="font-size:9px;color:var(--muted);margin-left:auto">' + escHtml(sourceLabel(r.source)) + '</span>' +
|
|
55
|
+
'</div>' +
|
|
56
|
+
(r.description ? '<div class="inbox-preview" style="color:var(--text)">' + escHtml(r.description) + '</div>' : '') +
|
|
57
|
+
'</div>';
|
|
58
|
+
}
|
|
59
|
+
html += '</div>';
|
|
60
|
+
|
|
61
|
+
// Pagination controls
|
|
62
|
+
if (totalPages > 1) {
|
|
63
|
+
html += '<div style="display:flex;align-items:center;gap:8px;margin-top:6px;justify-content:center">';
|
|
64
|
+
html += '<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px" ' +
|
|
65
|
+
(_skillsPage === 0 ? 'disabled' : '') +
|
|
66
|
+
' onclick="_skillsPage--;renderSkills(window._lastSkills)">«</button>';
|
|
67
|
+
html += '<span style="font-size:10px;color:var(--muted)">' + (_skillsPage + 1) + ' / ' + totalPages + '</span>';
|
|
68
|
+
html += '<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px" ' +
|
|
69
|
+
(_skillsPage >= totalPages - 1 ? 'disabled' : '') +
|
|
70
|
+
' onclick="_skillsPage++;renderSkills(window._lastSkills)">»</button>';
|
|
71
|
+
html += '</div>';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
el.innerHTML = html;
|
|
75
|
+
window._lastSkills = skills;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function openSkill(file, source, dir) {
|
|
79
|
+
fetch('/api/skill?file=' + encodeURIComponent(file) + '&source=' + encodeURIComponent(source || 'claude-code') + (dir ? '&dir=' + encodeURIComponent(dir) : ''))
|
|
80
|
+
.then(r => r.text())
|
|
81
|
+
.then(content => {
|
|
82
|
+
document.getElementById('modal-title').textContent = file;
|
|
83
|
+
document.getElementById('modal-body').textContent = content;
|
|
84
|
+
document.getElementById('modal-body').style.fontFamily = 'Consolas, monospace';
|
|
85
|
+
document.getElementById('modal-body').style.whiteSpace = 'pre-wrap';
|
|
86
|
+
document.getElementById('modal').classList.add('open');
|
|
87
|
+
})
|
|
88
|
+
.catch(() => {});
|
|
89
|
+
}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
// dashboard/js/render-work-items.js — Work item rendering and management extracted from dashboard.html
|
|
2
|
+
|
|
3
|
+
let allWorkItems = [];
|
|
4
|
+
let wiPage = 0;
|
|
5
|
+
const WI_PER_PAGE = 6;
|
|
6
|
+
|
|
7
|
+
function wiRow(item) {
|
|
8
|
+
const statusBadge = (s) => {
|
|
9
|
+
const cls = s === 'failed' ? 'rejected' : s === 'dispatched' ? 'building' : s === 'pending' || s === 'queued' ? 'active' : s === 'done' ? 'approved' : 'draft';
|
|
10
|
+
return '<span class="pr-badge ' + cls + '">' + escHtml(s) + '</span>';
|
|
11
|
+
};
|
|
12
|
+
const typeBadge = (t) => '<span class="dispatch-type ' + (t || 'implement') + '">' + escHtml(t || 'implement') + '</span>';
|
|
13
|
+
const priBadge = (p) => '<span class="prd-item-priority ' + (p || '') + '">' + escHtml(p || 'medium') + '</span>';
|
|
14
|
+
const prLink = item._pr
|
|
15
|
+
? '<a class="pr-title" href="' + escHtml(item._prUrl || '#') + '" target="_blank" style="font-size:10px">' + escHtml(item._pr) + '</a>'
|
|
16
|
+
: '<span style="color:var(--muted)">—</span>';
|
|
17
|
+
return '<tr>' +
|
|
18
|
+
'<td><span class="pr-id">' + escHtml(item.id || '') + '</span></td>' +
|
|
19
|
+
'<td style="max-width:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="' + escHtml(item.description || item.title || '') + '">' + escHtml(item.title || '') + '</td>' +
|
|
20
|
+
'<td><span style="font-size:10px;color:var(--muted)">' + escHtml(item._source || '') + '</span>' +
|
|
21
|
+
(item.scope === 'fan-out' ? ' <span class="pr-badge ' + (item.status === 'done' || item.status === 'failed' ? 'draft' : 'building') + '" style="font-size:8px">fan-out</span>' : '') + '</td>' +
|
|
22
|
+
'<td>' + typeBadge(item.type) + '</td>' +
|
|
23
|
+
'<td>' + priBadge(item.priority) + '</td>' +
|
|
24
|
+
'<td>' + statusBadge(item.status || 'pending') +
|
|
25
|
+
(item._pendingReason ? ' <span style="font-size:9px;color:var(--muted);margin-left:4px" title="Pending reason: ' + escHtml(item._pendingReason) + '">' + escHtml(item._pendingReason.replace(/_/g, ' ')) + '</span>' : '') +
|
|
26
|
+
(item.status === 'failed' ? ' <button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:var(--yellow);border-color:var(--yellow);margin-left:4px" onclick="event.stopPropagation();retryWorkItem(\'' + escHtml(item.id) + '\',\'' + escHtml(item._source || '') + '\')">Retry</button>' : '') +
|
|
27
|
+
'</td>' +
|
|
28
|
+
'<td>' +
|
|
29
|
+
(item.completedAgents && item.completedAgents.length > 0
|
|
30
|
+
? '<span class="pr-agent">' + escHtml(item.completedAgents.join(', ')) + '</span>'
|
|
31
|
+
: '<span class="pr-agent">' + escHtml(item.dispatched_to || '—') + '</span>') +
|
|
32
|
+
(item.failReason ? '<span style="display:block;font-size:9px;color:var(--red)" title="' + escHtml(item.failReason) + '">' + escHtml(item.failReason.slice(0, 30)) + '</span>' : '') +
|
|
33
|
+
'</td>' +
|
|
34
|
+
'<td>' + prLink + '</td>' +
|
|
35
|
+
'<td><span class="pr-date">' + shortTime(item.created) + '</span></td>' +
|
|
36
|
+
'<td style="white-space:nowrap">' +
|
|
37
|
+
((item.status === 'pending' || item.status === 'failed') ? '<button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:var(--blue);border-color:var(--blue);margin-right:4px" onclick="event.stopPropagation();editWorkItem(\'' + escHtml(item.id) + '\',\'' + escHtml(item._source || '') + '\')" title="Edit work item">✎</button>' : '') +
|
|
38
|
+
((item.status === 'done' || item.status === 'failed') ? '<button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:var(--muted);border-color:var(--border);margin-right:4px" onclick="event.stopPropagation();archiveWorkItem(\'' + escHtml(item.id) + '\',\'' + escHtml(item._source || '') + '\')" title="Archive work item">📦</button>' : '') +
|
|
39
|
+
'<button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:var(--red);border-color:var(--red)" onclick="event.stopPropagation();deleteWorkItem(\'' + escHtml(item.id) + '\',\'' + escHtml(item._source || '') + '\')" title="Delete work item and kill agent">✕</button>' +
|
|
40
|
+
'</td>' +
|
|
41
|
+
'</tr>';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function renderWorkItems(items) {
|
|
45
|
+
// Sort: active/dispatched first, then by most recent activity
|
|
46
|
+
const statusOrder = { dispatched: 0, pending: 1, queued: 1, failed: 2, done: 3 };
|
|
47
|
+
items.sort((a, b) => {
|
|
48
|
+
const sa = statusOrder[a.status] ?? 2, sb = statusOrder[b.status] ?? 2;
|
|
49
|
+
if (sa !== sb) return sa - sb;
|
|
50
|
+
const ta = a.completedAt || a.dispatched_at || a.created || '';
|
|
51
|
+
const tb = b.completedAt || b.dispatched_at || b.created || '';
|
|
52
|
+
return tb.localeCompare(ta); // most recent first
|
|
53
|
+
});
|
|
54
|
+
allWorkItems = items;
|
|
55
|
+
const el = document.getElementById('work-items-content');
|
|
56
|
+
const countEl = document.getElementById('wi-count');
|
|
57
|
+
countEl.textContent = items.length;
|
|
58
|
+
if (!items.length) {
|
|
59
|
+
el.innerHTML = '<p class="empty">No work items. Add tasks via Command Center above.</p>';
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const totalPages = Math.ceil(items.length / WI_PER_PAGE);
|
|
64
|
+
if (wiPage >= totalPages) wiPage = totalPages - 1;
|
|
65
|
+
const start = wiPage * WI_PER_PAGE;
|
|
66
|
+
const pageItems = items.slice(start, start + WI_PER_PAGE);
|
|
67
|
+
|
|
68
|
+
let html = '<div class="pr-table-wrap"><table class="pr-table"><thead><tr><th>ID</th><th>Title</th><th>Source</th><th>Type</th><th>Priority</th><th>Status</th><th>Agent</th><th>PR</th><th>Created</th><th></th></tr></thead><tbody>';
|
|
69
|
+
html += pageItems.map(wiRow).join('');
|
|
70
|
+
html += '</tbody></table></div>';
|
|
71
|
+
|
|
72
|
+
if (items.length > WI_PER_PAGE) {
|
|
73
|
+
html += '<div class="pr-pager">' +
|
|
74
|
+
'<span class="pr-page-info">Showing ' + (start+1) + ' to ' + Math.min(start+WI_PER_PAGE, items.length) + ' of ' + items.length + '</span>' +
|
|
75
|
+
'<div class="pr-pager-btns">' +
|
|
76
|
+
'<button class="pr-pager-btn ' + (wiPage === 0 ? 'disabled' : '') + '" onclick="wiPrev()">Prev</button>' +
|
|
77
|
+
'<button class="pr-pager-btn ' + (wiPage >= totalPages-1 ? 'disabled' : '') + '" onclick="wiNext()">Next</button>' +
|
|
78
|
+
'<button class="pr-pager-btn see-all" onclick="openAllWorkItems()">See all ' + items.length + '</button>' +
|
|
79
|
+
'</div>' +
|
|
80
|
+
'</div>';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
el.innerHTML = html;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function editWorkItem(id, source) {
|
|
87
|
+
const item = allWorkItems.find(i => i.id === id);
|
|
88
|
+
if (!item) return;
|
|
89
|
+
const types = ['implement', 'fix', 'review', 'plan', 'verify', 'investigate', 'refactor', 'test', 'docs'];
|
|
90
|
+
const priorities = ['critical', 'high', 'medium', 'low'];
|
|
91
|
+
const agentOpts = cmdAgents.map(a => '<option value="' + escHtml(a.id) + '"' + (item.agent === a.id ? ' selected' : '') + '>' + escHtml(a.name) + '</option>').join('');
|
|
92
|
+
const typeOpts = types.map(t => '<option value="' + t + '"' + ((item.type || 'implement') === t ? ' selected' : '') + '>' + t + '</option>').join('');
|
|
93
|
+
const priOpts = priorities.map(p => '<option value="' + p + '"' + ((item.priority || 'medium') === p ? ' selected' : '') + '>' + p + '</option>').join('');
|
|
94
|
+
|
|
95
|
+
document.getElementById('modal-title').textContent = 'Edit Work Item ' + id;
|
|
96
|
+
document.getElementById('modal-body').style.whiteSpace = 'normal';
|
|
97
|
+
document.getElementById('modal-body').innerHTML =
|
|
98
|
+
'<div style="display:flex;flex-direction:column;gap:12px;font-family:inherit">' +
|
|
99
|
+
'<label style="color:var(--text);font-size:var(--text-md)">Title' +
|
|
100
|
+
'<input id="wi-edit-title" value="' + escHtml(item.title || '') + '" style="display:block;width:100%;margin-top:4px;padding:6px 8px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);font-size:var(--text-md);font-family:inherit">' +
|
|
101
|
+
'</label>' +
|
|
102
|
+
'<label style="color:var(--text);font-size:var(--text-md)">Description' +
|
|
103
|
+
'<textarea id="wi-edit-desc" rows="3" style="display:block;width:100%;margin-top:4px;padding:6px 8px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);font-size:var(--text-md);font-family:inherit;resize:vertical">' + escHtml(item.description || '') + '</textarea>' +
|
|
104
|
+
'</label>' +
|
|
105
|
+
'<div style="display:flex;gap:12px">' +
|
|
106
|
+
'<label style="color:var(--text);font-size:var(--text-md);flex:1">Type' +
|
|
107
|
+
'<select id="wi-edit-type" style="display:block;width:100%;margin-top:4px;padding:6px 8px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);font-size:var(--text-md)">' + typeOpts + '</select>' +
|
|
108
|
+
'</label>' +
|
|
109
|
+
'<label style="color:var(--text);font-size:var(--text-md);flex:1">Priority' +
|
|
110
|
+
'<select id="wi-edit-priority" style="display:block;width:100%;margin-top:4px;padding:6px 8px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);font-size:var(--text-md)">' + priOpts + '</select>' +
|
|
111
|
+
'</label>' +
|
|
112
|
+
'<label style="color:var(--text);font-size:var(--text-md);flex:1">Agent' +
|
|
113
|
+
'<select id="wi-edit-agent" style="display:block;width:100%;margin-top:4px;padding:6px 8px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);font-size:var(--text-md)"><option value="">Auto</option>' + agentOpts + '</select>' +
|
|
114
|
+
'</label>' +
|
|
115
|
+
'</div>' +
|
|
116
|
+
'<div style="display:flex;justify-content:flex-end;gap:8px;margin-top:8px">' +
|
|
117
|
+
'<button onclick="closeModal()" class="pr-pager-btn" style="padding:6px 16px;font-size:var(--text-md)">Cancel</button>' +
|
|
118
|
+
'<button onclick="submitWorkItemEdit(\'' + escHtml(id) + '\',\'' + escHtml(source || '') + '\')" style="padding:6px 16px;font-size:var(--text-md);background:var(--blue);color:#fff;border:none;border-radius:var(--radius-sm);cursor:pointer">Save</button>' +
|
|
119
|
+
'</div>' +
|
|
120
|
+
'</div>';
|
|
121
|
+
document.getElementById('modal').classList.add('open');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function submitWorkItemEdit(id, source) {
|
|
125
|
+
const title = document.getElementById('wi-edit-title').value.trim();
|
|
126
|
+
const description = document.getElementById('wi-edit-desc').value;
|
|
127
|
+
const type = document.getElementById('wi-edit-type').value;
|
|
128
|
+
const priority = document.getElementById('wi-edit-priority').value;
|
|
129
|
+
const agent = document.getElementById('wi-edit-agent').value;
|
|
130
|
+
if (!title) { alert('Title is required'); return; }
|
|
131
|
+
try {
|
|
132
|
+
const res = await fetch('/api/work-items/update', {
|
|
133
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
134
|
+
body: JSON.stringify({ id, source: source || undefined, title, description, type, priority, agent })
|
|
135
|
+
});
|
|
136
|
+
if (res.ok) { closeModal(); refresh(); showToast('cmd-toast', 'Work item updated', true); } else {
|
|
137
|
+
const d = await res.json();
|
|
138
|
+
alert('Update failed: ' + (d.error || 'unknown'));
|
|
139
|
+
}
|
|
140
|
+
} catch (e) { alert('Update error: ' + e.message); }
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function deleteWorkItem(id, source) {
|
|
144
|
+
if (!confirm('Delete work item ' + id + '? This will kill any running agent and remove all dispatch history.')) return;
|
|
145
|
+
try {
|
|
146
|
+
const res = await fetch('/api/work-items/delete', {
|
|
147
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
148
|
+
body: JSON.stringify({ id, source: source || undefined })
|
|
149
|
+
});
|
|
150
|
+
if (res.ok) { refresh(); } else {
|
|
151
|
+
const d = await res.json();
|
|
152
|
+
alert('Delete failed: ' + (d.error || 'unknown'));
|
|
153
|
+
}
|
|
154
|
+
} catch (e) { alert('Delete error: ' + e.message); }
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async function archiveWorkItem(id, source) {
|
|
158
|
+
try {
|
|
159
|
+
const res = await fetch('/api/work-items/archive', {
|
|
160
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
161
|
+
body: JSON.stringify({ id, source: source || undefined })
|
|
162
|
+
});
|
|
163
|
+
if (res.ok) { refresh(); } else {
|
|
164
|
+
const d = await res.json();
|
|
165
|
+
alert('Archive failed: ' + (d.error || 'unknown'));
|
|
166
|
+
}
|
|
167
|
+
} catch (e) { alert('Archive error: ' + e.message); }
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
let wiArchiveVisible = false;
|
|
171
|
+
async function toggleWorkItemArchive() {
|
|
172
|
+
const el = document.getElementById('work-items-archive');
|
|
173
|
+
wiArchiveVisible = !wiArchiveVisible;
|
|
174
|
+
if (!wiArchiveVisible) { el.style.display = 'none'; return; }
|
|
175
|
+
el.style.display = 'block';
|
|
176
|
+
el.innerHTML = '<p class="empty">Loading archive...</p>';
|
|
177
|
+
try {
|
|
178
|
+
const items = await fetch('/api/work-items/archive').then(r => r.json());
|
|
179
|
+
if (!items.length) { el.innerHTML = '<p class="empty">No archived work items.</p>'; return; }
|
|
180
|
+
el.innerHTML = '<div style="font-size:10px;color:var(--muted);margin-bottom:6px;font-weight:600;text-transform:uppercase;letter-spacing:0.5px">Archived (' + items.length + ')</div>' +
|
|
181
|
+
'<div class="pr-table-wrap"><table class="pr-table"><thead><tr><th>ID</th><th>Title</th><th>Type</th><th>Status</th><th>Agent</th><th>Archived</th></tr></thead><tbody>' +
|
|
182
|
+
items.map(function(i) {
|
|
183
|
+
return '<tr style="opacity:0.6">' +
|
|
184
|
+
'<td><span class="pr-id">' + escHtml(i.id || '') + '</span></td>' +
|
|
185
|
+
'<td style="max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">' + escHtml(i.title || '') + '</td>' +
|
|
186
|
+
'<td><span class="dispatch-type ' + (i.type || '') + '">' + escHtml(i.type || '') + '</span></td>' +
|
|
187
|
+
'<td style="color:' + (i.status === 'done' ? 'var(--green)' : 'var(--red)') + '">' + escHtml(i.status || '') + '</td>' +
|
|
188
|
+
'<td>' + escHtml(i.dispatched_to || '—') + '</td>' +
|
|
189
|
+
'<td class="pr-date">' + shortTime(i.archivedAt) + '</td>' +
|
|
190
|
+
'</tr>';
|
|
191
|
+
}).join('') + '</tbody></table></div>';
|
|
192
|
+
} catch (e) { el.innerHTML = '<p class="empty">Failed to load archive.</p>'; }
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async function retryWorkItem(id, source) {
|
|
196
|
+
try {
|
|
197
|
+
const res = await fetch('/api/work-items/retry', {
|
|
198
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
199
|
+
body: JSON.stringify({ id, source: source || undefined })
|
|
200
|
+
});
|
|
201
|
+
if (res.ok) { refresh(); } else {
|
|
202
|
+
const d = await res.json();
|
|
203
|
+
alert('Retry failed: ' + (d.error || 'unknown'));
|
|
204
|
+
}
|
|
205
|
+
} catch (e) { alert('Retry error: ' + e.message); }
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function wiPrev() { if (wiPage > 0) { wiPage--; renderWorkItems(allWorkItems); } }
|
|
209
|
+
function wiNext() { const tp = Math.ceil(allWorkItems.length / WI_PER_PAGE); if (wiPage < tp-1) { wiPage++; renderWorkItems(allWorkItems); } }
|
|
210
|
+
|
|
211
|
+
function openAllWorkItems() {
|
|
212
|
+
document.getElementById('modal-title').textContent = 'All Work Items (' + allWorkItems.length + ')';
|
|
213
|
+
const html = '<div class="pr-table-wrap"><table class="pr-table"><thead><tr><th>ID</th><th>Title</th><th>Source</th><th>Type</th><th>Priority</th><th>Status</th><th>Agent</th><th>PR</th><th>Created</th><th></th></tr></thead><tbody>' +
|
|
214
|
+
allWorkItems.map(wiRow).join('') + '</tbody></table></div>';
|
|
215
|
+
document.getElementById('modal-body').innerHTML = html;
|
|
216
|
+
document.getElementById('modal-body').style.fontFamily = "'Segoe UI', system-ui, sans-serif";
|
|
217
|
+
document.getElementById('modal-body').style.whiteSpace = 'normal';
|
|
218
|
+
document.getElementById('modal').classList.add('open');
|
|
219
|
+
}
|