feat: batch clip download from motionEye (trim+concat, history Klip button)
This commit is contained in:
1 parent
d3357fccef
commit
110d557dbf
8 files changed
+694
-6
No files matched your search
+54
-4
@@ -269,11 +269,12 @@
|
||||
<th>Start Time</th>
|
||||
<th>End Time</th>
|
||||
<th>Duration (Min)</th>
|
||||
<th>Klip</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="batchesTableBody">
|
||||
<tr>
|
||||
<td colspan="9" class="no-data">
|
||||
<td colspan="10" class="no-data">
|
||||
<i class="fa-solid fa-arrow-left"></i>
|
||||
Silakan pilih tanggal dari daftar sebelah kiri untuk memuat detail batch.
|
||||
</td>
|
||||
@@ -357,7 +358,7 @@
|
||||
const tableBody = document.getElementById('batchesTableBody');
|
||||
tableBody.innerHTML = `
|
||||
<tr>
|
||||
<td colspan="9" class="no-data">
|
||||
<td colspan="10" class="no-data">
|
||||
<i class="fa-solid fa-circle-notch fa-spin"></i>
|
||||
Memuat data batch untuk tanggal ${date}...
|
||||
</td>
|
||||
@@ -383,7 +384,7 @@
|
||||
if (!data.batches || data.batches.length === 0) {
|
||||
tableBody.innerHTML = `
|
||||
<tr>
|
||||
<td colspan="9" class="no-data">
|
||||
<td colspan="10" class="no-data">
|
||||
<i class="fa-solid fa-inbox"></i>
|
||||
Tidak ada batch tercatat pada tanggal ${date}.
|
||||
</td>
|
||||
@@ -411,6 +412,15 @@
|
||||
<td>${startStr}</td>
|
||||
<td>${endStr}</td>
|
||||
<td>${b.duration_minutes || 0} min</td>
|
||||
<td style="text-align: center;">
|
||||
<button type="button" class="btn btn-secondary"
|
||||
style="padding: 5px 10px; font-size: 12px; gap: 6px;"
|
||||
data-date="${escapeHtmlHist(selectedDate)}"
|
||||
data-batch="${b.batch_number}"
|
||||
onclick="downloadBatchClip(this)">
|
||||
<i class="fa-solid fa-film"></i> Klip
|
||||
</button>
|
||||
</td>
|
||||
`;
|
||||
tableBody.appendChild(tr);
|
||||
});
|
||||
@@ -419,7 +429,7 @@
|
||||
console.error('Failed to load date details:', err);
|
||||
tableBody.innerHTML = `
|
||||
<tr>
|
||||
<td colspan="9" class="no-data" style="color: var(--accent-red);">
|
||||
<td colspan="10" class="no-data" style="color: var(--accent-red);">
|
||||
<i class="fa-solid fa-circle-exclamation"></i>
|
||||
Gagal memuat detail data: ${err.message}
|
||||
</td>
|
||||
@@ -434,6 +444,46 @@
|
||||
})[c]);
|
||||
}
|
||||
|
||||
// Klip: fetch office-only /api/batch-clip/<date>/<batch> (may take ~30s),
|
||||
// then save the blob under the filename from Content-Disposition.
|
||||
async function downloadBatchClip(btn) {
|
||||
if (btn.disabled) return;
|
||||
const date = btn.dataset.date;
|
||||
const batch = btn.dataset.batch;
|
||||
const original = btn.innerHTML;
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<i class="fa-solid fa-circle-notch fa-spin"></i> Memuat...';
|
||||
try {
|
||||
const res = await fetch(`/api/batch-clip/${encodeURIComponent(date)}/${encodeURIComponent(batch)}`);
|
||||
if (!res.ok) {
|
||||
let msg = `Gagal mengunduh klip (HTTP ${res.status})`;
|
||||
try {
|
||||
const data = await res.json();
|
||||
if (data && data.error) msg = data.error;
|
||||
} catch (e) { /* body bukan JSON */ }
|
||||
alert(msg);
|
||||
return;
|
||||
}
|
||||
const blob = await res.blob();
|
||||
const cd = res.headers.get('Content-Disposition') || '';
|
||||
const m = cd.match(/filename="?([^";]+)"?/);
|
||||
const filename = m ? m[1] : `b${batch}-${date}.mp4`;
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
setTimeout(() => URL.revokeObjectURL(url), 60000);
|
||||
} catch (err) {
|
||||
alert('Gagal mengunduh klip: ' + err.message);
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = original;
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', loadDates);
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in new issue
Block a user