diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-03-09 01:17:05 +0000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-03-09 01:17:05 +0000 |
| commit | 13846ea4ce4bacfadda6f86c5e48e5e76e13db07 (patch) | |
| tree | 20838cbcbc343e8cd8b8f6bb0f8ad347c3fa52b3 /web/app.js | |
| parent | f9d2056f4d10fe7dde71f5d17d8e8c473b3a611f (diff) | |
feat: delete templates feature and allow requeueing BUDGET_EXCEEDED tasks
Removed all template-related code from frontend (tabs, modals, logic) and backend (routes, files, DB table). Updated BUDGET_EXCEEDED tasks to be requeueable with a Restart button. Fixed ReferenceError in isUserEditing for Node.js tests.
Diffstat (limited to 'web/app.js')
| -rw-r--r-- | web/app.js | 187 |
1 files changed, 29 insertions, 158 deletions
@@ -9,12 +9,6 @@ async function fetchTasks() { return res.json(); } -async function fetchTemplates() { - const res = await fetch(`${API_BASE}/api/templates`); - if (!res.ok) throw new Error(`HTTP ${res.status}`); - return res.json(); -} - // Fetches recent executions (last 24h) from /api/executions?since=24h. // fetchFn defaults to window.fetch; injectable for tests. async function fetchRecentExecutions(basePath = BASE_PATH, fetchFn = fetch) { @@ -215,6 +209,35 @@ function createTaskCard(task) { return card; } +/** + * Returns true if the user is currently editing a text field or has a modal open. + * Used to avoid destructive DOM refreshes during polling. + */ +export function isUserEditing(activeEl = (typeof document !== 'undefined' ? document.activeElement : null)) { + if (!activeEl) return false; + const tag = activeEl.tagName; + if (tag === 'INPUT' || tag === 'TEXTAREA') return true; + if (activeEl.isContentEditable) return true; + if (activeEl.closest('[role="dialog"]') || activeEl.closest('dialog')) return true; + return false; +} + +/** + * Partitions tasks into 'running' and 'ready' arrays for the Active tab view. + * Both arrays are sorted by created_at ascending (oldest first). + */ +export function partitionActivePaneTasks(tasks) { + const running = tasks + .filter(t => t.state === 'RUNNING') + .sort((a, b) => new Date(a.created_at) - new Date(b.created_at)); + + const ready = tasks + .filter(t => t.state === 'READY') + .sort((a, b) => new Date(a.created_at) - new Date(b.created_at)); + + return { running, ready }; +} + // ── Sort ────────────────────────────────────────────────────────────────────── function sortTasksByDate(tasks, descend = false) { @@ -330,62 +353,6 @@ function renderActiveTaskList(tasks) { } } -function createTemplateCard(tmpl) { - const card = document.createElement('div'); - card.className = 'template-card'; - - const name = document.createElement('div'); - name.className = 'template-name'; - name.textContent = tmpl.name; - card.appendChild(name); - - if (tmpl.description) { - const desc = document.createElement('div'); - desc.className = 'template-description'; - desc.textContent = tmpl.description; - card.appendChild(desc); - } - - if (tmpl.tags && tmpl.tags.length > 0) { - const tagsEl = document.createElement('div'); - tagsEl.className = 'template-tags'; - for (const tag of tmpl.tags) { - const chip = document.createElement('span'); - chip.className = 'tag-chip'; - chip.textContent = tag; - tagsEl.appendChild(chip); - } - card.appendChild(tagsEl); - } - - const footer = document.createElement('div'); - footer.className = 'template-card-footer'; - - const delBtn = document.createElement('button'); - delBtn.className = 'btn-danger btn-sm'; - delBtn.textContent = 'Delete'; - delBtn.addEventListener('click', () => deleteTemplate(tmpl.id)); - - footer.appendChild(delBtn); - card.appendChild(footer); - - return card; -} - -function renderTemplateList(templates) { - const container = document.querySelector('.template-list'); - - if (!templates || templates.length === 0) { - container.innerHTML = '<div id="loading">No templates yet.</div>'; - return; - } - - container.innerHTML = ''; - for (const tmpl of templates) { - container.appendChild(createTemplateCard(tmpl)); - } -} - // ── Run action ──────────────────────────────────────────────────────────────── async function runTask(taskId) { @@ -839,23 +806,6 @@ async function handleStartNextTask(btn) { } } -// ── Delete template ──────────────────────────────────────────────────────────── - -async function deleteTemplate(id) { - if (!window.confirm('Delete this template?')) return; - - const res = await fetch(`${API_BASE}/api/templates/${id}`, { method: 'DELETE' }); - if (!res.ok) { - let msg = `HTTP ${res.status}`; - try { const body = await res.json(); msg = body.error || body.message || msg; } catch {} - alert(`Failed to delete: ${msg}`); - return; - } - - const templates = await fetchTemplates(); - renderTemplateList(templates); -} - // ── Polling ─────────────────────────────────────────────────────────────────── async function poll() { @@ -1212,50 +1162,6 @@ async function createTask(formData) { renderTaskList(tasks); } -// ── Template modal ──────────────────────────────────────────────────────────── - -function openTemplateModal() { - document.getElementById('template-modal').showModal(); -} - -function closeTemplateModal() { - document.getElementById('template-modal').close(); - document.getElementById('template-form').reset(); -} - -async function saveTemplate(formData) { - const splitTrim = val => val.split(',').map(s => s.trim()).filter(Boolean); - - const body = { - name: formData.get('name'), - description: formData.get('description'), - agent: { - instructions: formData.get('instructions'), - project_dir: formData.get('project_dir'), - max_budget_usd: parseFloat(formData.get('max_budget_usd')), - allowed_tools: splitTrim(formData.get('allowed_tools') || ''), - }, - timeout: formData.get('timeout'), - priority: formData.get('priority'), - tags: splitTrim(formData.get('tags') || ''), - }; - - const res = await fetch(`${API_BASE}/api/templates`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(body), - }); - - if (!res.ok) { - const text = await res.text(); - throw new Error(text || `HTTP ${res.status}`); - } - - closeTemplateModal(); - const templates = await fetchTemplates(); - renderTemplateList(templates); -} - // ── Task side panel ─────────────────────────────────────────────────────────── // Format Go's task.Duration JSON value {"Duration": <nanoseconds>} to human string. @@ -1966,13 +1872,6 @@ function switchTab(name) { document.getElementById('btn-new-task').style.display = name === 'tasks' ? '' : 'none'; - if (name === 'templates') { - fetchTemplates().then(renderTemplateList).catch(() => { - document.querySelector('.template-list').innerHTML = - '<div id="loading">Could not reach server.</div>'; - }); - } - if (name === 'running') { fetchTasks().then(renderRunningView).catch(() => { const currentEl = document.querySelector('.running-current'); @@ -2158,32 +2057,4 @@ if (typeof document !== 'undefined') document.addEventListener('DOMContentLoaded btn.textContent = 'Create & Queue'; } }); - - // Template modal - document.getElementById('btn-new-template').addEventListener('click', openTemplateModal); - document.getElementById('btn-cancel-template').addEventListener('click', closeTemplateModal); - - document.getElementById('template-form').addEventListener('submit', async e => { - e.preventDefault(); - - // Remove any previous error - const prev = e.target.querySelector('.form-error'); - if (prev) prev.remove(); - - const btn = e.submitter; - btn.disabled = true; - btn.textContent = 'Saving…'; - - try { - await saveTemplate(new FormData(e.target)); - } catch (err) { - const errEl = document.createElement('p'); - errEl.className = 'form-error'; - errEl.textContent = err.message; - e.target.appendChild(errEl); - } finally { - btn.disabled = false; - btn.textContent = 'Save Template'; - } - }); }); |
