// Resource detail: status header + dependencies + source docs + runbook pages + change history. import { api } from '../api.js'; import { el, mount, clear, safeHref } from '../dom.js'; function statusClass(s) { return s === 'running' ? 'ok' : s === 'stopped' ? 'warn' : s === 'down' ? 'bad' : 'idle'; } async function loadDependencies(id, listEl) { let deps = []; try { deps = await api.get(`/api/resources/${id}/dependencies`); } catch { /* */ } clear(listEl); if (!deps.length) { listEl.appendChild(el('li', { class: 'muted' }, '(no dependencies)')); return; } for (const d of deps) { listEl.appendChild(el('li', {}, el('span', { class: 'status idle' }, d.kind || 'depends-on'), ' ', el('a', { href: '#/resource/' + d.depends_on }, d.depends_on.slice(0, 8) + '…') )); } } async function loadRunbooks(id, listEl) { clear(listEl); let links = []; try { const all = await api.get('/api/links/to/resource/' + id); links = all.filter(l => l.from_type === 'page' && l.relation === 'runbook'); } catch { /* */ } if (!links.length) { listEl.appendChild(el('li', { class: 'muted' }, 'No runbook pages linked.')); return; } for (const l of links) { let title = l.from_id.slice(0, 8) + '…'; try { const page = await api.get('/api/pages/' + l.from_id); title = page.title; } catch { /* keep id */ } listEl.appendChild(el('li', {}, el('a', { href: '#/page/' + l.from_id }, title))); } } async function loadChangeHistory(id, listEl) { clear(listEl); let rows = []; try { rows = await api.get('/api/resources/' + id + '/changes'); } catch { /* */ } if (!rows.length) { listEl.appendChild(el('li', { class: 'muted' }, 'No history yet.')); return; } for (const r of rows.slice(0, 20)) { listEl.appendChild(el('li', {}, el('span', { class: 'muted' }, new Date(r.occurred_at).toISOString().slice(0, 16).replace('T', ' ') + ' — '), r.actor_kind, ' ', r.action )); } } async function loadSourceDocs(id, listEl) { clear(listEl); let docs = []; try { docs = await api.get('/api/resources/' + id + '/source-docs'); } catch { /* */ } if (!docs.length) { listEl.appendChild(el('li', { class: 'muted' }, 'No source docs.')); return; } for (const d of docs) { listEl.appendChild(el('li', {}, d.name, ' ', el('span', { class: 'muted', style: { fontSize: '11px' } }, d.upstream_url))); } } export async function render(main, ctx) { const id = ctx.params.id; mount(main, el('p', { class: 'view-sub muted' }, 'Loading …')); let res; try { res = await api.get('/api/resources/' + id); } catch (e) { mount(main, el('h1', { class: 'view-h1' }, 'Resource not found'), el('p', { class: 'view-sub muted' }, e.message) ); return; } const depsList = el('ul', { class: 'plain' }); const runbookList = el('ul', { class: 'plain' }); const sdList = el('ul', { class: 'plain' }); const histList = el('ul', { class: 'plain' }); const depIdInput = el('input', { type: 'text', placeholder: 'depends_on UUID', style: { flex: 1 } }); const depKindInput = el('input', { type: 'text', placeholder: 'kind (optional)', style: { width: '120px' } }); async function addDep() { const dep = depIdInput.value.trim(); if (!dep) return; try { await api.post(`/api/resources/${id}/dependencies`, { depends_on: dep, kind: depKindInput.value.trim() || null }); depIdInput.value = ''; depKindInput.value = ''; loadDependencies(id, depsList); } catch (e) { alert('add failed: ' + e.message); } } mount(main, el('h1', { class: 'view-h1' }, res.name), el('p', { class: 'view-sub' }, el('span', { class: 'status ' + statusClass(res.status) }, res.status), ' · ', el('span', { class: 'status idle' }, res.runtime_type), res.host ? ' · ' + res.host : '', res.url ? ' · ' : '', res.url ? el('a', { href: safeHref(res.url), target: '_blank', rel: 'noopener noreferrer' }, res.url) : null ), el('div', { class: 'row' }, el('div', { class: 'card' }, el('h3', {}, 'Dependencies'), depsList, el('div', { style: { display: 'flex', gap: '8px', marginTop: '10px' } }, depIdInput, depKindInput, el('button', { class: 'primary', onclick: addDep }, 'Link') ) ), el('div', { class: 'card' }, el('h3', {}, 'Source docs'), sdList ), el('div', { class: 'card' }, el('h3', {}, 'Runbook pages'), runbookList ) ), el('div', { class: 'card' }, el('h3', {}, 'Change history'), histList) ); loadDependencies(id, depsList); loadSourceDocs(id, sdList); loadRunbooks(id, runbookList); loadChangeHistory(id, histList); }