Files
2026-07-10 15:02:09 +01:00

127 lines
4.0 KiB
JavaScript

import { html, nothing } from 'lit';
import { LightElement } from '../../lib/base.js';
import { formatDate, formatDuration } from './utils.js';
export class TaskHistorySection extends LightElement {
static properties = {
_runs: { state: true },
_error: { state: true },
_loading: { state: true },
_expanded: { state: true },
};
constructor() {
super();
this._runs = [];
this._error = null;
this._loading = false;
this._expanded = null;
}
async load() {
this._error = null;
this._loading = true;
try {
const res = await fetch('/api/cron/runs');
if (!res.ok) throw new Error(`HTTP ${res.status}`);
this._runs = await res.json();
} catch (e) {
this._error = e.message;
} finally {
this._loading = false;
}
}
_statusClass(status) {
return { completed: 'success', failed: 'danger', cancelled: 'warning' }[status] ?? 'secondary';
}
_toggleExpand(id) {
this._expanded = this._expanded === id ? null : id;
}
_renderRow(run) {
const isExpanded = this._expanded === run.id;
return html`
<tr class="task-history-row ${isExpanded ? 'task-history-row--expanded' : ''}"
@click=${() => this._toggleExpand(run.id)}>
<td>
<span class="badge bg-${this._statusClass(run.status)}">${run.status}</span>
</td>
<td class="task-history-title">${run.job_title ?? `Job #${run.job_id}`}</td>
<td>${run.agent_id ?? '—'}</td>
<td>${formatDate(run.completed_at)}</td>
<td>${formatDuration(run.duration_ms)}</td>
<td><i class="bi bi-chevron-${isExpanded ? 'up' : 'down'} task-history-chevron"></i></td>
</tr>
${isExpanded ? html`
<tr class="task-history-detail">
<td colspan="6">
${run.session_id != null ? html`
<div style="margin-bottom:0.5rem">
<a href="#session/${run.session_id}"
style="font-size:0.8rem;font-family:var(--bs-font-monospace)">
<i class="bi bi-chat-text me-1"></i>View session #${run.session_id}
</a>
</div>
` : nothing}
${run.error ? html`
<div class="task-history-error"><strong>Error:</strong> ${run.error}</div>
` : nothing}
${run.final_response ? html`
<div class="task-history-response"><pre>${run.final_response}</pre></div>
` : nothing}
${!run.error && !run.final_response ? html`
<div class="text-muted" style="font-size:0.82rem">No output recorded.</div>
` : nothing}
</td>
</tr>
` : nothing}
`;
}
render() {
return html`
<div class="task-page">
<div class="task-page-header">
<h2 class="task-page-title"><i class="bi bi-journal-text"></i> History</h2>
<div style="font-size:0.82rem;color:var(--bs-secondary-color)">
${this._runs.length} run${this._runs.length !== 1 ? 's' : ''}
</div>
</div>
${this._error ? html`
<div class="alert alert-danger py-2 mx-3 mb-2" style="font-size:0.85rem">${this._error}</div>
` : nothing}
${this._loading ? html`
<div class="task-empty"><i class="bi bi-hourglass-split"></i><p>Loading…</p></div>
` : this._runs.length === 0 ? html`
<div class="task-empty">
<i class="bi bi-journal-text"></i>
<p>No completed runs yet.</p>
</div>
` : html`
<div class="task-history-table-wrap">
<table class="task-history-table">
<thead>
<tr>
<th>Status</th>
<th>Task</th>
<th>Agent</th>
<th>Completed</th>
<th>Duration</th>
<th></th>
</tr>
</thead>
<tbody>
${this._runs.map(r => this._renderRow(r))}
</tbody>
</table>
</div>
`}
</div>
`;
}
}