Files
jamie d5b4df9d56
Build / Build and Push Docker Image (push) Successful in 50s
Build / Build and Upload Binary (push) Successful in 22s
feat: add folder filtering support to job reports and update UI filters
2026-06-10 23:20:40 +01:00

153 lines
7.2 KiB
HTML

{{define "content"}}
<div class="card">
<div style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 1rem; margin-bottom: 1rem;">
<h2 style="margin: 0;">History</h2>
<div style="display: flex; align-items: center; gap: 1rem; flex-wrap: wrap;">
<div class="filter-bar">
<label for="status-filter" style="font-size: 0.875rem; color: var(--text-secondary);">Status:</label>
<select id="status-filter" class="filter-select" onchange="updateFilter('status', this.value)">
<option value="all" {{if eq .FilterStatus "all"}}selected{{end}}>All</option>
<option value="exclude_skipped" {{if eq .FilterStatus "exclude_skipped"}}selected{{end}}>Exclude Skipped</option>
<option value="success" {{if eq .FilterStatus "success"}}selected{{end}}>Success Only</option>
<option value="failed" {{if eq .FilterStatus "failed"}}selected{{end}}>Failed Only</option>
</select>
<label for="folder-filter" style="font-size: 0.875rem; color: var(--text-secondary); margin-left: 0.5rem;">Folder:</label>
<select id="folder-filter" class="filter-select" onchange="updateFilter('folder', this.value)">
<option value="0" {{if eq .FilterFolder 0}}selected{{end}}>All Folders</option>
{{$filterFolder := .FilterFolder}}
{{range .Folders}}
<option value="{{.ID}}" {{if eq $filterFolder .ID}}selected{{end}}>{{.FolderPath}}</option>
{{end}}
</select>
</div>
<input type="text" id="history-search" class="form-control" placeholder="Search page..." onkeyup="filterHistory()" style="max-width: 200px;">
<span style="color: var(--text-secondary); font-size: 0.875rem; white-space: nowrap;">Total Records: <span id="record-count">{{.Total}}</span></span>
</div>
</div>
<div class="table-container">
<table>
<thead>
<tr>
<th>File</th>
<th>Type</th>
<th>Status</th>
<th>Original Size</th>
<th>Encoded Size</th>
<th>Saved</th>
<th>Time</th>
<th>Date</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{{range .Reports}}
<tr>
<td style="word-break: break-all;">
<div style="font-weight: 500;">{{.FilePath}}</div>
{{if .ErrorMessage}}
<div style="color: var(--error); font-size: 0.75rem; margin-top: 0.25rem;">{{.ErrorMessage}}</div>
{{end}}
</td>
<td><span class="badge" style="background: var(--surface-hover); color: var(--text-primary); border-color: var(--border);">{{.MediaType}}</span></td>
<td><span class="badge {{.Status}}">{{.Status}}</span></td>
<td>{{formatBytes .OriginalSize}}</td>
<td>{{formatBytes .EncodedSize}}</td>
<td style="color: var(--accent);">{{formatBytes .SizeSaved}}</td>
<td>{{formatDuration .ProcessingTime}}</td>
<td>{{.CreatedAt.Format "Jan 02, 15:04:05"}}</td>
<td>
{{if eq .Status "failed"}}
<button class="btn btn-sm btn-outline" onclick="requeueJob({{.ID}})">Requeue</button>
{{end}}
</td>
</tr>
{{else}}
<tr>
<td colspan="9" style="text-align: center; color: var(--text-secondary);">No job reports found.</td>
</tr>
{{end}}
</tbody>
</table>
</div>
<div style="display: flex; justify-content: space-between; align-items: center; margin-top: 1rem;">
<div>
<label for="limit-select" style="color: var(--text-secondary); font-size: 0.875rem; margin-right: 0.5rem;">Rows per page:</label>
<select id="limit-select" onchange="updateFilter('limit', this.value)" style="background: var(--surface); color: var(--text-primary); border: 1px solid var(--border); border-radius: 4px; padding: 0.25rem 0.5rem;">
<option value="10" {{if eq .Limit 10}}selected{{end}}>10</option>
<option value="50" {{if eq .Limit 50}}selected{{end}}>50</option>
<option value="100" {{if eq .Limit 100}}selected{{end}}>100</option>
</select>
</div>
<div style="display: flex; gap: 0.5rem; align-items: center;">
{{if .HasPrev}}
<a href="?page={{.PrevPage}}&limit={{.Limit}}&status={{.FilterStatus}}&folder={{.FilterFolder}}" class="btn btn-sm btn-outline" style="text-decoration: none;">Previous</a>
{{else}}
<button class="btn btn-sm btn-outline" disabled>Previous</button>
{{end}}
<span style="color: var(--text-secondary); font-size: 0.875rem;">Page {{.CurrentPage}} of {{.TotalPages}}</span>
{{if .HasNext}}
<a href="?page={{.NextPage}}&limit={{.Limit}}&status={{.FilterStatus}}&folder={{.FilterFolder}}" class="btn btn-sm btn-outline" style="text-decoration: none;">Next</a>
{{else}}
<button class="btn btn-sm btn-outline" disabled>Next</button>
{{end}}
</div>
</div>
</div>
<script>
function updateFilter(key, value) {
const url = new URL(window.location.href);
url.searchParams.set(key, value);
if (key !== 'limit' && key !== 'page') {
url.searchParams.set('page', '1');
}
window.location.href = url.toString();
}
function filterHistory() {
var input, filter, table, tr, td, i, txtValue;
input = document.getElementById("history-search");
filter = input.value.toUpperCase();
table = document.querySelector(".table-container table");
tr = table.getElementsByTagName("tr");
var visibleCount = 0;
for (i = 1; i < tr.length; i++) { // Skip header row
// If it's the "No job reports found" row, skip it
if (tr[i].cells.length === 1) continue;
td = tr[i].getElementsByTagName("td")[0]; // File column
if (td) {
txtValue = td.textContent || td.innerText;
if (txtValue.toUpperCase().indexOf(filter) > -1) {
tr[i].style.display = "";
visibleCount++;
} else {
tr[i].style.display = "none";
}
}
}
// Update the record count
document.getElementById("record-count").textContent = filter ? visibleCount : "{{.Total}}";
}
async function requeueJob(id) {
if (!confirm("Requeue this failed job?")) return;
try {
const res = await fetch('/api/jobs/requeue/' + id, { method: 'POST' });
if (res.ok) {
window.location.reload();
} else {
alert("Failed to requeue job: " + await res.text());
}
} catch (e) {
alert("Error: " + e.message);
}
}
</script>
{{end}}