Skip to content

Commit 8693708

Browse files
committed
Merge feat/HB-021-hook-list: Hook list view
2 parents c6094b6 + a7110cc commit 8693708

1 file changed

Lines changed: 84 additions & 2 deletions

File tree

ui/assets/app.js

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,10 +136,92 @@ async function checkHealth() {
136136
}
137137
}
138138

139-
// --- Views (stubs, implemented in HB-021 through HB-023) ---
139+
// --- Hook List View ---
140140
async function renderHookList() {
141141
show('view-hooks');
142-
$('#view-hooks').innerHTML = '<p class="empty-state">Loading hooks...</p>';
142+
const el = $('#view-hooks');
143+
el.innerHTML = '<p class="empty-state">Loading hooks...</p>';
144+
145+
const data = await api.get('/api/hooks');
146+
const hooks = data.hooks || [];
147+
const origin = location.origin;
148+
149+
let html = '<div class="toolbar">';
150+
html += '<h2 style="margin:0;font-size:16px;">Hooks (' + data.count + ')</h2>';
151+
html += '<button class="btn btn-primary" id="btn-create-hook">+ Create Hook</button>';
152+
html += '</div>';
153+
154+
if (hooks.length === 0) {
155+
html += '<p class="empty-state">No hooks yet. Create one to start capturing webhooks.</p>';
156+
} else {
157+
for (const hook of hooks) {
158+
const url = origin + hook.url;
159+
html += '<div class="card card-clickable" data-hook-id="' + escapeHtml(hook.hook_id) + '">';
160+
html += '<div class="card-header">';
161+
html += '<span class="card-title">' + escapeHtml(hook.name) + '</span>';
162+
html += '<span style="display:flex;gap:6px;flex-shrink:0;">';
163+
html += '<button class="btn btn-small btn-copy-url" data-url="' + escapeHtml(url) + '" title="Copy URL">copy</button>';
164+
html += '<button class="btn btn-small btn-danger btn-delete-hook" data-hook-id="' + escapeHtml(hook.hook_id) + '" data-hook-name="' + escapeHtml(hook.name) + '" title="Delete">del</button>';
165+
html += '</span>';
166+
html += '</div>';
167+
html += '<div class="card-meta">';
168+
html += '<span>' + hook.request_count + ' request' + (hook.request_count !== 1 ? 's' : '') + '</span>';
169+
html += '<span class="url-box" style="border:0;padding:0;background:none;font-size:12px;">' + escapeHtml(url) + '</span>';
170+
html += '<span>' + timeAgo(hook.created_at) + '</span>';
171+
html += '</div>';
172+
html += '</div>';
173+
}
174+
}
175+
176+
el.innerHTML = html;
177+
178+
// Create hook handler
179+
const createBtn = document.getElementById('btn-create-hook');
180+
if (createBtn) {
181+
createBtn.addEventListener('click', async (e) => {
182+
e.stopPropagation();
183+
const name = prompt('Hook name (leave blank for auto-generated):');
184+
if (name === null) return;
185+
try {
186+
const body = name.trim() ? { name: name.trim() } : {};
187+
await api.post('/api/hooks', body);
188+
await renderHookList();
189+
} catch (err) {
190+
toast(err.error || 'Failed to create hook', 'error');
191+
}
192+
});
193+
}
194+
195+
// Copy URL handlers
196+
el.querySelectorAll('.btn-copy-url').forEach(btn => {
197+
btn.addEventListener('click', (e) => {
198+
e.stopPropagation();
199+
copyText(btn.dataset.url);
200+
});
201+
});
202+
203+
// Delete hook handlers
204+
el.querySelectorAll('.btn-delete-hook').forEach(btn => {
205+
btn.addEventListener('click', async (e) => {
206+
e.stopPropagation();
207+
const name = btn.dataset.hookName;
208+
if (!confirm('Delete hook "' + name + '"? All captured requests will be lost.')) return;
209+
try {
210+
await api.del('/api/hooks/' + btn.dataset.hookId);
211+
toast('Hook deleted', 'success');
212+
await renderHookList();
213+
} catch (err) {
214+
toast(err.error || 'Failed to delete hook', 'error');
215+
}
216+
});
217+
});
218+
219+
// Click card to navigate
220+
el.querySelectorAll('.card-clickable').forEach(card => {
221+
card.addEventListener('click', () => {
222+
navigate('/hooks/' + card.dataset.hookId);
223+
});
224+
});
143225
}
144226

145227
async function renderRequestFeed(hookId) {

0 commit comments

Comments
 (0)