{% include "partials/head.html" %}
<div class="container mt-4">
<h1 class="mb-4">// originals</h1>
<div class="card">
<div class="card-header">
<h2 class="h5 mb-0">// backup files</h2>
</div>
<div class="card-body p-0">
<div class="originals-empty text-muted text-center py-4" style="display:none">no backup files found</div>
<table class="table mb-0 originals-table" style="display:none">
<thead>
<tr>
<th>source file</th>
<th>backup file</th>
<th>size</th>
<th>date</th>
<th></th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</div>
<!-- Restore progress modal -->
<div class="modal fade" id="restore-progress-modal" tabindex="-1" data-bs-backdrop="static" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title h6 mb-0">// restoring original</h5>
</div>
<div class="modal-body">
<p class="text-muted mb-2" id="restore-progress-filename"></p>
<div class="progress" style="height: 6px">
<div class="progress-bar progress-bar-striped progress-bar-animated" id="restore-progress-bar" style="width: 0%"></div>
</div>
<small class="text-muted mt-1 d-block" id="restore-progress-pct">0%</small>
</div>
</div>
</div>
</div>
<!-- Confirm restore modal -->
<div class="modal fade" id="confirm-restore" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title h6 mb-0">// restore original</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<p class="mb-1">Restore <strong id="confirm-restore-filename"></strong>?</p>
<small class="text-muted">The current transcoded file will be replaced with this backup.</small>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary btn-sm" data-bs-dismiss="modal">cancel</button>
<button type="button" class="btn btn-warning btn-sm d-inline-flex align-items-center gap-1" id="confirm-restore-btn">
<span class="spinner-border spinner-border-sm" style="display:none"></span>
restore
</button>
</div>
</div>
</div>
</div>
<!-- Confirm delete modal -->
<div class="modal fade" id="confirm-delete-original" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title h6 mb-0">// delete backup</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<p class="mb-1">Delete backup <strong id="confirm-delete-original-filename"></strong>?</p>
<small class="text-muted">The file will be permanently removed from disk.</small>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary btn-sm" data-bs-dismiss="modal">cancel</button>
<button type="button" class="btn btn-danger btn-sm d-inline-flex align-items-center gap-1" id="confirm-delete-original-btn">
<span class="spinner-border spinner-border-sm" style="display:none"></span>
delete
</button>
</div>
</div>
</div>
</div>
<script>
function formatBytes(bytes) {
if (!bytes) return "—";
if (bytes >= 1073741824) return (bytes / 1073741824).toFixed(2) + " GB";
if (bytes >= 1048576) return (bytes / 1048576).toFixed(1) + " MB";
return (bytes / 1024).toFixed(0) + " KB";
}
function loadOriginals() {
$.getJSON("/originals/list", function(items) {
const empty = $(".originals-empty");
const table = $(".originals-table");
const tbody = table.find("tbody");
tbody.empty();
if (!items.length) {
empty.show(); table.hide();
return;
}
empty.hide(); table.show();
items.forEach(item => {
const date = item.started_at
? new Date(item.started_at + "Z").toLocaleString()
: "—";
const sourceCell = item.source_name
? `<a href="/single?path=${encodeURIComponent(item.source_path)}">${item.source_name}</a>`
: `<span class="text-muted">—</span>`;
const restoreBtn = item.operation_id
? `<button class="btn btn-outline-secondary btn-sm btn-restore-original d-inline-flex align-items-center gap-1"
data-operation-id="${item.operation_id}"
data-backup-name="${item.backup_name}"
title="restore">
<span class="spinner-border spinner-border-sm" style="display:none"></span>
<i class="bi bi-arrow-counterclockwise"></i>
</button>`
: '';
tbody.append(`
<tr data-backup-path="${item.backup_path}">
<td>${sourceCell}</td>
<td><small class="text-muted">${item.backup_name}</small></td>
<td><small class="text-muted">${formatBytes(item.size_bytes)}</small></td>
<td><small class="text-muted">${date}</small></td>
<td class="d-flex gap-1 justify-content-end">
${restoreBtn}
<button class="btn btn-outline-secondary btn-sm btn-delete-original"
data-operation-id="${item.operation_id || ''}"
data-backup-path="${item.backup_path}"
data-backup-name="${item.backup_name}"
title="delete">
<span class="spinner-border spinner-border-sm" style="display:none"></span>
<i class="bi bi-trash"></i>
</button>
</td>
</tr>
`);
});
});
}
$(document).ready(function() {
loadOriginals();
// — Restore —
let pendingRestoreId = null;
const restoreModal = new bootstrap.Modal(document.getElementById("confirm-restore"));
const progressModal = new bootstrap.Modal(document.getElementById("restore-progress-modal"));
$(document).on("click", ".btn-restore-original", function() {
pendingRestoreId = $(this).data("operation-id");
$("#confirm-restore-filename").text($(this).data("backup-name"));
restoreModal.show();
});
$("#confirm-restore-btn").on("click", function() {
if (!pendingRestoreId) return;
const id = pendingRestoreId;
const confirmBtn = $(this);
confirmBtn.prop("disabled", true).find(".spinner-border").show();
const backupName = $(`tr[data-operation-id="${id}"] .btn-restore-original`).data("backup-name");
$("#restore-progress-filename").text(backupName);
$("#restore-progress-bar").css("width", "0%");
$("#restore-progress-pct").text("0%");
$.ajax({
url: "/restore-original",
method: "POST",
contentType: "application/json",
data: JSON.stringify({ operation_id: id }),
success: function() {
restoreModal.hide();
confirmBtn.prop("disabled", false).find(".spinner-border").hide();
pendingRestoreId = null;
progressModal.show();
},
error: function(xhr) {
confirmBtn.prop("disabled", false).find(".spinner-border").hide();
restoreModal.hide();
pendingRestoreId = null;
pushErrMsg("Restore failed: " + (xhr.responseJSON?.error || "unknown error"));
}
});
});
document.getElementById("confirm-restore").addEventListener("hidden.bs.modal", function() {
pendingRestoreId = null;
$("#confirm-restore-btn").prop("disabled", false).find(".spinner-border").hide();
});
socket.on("restore-progress", function(data) {
$("#restore-progress-bar").css("width", data.percent + "%");
$("#restore-progress-pct").text(data.percent + "%");
});
socket.on("restore-completed", function() {
progressModal.hide();
loadOriginals();
});
socket.on("restore-error", function(data) {
progressModal.hide();
pushErrMsg("Restore failed: " + data.message);
});
// — Delete —
let pendingDeleteId = null;
const deleteModal = new bootstrap.Modal(document.getElementById("confirm-delete-original"));
let pendingDeleteBtn = null;
$(document).on("click", ".btn-delete-original", function() {
pendingDeleteBtn = $(this);
$("#confirm-delete-original-filename").text($(this).data("backup-name"));
deleteModal.show();
});
$("#confirm-delete-original-btn").on("click", function() {
if (!pendingDeleteBtn) return;
const btn = pendingDeleteBtn;
const confirmBtn = $(this);
confirmBtn.prop("disabled", true).find(".spinner-border").show();
const operationId = btn.data("operation-id") || null;
const backupPath = btn.data("backup-path");
const payload = operationId
? { operation_id: operationId }
: { backup_path: backupPath };
$.ajax({
url: "/delete-backup",
method: "POST",
contentType: "application/json",
data: JSON.stringify(payload),
success: function() {
deleteModal.hide();
confirmBtn.prop("disabled", false).find(".spinner-border").hide();
pendingDeleteBtn = null;
loadOriginals();
},
error: function(xhr) {
confirmBtn.prop("disabled", false).find(".spinner-border").hide();
deleteModal.hide();
pendingDeleteBtn = null;
pushErrMsg("Delete failed: " + (xhr.responseJSON?.error || "unknown error"));
}
});
});
document.getElementById("confirm-delete-original").addEventListener("hidden.bs.modal", function() {
pendingDeleteBtn = null;
$("#confirm-delete-original-btn").prop("disabled", false).find(".spinner-border").hide();
});
});
</script>
{% include "partials/footer.html" %}