Newer
Older
medialib-manager / templates / originals.html
{% 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" %}