<?php
/********************************************
 * FILE BROWSER + Delete / Backup / Push
 * - Push includes modal for selecting live destination
 * - Dev snapshot saved as: repos/{project}/dev-YYYY-MM-DD_HH-MM-SS
 * - Live snapshot saved as: repos/{project}/live-YYYY-MM-DD_HH-MM-SS
 ********************************************/

date_default_timezone_set('Africa/Johannesburg');

// ROOT PATH (one folder up)
$ROOT = realpath(__DIR__ . '/..');

$IGNORE = [
    'node_modules',
    '.git',
    '.env',
    'vendor',
    'backups',
    'cache',
    'error_log',
    'repos',       // ← added
    'delete',      // ← added
    'backups',     // ← added
    '.bash_logout',
    '.bash_profile',
    '.bashrc',
    '.cpanel',
    '.caldav',
    '.ftpquota',
    '.htpasswds',
    '.imunify_patch_id',
    '.koality',
    '.lastlogin',
    '.myimunify_id',
    '.pearrc',
    '.razor',
    '.spamassassin',
    '.spamassassinboxenable',
    '.spamassassinenable',
    '.subaccounts',
    '.trash',
    '.wp-toolkit',
    '.wp-toolkit-identifier',
    'access-logs',
    'bin',
    'composer.json',
    'composer.lock',
    'emailsender',
    'etc',
    '.png',
    'ssl',
    'site_publisher',
    'quarantine',
    'public_ftp',
    'php',
    'perl5',
    'passwords',
    'mail',
    'lscmData',
    'lscache',
    'logs',
    'images',
    'demosite1.elegantwork.co.za',
    'demosite2.elegantwork.co.za',
    'demosite3.elegantwork.co.za',
    'demosite4.elegantwork.co.za',
    'demosite5.elegantwork.co.za',
    'demosite6.elegantwork.co.za',
    'demosite7.elegantwork.co.za',
    'demosite8.elegantwork.co.za',
    'demosite9.elegantwork.co.za',
    'demosite10.elegantwork.co.za',
    'tpm',
    'change-log.txt',
    'www'
];

// Helpers
function safePath($root, $pathFragment)
{
    $candidate = realpath($root . '/' . ltrim($pathFragment, '/'));
    if ($candidate === false)
        return false;
    if (strpos($candidate, $root) !== 0)
        return false;
    return $candidate;
}

function esc($s)
{
    return htmlspecialchars($s, ENT_QUOTES);
}

function formatSize($bytes)
{
    if ($bytes < 1024)
        return $bytes . " B";
    if ($bytes < 1024 * 1024)
        return round($bytes / 1024, 2) . " KB";
    return round($bytes / (1024 * 1024), 2) . " MB";
}

// Recursive copy (copies src contents into dst; overwrites existing files; does not delete extra files in dst)
function rrmdir_copy($src, $dst)
{
    if (!is_dir($src))
        return false;
    if (!is_dir($dst)) {
        if (!mkdir($dst, 0755, true))
            return false;
    }
    $items = scandir($src);
    foreach ($items as $item) {
        if ($item === '.' || $item === '..')
            continue;
        $s = $src . '/' . $item;
        $d = $dst . '/' . $item;
        if (is_dir($s)) {
            // ensure directory exists in dst
            if (!is_dir($d))
                @mkdir($d, 0755, true);
            rrmdir_copy($s, $d);
        } else {
            if (!is_dir(dirname($d)))
                @mkdir(dirname($d), 0755, true);
            @copy($s, $d);
        }
    }
    return true;
}

// Move folder (preferred rename, fallback to copy+delete)
function move_folder_to($src, $dst)
{
    if (@rename($src, $dst))
        return true;
    if (!rrmdir_copy($src, $dst))
        return false;
    // delete original recursively
    $it = new RecursiveDirectoryIterator($src, RecursiveDirectoryIterator::SKIP_DOTS);
    $files = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST);
    foreach ($files as $file) {
        if ($file->isDir()) {
            @rmdir($file->getRealPath());
        } else {
            @unlink($file->getRealPath());
        }
    }
    @rmdir($src);
    return true;
}

// Create zip from folder
function create_zip_from_folder($folderPath, $zipFilePath)
{
    if (!extension_loaded('zip'))
        return false;
    $zip = new ZipArchive();
    if ($zip->open($zipFilePath, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== TRUE) {
        return false;
    }
    $rootLen = strlen(rtrim($folderPath, '/') . '/');
    $files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($folderPath, RecursiveDirectoryIterator::SKIP_DOTS));
    foreach ($files as $file) {
        $filePath = $file->getRealPath();
        $localPath = substr($filePath, $rootLen);
        $zip->addFile($filePath, $localPath);
    }
    $zip->close();
    return true;
}

// Messages
$messages = [];

// Request handling
$path = isset($_REQUEST['path']) ? $_REQUEST['path'] : '';
$fullPath = safePath($ROOT, $path);
if ($fullPath === false) {
    $fullPath = $ROOT;
    $path = '';
}

// Action handling: delete, backup, push_start, push_execute
$action = isset($_REQUEST['action']) ? $_REQUEST['action'] : '';

// DELETE
if ($action === 'delete' && isset($_POST['target'])) {
    $target = $_POST['target'];
    $targetFull = safePath($ROOT, $target);
    if ($targetFull && is_dir($targetFull)) {
        $name = basename($targetFull);
        $stamp = date('Y-m-d_H-i-s');
        $deleteRoot = $ROOT . '/delete/' . date('Y-m-d');
        @mkdir($deleteRoot, 0755, true);
        $dest = $deleteRoot . '/' . $stamp . '-' . $name;
        if (move_folder_to($targetFull, $dest)) {
            $messages[] = "Moved <strong>" . esc($name) . "</strong> to <code>" . esc($dest) . "</code> (soft-deleted).";
        } else {
            $messages[] = "Failed to move folder <strong>" . esc($name) . "</strong> to delete location.";
        }
    } else {
        $messages[] = "Invalid delete target.";
    }
}
// DOWNLOAD ZIP
if ($action === 'download_zip' && isset($_GET['target'])) {
    $target = $_GET['target'];
    $targetFull = safePath($ROOT, $target);

    if ($targetFull) {

        $name = basename($targetFull);
        $stamp = date('Y-m-d_H-i-s');

        $tmpZip = sys_get_temp_dir() . "/{$name}-{$stamp}.zip";

        if (is_dir($targetFull)) {

            if (create_zip_from_folder($targetFull, $tmpZip)) {

                header("Content-Type: application/zip");
                header("Content-Disposition: attachment; filename=\"{$name}.zip\"");
                header("Content-Length: " . filesize($tmpZip));

                readfile($tmpZip);
                unlink($tmpZip);
                exit;

            } else {
                $messages[] = "Failed to create ZIP for download.";
            }

        } elseif (is_file($targetFull)) {

            $mimeType = mime_content_type($targetFull);
            if (strpos($mimeType, 'text/') === 0) {
                header("Content-Type: $mimeType");
                header("Content-Disposition: inline; filename=\"{$name}\"");
                header("Content-Length: " . filesize($targetFull));

                readfile($targetFull);
                exit;
            } else {
                header("Content-Type: application/octet-stream");
                header("Content-Disposition: attachment; filename=\"{$name}\"");
                header("Content-Length: " . filesize($targetFull));

                readfile($targetFull);
                exit;
            }

        } else {
            $messages[] = "Invalid ZIP download target." . $targetFull;
        }

    } else {
        $messages[] = "Invalid ZIP download target." . $targetFull;
    }
}

// POPUP TEXT FILE IN IFRAME
if ($action === 'popup_text' && isset($_GET['target'])) {
    $target = $_GET['target'];
    $targetFull = safePath($ROOT, $target);

    if ($targetFull && is_file($targetFull) && is_readable($targetFull)) {
        $mimeType = mime_content_type($targetFull);
        if (strpos($mimeType, 'text/') === 0) {
            header("Content-Type: $mimeType");
            echo "<html><head><base target=\"_blank\"></head><body><iframe src=\"{$target}\" frameborder=\"0\" width=\"100%\" height=\"100%\"></iframe></body></html>";
            exit;
        }
    }
}
// BACKUP
if ($action === 'backup' && isset($_POST['target'])) {
    $target = $_POST['target'];
    $targetFull = safePath($ROOT, $target);
    if ($targetFull && is_dir($targetFull)) {
        $name = basename($targetFull);
        $stamp = date('Y-m-d_H-i-s');
        $backupRoot = $ROOT . '/backups/' . date('Y-m-d');
        @mkdir($backupRoot, 0755, true);
        $zipName = $backupRoot . '/' . $name . '-' . $stamp . '.zip';
        if (create_zip_from_folder($targetFull, $zipName)) {
            $messages[] = "Created backup <code>" . esc($zipName) . "</code>.";
        } else {
            $messages[] = "Failed to create zip backup for <strong>" . esc($name) . "</strong>. Ensure Zip extension is enabled.";
        }
    } else {
        $messages[] = "Invalid backup target.";
    }
}

// PUSH START: show modal to select destination
$push_start = false;
$push_source = '';
if ($action === 'push_start' && isset($_POST['target'])) {
    $push_source = $_POST['target'];
    $push_source_full = safePath($ROOT, $push_source);
    if ($push_source_full && is_dir($push_source_full)) {
        $push_start = true;
    } else {
        $messages[] = "Invalid push source.";
    }
}
// PUSH EXECUTE
if ($action === 'push_execute' && isset($_POST['source']) && isset($_POST['dest'])) {
    $source = $_POST['source'];
    $dest = $_POST['dest'];
    $username = $_POST['name'];
    $change = $_POST['changes'];

    $sourceFull = safePath($ROOT, $source);
    $destFull = safePath($ROOT, $dest);

    if (!($sourceFull && is_dir($sourceFull))) {
        $messages[] = "Invalid source for push.";
    } elseif (!($destFull && is_dir($destFull))) {
        $messages[] = "Invalid destination (live) for push.";
    } else {

        $name = basename($sourceFull); // domain name
        $stamp = date('Y-m-d_H-i-s');

        // main repos folder for this domain
        $reposRoot = $ROOT . '/repos/' . $name;
        @mkdir($reposRoot, 0755, true);

        // NEW STRUCTURE:
        // /repos/domain/{timestamp}/live
        // /repos/domain/{timestamp}/dev
        $deployFolder = $reposRoot . '/' . $stamp;
        @mkdir($deployFolder, 0755, true);

        // subfolders
        $liveSnapshot = $deployFolder . '/live';
        $devSnapshot = $deployFolder . '/dev';
        @mkdir($liveSnapshot, 0755, true);
        @mkdir($devSnapshot, 0755, true);

        // -------------------------------------------------------
        // SAVE CURRENT LIVE INTO /{timestamp}/live
        // -------------------------------------------------------
        if (rrmdir_copy($destFull, $liveSnapshot)) {
            $messages[] = "Saved current live to <code>" . esc($liveSnapshot) . "</code>.";
        } else {
            $messages[] = "Failed to snapshot current live.";
        }

        // -------------------------------------------------------
        // LOGGING SYSTEM (per domain)
        // -------------------------------------------------------
        $domainLogDir = $ROOT . '/repos.elegantwork.co.za';
        @mkdir($domainLogDir, 0755, true);

        $domainLogPath = $domainLogDir . '/' . $name . '-log.txt';

        $domainLog =
            date('Y-m-d H:i') . "\n" .
            "Source: " . $source . "\n" .
            "Destination: " . $dest . "\n" .
            "User: " . $username . "\n\n" .
            $change .
            "\n\n-------------------------------------------------------------\n\n";

        file_put_contents($domainLogPath, $domainLog, FILE_APPEND);

        $messages[] = "Updated domain log <code>" . esc($domainLogPath) . "</code>.";

        // -------------------------------------------------------
        // SAVE DEV INTO /{timestamp}/dev
        // -------------------------------------------------------
        if (rrmdir_copy($sourceFull, $devSnapshot)) {
            $messages[] = "Saved dev snapshot to <code>" . esc($devSnapshot) . "</code>.";
        } else {
            $messages[] = "Failed to save dev snapshot.";
        }

        // -------------------------------------------------------
        // DEPLOY DEV → LIVE
        // -------------------------------------------------------
        if (rrmdir_copy($sourceFull, $destFull)) {
            $messages[] = "Deployed dev → live. Live-only files preserved.";
        } else {
            $messages[] = "Failed to copy dev into live.";
        }
    }
}
// List directory for display
$items = @scandir($fullPath);
if ($items === false)
    $items = [];

// Build candidate list for destinations (folders inside $ROOT, excluding ignored)
$deployCandidates = [];
$rootItems = @scandir($ROOT);
if ($rootItems !== false) {
    foreach ($rootItems as $ri) {
        if ($ri === '.' || $ri === '..')
            continue;
        if (in_array($ri, $IGNORE))
            continue;
        $rf = $ROOT . '/' . $ri;
        if (is_dir($rf))
            $deployCandidates[] = $ri;
    }
}
?>
<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <title>Server File Browser + Actions</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            padding: 20px;
            background: #f4f6f9;
        }

        h2 {
            margin-bottom: 10px;
        }

        table {
            width: 100%;
            border-collapse: collapse;
            background: white;
            box-shadow: 0 0 0 1px #e9eef3 inset;
        }

        th,
        td {
            padding: 10px;
            border-bottom: 1px solid #eaeef2;
            text-align: left;
            vertical-align: middle;
        }

        tr:hover {
            background: #f8fbff;
        }

        a {
            text-decoration: none;
            color: #1976d2;
        }

        .folder {
            font-weight: bold;
        }

        .breadcrumb {
            margin-bottom: 12px;
            padding: 10px;
            background: #fff;
            border-radius: 6px;
        }

        .btn {
            display: inline-block;
            padding: 6px 10px;
            border-radius: 6px;
            border: none;
            cursor: pointer;
            text-decoration: none;
            font-size: 13px;
        }

        .btn-danger {
            background: #e53935;
            color: white;
        }

        .btn-warning {
            background: #ffa000;
            color: white;
        }

        .btn-primary {
            background: #1976d2;
            color: white;
        }

        .muted {
            color: #777;
            font-size: 13px;
        }

        .msg {
            padding: 8px 12px;
            background: #fff;
            border-left: 4px solid #1976d2;
            margin-bottom: 10px;
            border-radius: 4px;
        }

        .small {
            font-size: 12px;
            color: #666;
        }

        form.inline {
            display: inline;
            margin: 0;
            padding: 0;
        }

        /* Simple modal */
        .modal-backdrop {
            position: fixed;
            left: 0;
            top: 0;
            right: 0;
            bottom: 0;
            background: rgba(0, 0, 0, 0.4);
            display: none;
            align-items: center;
            justify-content: center;
            z-index: 999;
        }

        .modal {
            background: #fff;
            padding: 18px;
            border-radius: 8px;
            width: 720px;
            max-width: 95%;
            box-shadow: 0 8px 30px rgba(0, 0, 0, 0.2);
        }

        .modal h3 {
            margin-top: 0;
        }

        .modal .row {
            margin-bottom: 10px;
        }

        .modal .muted {
            margin-bottom: 8px;
            display: block;
        }

        .modal .actions {
            text-align: right;
            margin-top: 12px;
        }

        /* show modal when body has .show-modal */
        body.show-modal .modal-backdrop {
            display: flex;
        }
    </style>
    <script>
        function showLoading() {
            var popup = document.getElementById("loadingPopup");
            if (!popup) return;
            popup.style.display = "flex";
            var bar = document.getElementById("progressBar");
            var text = document.getElementById("progressText");

            var pct = 0;

            var interval = setInterval(function () {
                pct += Math.random() * 8; // random natural progression
                if (pct >= 99.5) pct = 99.5; // hold near-complete until action finishes (server navigation)
                bar.style.width = pct + "%";
                text.innerText = Math.floor(pct) + "%";
            }, 120);

            // safety: ensure 100% after 30s
            setTimeout(function () {
                clearInterval(interval);
                bar.style.width = "100%";
                text.innerText = "100%";
            }, 30000);
        }

        function confirmAction(text, formId) {
            if (confirm(text)) {
                // show loading before submit
                showLoading();
                document.getElementById(formId).submit();
            }
        }

        function openPushModal(source) {
            // set source value in hidden input and show modal
            var srcInput = document.getElementById('modal_source');
            srcInput.value = source;
            // attempt to pick suggested dest in dropdown
            var sel = document.querySelector('.modal select[name="dest"]');
            if (sel) {
                var base = source;
                // if source starts with "dev." remove it for suggested
                if (base.indexOf('dev.') === 0) base = base.substring(4);
                // find option that matches base
                for (var i = 0; i < sel.options.length; i++) {
                    if (sel.options[i].value === base) {
                        sel.selectedIndex = i;
                        break;
                    }
                }
            }
            document.body.classList.add('show-modal');
        }

        function closeModal() {
            document.body.classList.remove('show-modal');
        }

        // Capture clicks on action buttons (delete, backup, push) as a fallback
        document.addEventListener("click", function (e) {
            if (e.target.classList && e.target.classList.contains("action-btn")) {
                // don't call showLoading here if confirmAction will call it (to avoid double)
                // but for direct submits we want to show it
                // we will not call showLoading here to avoid race with confirm() dialogs
            }
        });

        // When modal form is submitted, show loading after user confirms via onsubmit handler
    </script>
</head>

<body>
    <!-- LOADING POPUP -->
    <div id="loadingPopup" style="display:none; position:fixed; left:0; top:0; width:100%; height:100%;
            background:rgba(0,0,0,0.5); backdrop-filter:blur(3px);
            z-index:9999; justify-content:center; align-items:center;">

        <div style="background:white; padding:30px; border-radius:10px;
                width:300px; text-align:center; font-family:Arial;">
            <h3>Processing...</h3>
            <div id="progressBox"
                style="width:100%; height:20px; background:#eee; border-radius:10px; overflow:hidden; margin-bottom:10px;">
                <div id="progressBar" style="height:100%; width:0%; background:#4caf50;"></div>
            </div>
            <p id="progressText">0%</p>
        </div>
    </div>
    <h1>Server File Browser</h1>

    <div style="margin-bottom:20px; display:flex; gap:30px; font-size:20px;">
        <a href="?path=" style="text-decoration:none;">
            🏠 <strong>Home</strong>
        </a>

        <a href="?path=repos" style="text-decoration:none;">
            📦 <strong>Repos</strong>
        </a>

        <a href="?path=backups" style="text-decoration:none;">
            💾 <strong>Backups</strong>
        </a>

        <a href="?path=delete" style="text-decoration:none;">
            🗑️ <strong>BIN</strong>
        </a>

        <a onclick="open_logs()" style="text-decoration:none;">
            📜 <strong>Logs</strong>
        </a>
    </div>
    <?php
    if (!empty($messages)) {
        foreach ($messages as $m) {
            echo "<div class='msg'>" . $m . "</div>";
        }
    }
    ?>

    <script>
        function open_logs() {

            // Create overlay
            var overlay = document.createElement('div');
            overlay.style.position = "fixed";
            overlay.style.top = "0";
            overlay.style.left = "0";
            overlay.style.width = "100%";
            overlay.style.height = "100%";
            overlay.style.backgroundColor = "rgba(0,0,0,0.5)";
            overlay.style.zIndex = "9999";

            // Modal container
            var modal = document.createElement('div');
            modal.style.position = "absolute";
            modal.style.top = "50%";
            modal.style.left = "50%";
            modal.style.transform = "translate(-50%, -50%)";
            modal.style.width = "900px";
            modal.style.height = "600px";
            modal.style.background = "#fff";
            modal.style.borderRadius = "10px";
            modal.style.boxShadow = "0 0 20px #000";
            modal.style.display = "flex";

            // Sidebar for list of logs
            var sidebar = document.createElement('div');
            sidebar.style.width = "250px";
            sidebar.style.borderRight = "1px solid #ccc";
            sidebar.style.overflowY = "auto";
            sidebar.style.padding = "10px";
            sidebar.innerHTML = "<h3>Log Files</h3>";

            // Iframe viewer
            var iframe = document.createElement('iframe');
            iframe.style.flex = "1";
            iframe.style.border = "0";

            // Load list of logs dynamically
            fetch("list_logs.php")
                .then(res => res.json())
                .then(files => {
                    if (files.length === 0) {
                        sidebar.innerHTML += "<div>No log files found.</div>";
                    }
                    files.forEach(file => {
                        var btn = document.createElement('div');
                        btn.style.padding = "6px";
                        btn.style.margin = "4px 0";
                        btn.style.cursor = "pointer";
                        btn.style.border = "1px solid #ddd";
                        btn.style.borderRadius = "5px";
                        btn.innerText = file;
                        btn.onclick = () => {
                            iframe.src = "view_log.php?file=" + encodeURIComponent(file);
                        };
                        sidebar.appendChild(btn);
                    });
                });

            // Close button
            var closeBtn = document.createElement('button');
            closeBtn.innerText = "Close";
            closeBtn.style.position = "absolute";
            closeBtn.style.top = "-40px";
            closeBtn.style.right = "0";
            closeBtn.style.padding = "10px 20px";
            closeBtn.style.cursor = "pointer";
            closeBtn.onclick = () => document.body.removeChild(overlay);

            // Build modal
            modal.appendChild(sidebar);
            modal.appendChild(iframe);
            modal.appendChild(closeBtn);

            overlay.appendChild(modal);
            document.body.appendChild(overlay);
        }
    </script>

    <div class="breadcrumb">
        <strong>Path:</strong> /
        <?php
        $parts = explode('/', $path);
        $accum = '';
        foreach ($parts as $p) {
            if ($p === '')
                continue;
            $accum .= $p . '/';
            echo "<a href='?path=" . urlencode($accum) . "'>" . esc($p) . "</a> / ";
        }
        ?>
    </div>

    <table>
        <tr>
            <th>Name</th>
            <th>Size</th>
            <th>Type</th>
            <th>Actions</th>
        </tr>

        <?php
        // UP ONE LEVEL
        if ($path !== '') {
            $parent = dirname($path);
            if ($parent === '.')
                $parent = '';
            echo "<tr><td><a href='?path=" . urlencode($parent) . "'>⬅ Back</a></td><td></td><td></td><td></td></tr>";
        }

        foreach ($items as $item) {
            if ($item === '.' || $item === '..')
                continue;
            if (in_array($item, $IGNORE))
                continue;
            if (stripos($item, '.png') !== false)
                continue;
            if (stripos($item, '.jpg') !== false)
                continue;

            if ($path === '') {
                if (stripos($item, 'dev.') === false) {
                    continue;

                }
            }

            $itemPath = $fullPath . '/' . $item;
            $relPath = trim($path . '/' . $item, '/');

            if (is_dir($itemPath)) {
                echo "<tr>";
                echo "<td class='folder'>📁 <a href='?path=" . urlencode($relPath) . "'>" . esc($item) . "</a></td>";
                echo "<td>—</td>";
                echo "<td>Folder</td>";

                // Actions: Delete, Backup, Push (forms)
                $deleteFormId = 'del_' . md5($relPath);
                echo "<td>";

                // Delete form
                echo "<form id='$deleteFormId' class='inline' method='post' onsubmit='return false;'>
                    <input type='hidden' name='action' value='delete'>
                    <input type='hidden' name='target' value='" . esc($relPath) . "'>
                  </form>";
                echo "<button class='btn btn-danger action-btn' onclick=\"confirmAction('Move folder " . esc($item) . " to delete (soft-delete)?', '$deleteFormId')\">Delete</button> ";

                // Backup form
                $backupFormId = 'bak_' . md5($relPath);
                echo "<form id='$backupFormId' class='inline' method='post' onsubmit='return false;'>
                    <input type='hidden' name='action' value='backup'>
                    <input type='hidden' name='target' value='" . esc($relPath) . "'>
                  </form>";
                echo "<button class='btn btn-warning action-btn' onclick=\"confirmAction('Create ZIP backup for " . esc($item) . "?', '$backupFormId')\">Backup (ZIP)</button> ";

                // Push button uses modal
                echo "<button class='btn btn-primary action-btn'  onclick=\"openPushModal('" . esc($relPath) . "')\">Push</button> ";
                // DOWNLOAD BUTTON
                echo "<a class='btn btn-primary'
                       href=\"?action=download_zip&target=" . urlencode(trim($path . '/' . $item, '/')) . "\"
                       onclick=\"showLoading();\">
                    ⬇ Download
                </a>";
                echo "</td>";
                echo "</tr>";
            } else {
                echo "<tr>";
                echo "<td>📄 " . esc($item) . "</td>";
                echo "<td>" . (@filesize($itemPath) ? formatSize(filesize($itemPath)) : '-') . "</td>";
                echo "<td>File</td>";
                echo "<td>";
                // DOWNLOAD BUTTON
                echo "<a class='btn btn-primary'
                       href=\"?action=download_zip&target=" . urlencode(trim($path . '/' . $item, '/')) . "\"
                       onclick=\"showLoading();\">
                    ⬇ Download
                </a>";
                // Actions: Delete, Backup, Push (forms)
                $deleteFormId = 'del_' . md5($relPath);
                // Delete form
                echo "<form id='$deleteFormId' class='inline' method='post' onsubmit='return false;'>
                    <input type='hidden' name='action' value='delete'>
                    <input type='hidden' name='target' value='" . esc($relPath) . "'>
                  </form>";
                echo "<button class='btn btn-danger action-btn' onclick=\"confirmAction('Move folder " . esc($item) . " to delete (soft-delete)?', '$deleteFormId')\">Delete</button> ";

                echo "</td>";
                echo "</tr>";
            }
        }
        ?>
    </table>

    <!-- Push Modal -->
    <div class="modal-backdrop" onclick="closeModal();">
        <div class="modal" onclick="event.stopPropagation();">
            <h3>Push (deploy) — select destination (live)</h3>
            <div class="muted small">Select the live folder that should receive the dev contents. The system will:</div>
            <div class="muted small">1) Snapshot current live to
                <code>/repos/{devFolder}/live-YYYY-MM-DD_HH-MM-SS/</code> (if live exists).<br>
                2) Snapshot dev to <code>/repos/{devFolder}/dev-YYYY-MM-DD_HH-MM-SS/</code>.<br>
                3) Copy dev files into the selected live folder, <strong>overwriting</strong> files from dev but
                <strong>preserving</strong> any extra files already present in live (images/logs).
            </div>

            <form method="post"
                onsubmit="if(confirm('Execute push now? This will overwrite files in the selected live folder using dev contents.')) { showLoading(); return true; } return false;">
                <input type="hidden" name="action" value="push_execute">
                <input type="hidden" name="source" id="modal_source" value="">
                <div class="row" style="margin-top:12px;">
                    <label><strong>Destination (live) folder:</strong></label><br />
                    <select name="dest" style="padding:8px; width:100%; margin-top:6px;">
                        <?php
                        // Show candidates
                        foreach ($deployCandidates as $cand) {
                            echo "<option value='" . esc($cand) . "'>" . esc($cand) . "</option>";
                        }
                        ?>
                    </select>
                </div>


                <div class="row">
                    <label><strong>Name:</strong></label><br />
                    <input type="text" name="name" placeholder="" style="width:100%; padding:8px; margin-top:6px;">
                </div>
                <div class="row">
                    <label><strong>Changes:</strong></label><br />
                    <textarea name="changes" style="width:100%; padding:8px; margin-top:6px;" rows="4"></textarea>
                </div>


                <div class="row" style="display:none;">
                    <label><strong>Or enter exact folder name (relative to root):</strong></label><br />
                    <input type="text" name="dest_manual" placeholder="optional - e.g. subdomain.example.com"
                        style="width:100%; padding:8px; margin-top:6px;">
                    <div class="small muted">If you provide a manual folder name, server will use it (validated).
                        Otherwise the dropdown value is used.</div>
                </div>

                <div class="actions">
                    <button type="button" class="btn" onclick="closeModal();">Cancel</button>
                    <button type="submit" class="btn btn-primary">Execute Push</button>
                </div>
            </form>
        </div>
    </div>

    <script>
        // Enhance form submit: if dest_manual is filled use that value
        document.addEventListener('submit', function (e) {
            var form = e.target;
            if (form && form.querySelector && form.querySelector('input[name="action"][value="push_execute"]')) {
                var manual = form.querySelector('input[name="dest_manual"]');
                if (manual && manual.value.trim() !== '') {
                    // replace select value with manual
                    var sel = form.querySelector('select[name="dest"]');
                    if (sel) {
                        // create a hidden input with manual value and disable select so server receives manual
                        sel.disabled = true;
                        var hidden = document.createElement('input');
                        hidden.type = 'hidden';
                        hidden.name = 'dest';
                        hidden.value = manual.value.trim();
                        form.appendChild(hidden);
                    }
                }
            }
        }, true);
    </script>

</body>

</html>