<?php
// Server admin disk-usage page. Root-only -- both the page-permission
// whitelist in libs/permission_manager.php AND this guard enforce that.
// Renders the latest cached snapshot taken by scripts/disk-usage-snapshot.php
// (cron every 6h) and lets root force a refresh / clean specific candidates.

if (!defined('AUTH_USER') || AUTH_USER !== 'root') {
    echo '<div class="alert alert-danger">Root access required.</div>';
    return;
}

require_once('/docker/whp/web/libs/mysqlmgmt.php');
require_once('/docker/whp/web/libs/disk_usage_snapshot.php');

$snapshot = null;
$snapshot_at = null;
$duration_ms = 0;

try {
    $du_mysql = new mysqlmgmt();
    $du_db = $du_mysql->getMySQLConnection();
    if ($du_db) {
        $du_db->query("USE whp");
        $row = whp_du_load_snapshot($du_db);
        if ($row) {
            $snapshot = $row['payload'];
            $snapshot_at = $row['snapshot_at'];
            $duration_ms = $row['duration_ms'];
        }
    }
} catch (\Throwable $e) {
    error_log('Disk-usage page DB load failed: ' . $e->getMessage());
}

function whp_du_human_bytes($bytes) {
    $bytes = (int)$bytes;
    if ($bytes < 1024) return $bytes . ' B';
    $units = ['KB', 'MB', 'GB', 'TB'];
    $i = -1;
    do { $bytes /= 1024; $i++; } while ($bytes >= 1024 && $i < count($units) - 1);
    return number_format($bytes, $bytes < 10 ? 2 : 1) . ' ' . $units[$i];
}
?>

<div class="card mb-3">
    <div class="card-body d-flex flex-wrap align-items-center justify-content-between gap-2">
        <div>
            <strong>Server disk usage</strong>
            <?php if ($snapshot_at): ?>
                <span class="text-muted ms-2">last refreshed
                    <code id="du-snapshot-at"><?php echo htmlspecialchars($snapshot_at); ?></code>
                    (took <?php echo intval($duration_ms); ?>ms)
                </span>
            <?php else: ?>
                <span class="text-warning ms-2">no snapshot recorded yet</span>
            <?php endif; ?>
        </div>
        <div>
            <button type="button" class="btn btn-sm btn-primary" id="du-refresh-btn">
                <i class="fa fa-refresh"></i> Refresh now
            </button>
        </div>
    </div>
</div>

<?php if ($snapshot):
    // Dangling-image summary used by the Docker tab's prune card.
    $images_row = null;
    foreach (($snapshot['docker']['system_df'] ?? []) as $r) {
        if (strcasecmp($r['type'] ?? '', 'Images') === 0) { $images_row = $r; break; }
    }
    $dangling_count = max(0, ($images_row['total'] ?? 0) - ($images_row['active'] ?? 0));
    $reclaimable = $images_row['reclaimable'] ?? '';
?>

<!-- Filesystems strip -->
<div class="card mb-3">
    <div class="card-header"><i class="fa fa-hdd-o"></i> Filesystems</div>
    <div class="card-body p-0">
        <div class="table-responsive">
            <table class="table table-sm mb-0" data-whp-table data-whp-page-size="0">
                <thead><tr><th>Mount</th><th>Source</th><th>Type</th><th class="text-end">Size</th><th class="text-end">Used</th><th class="text-end">Avail</th><th class="text-end">Used %</th></tr></thead>
                <tbody>
                <?php foreach ($snapshot['filesystems'] as $fs):
                    $pct = (int) trim($fs['pcent'], '%');
                    $cls = $pct >= 95 ? 'table-danger' : ($pct >= 80 ? 'table-warning' : '');
                ?>
                    <tr class="<?php echo $cls; ?>">
                        <td><code><?php echo htmlspecialchars($fs['target']); ?></code></td>
                        <td class="small"><?php echo htmlspecialchars($fs['source']); ?></td>
                        <td class="small"><?php echo htmlspecialchars($fs['fstype']); ?></td>
                        <td class="text-end"><?php echo htmlspecialchars($fs['size']); ?></td>
                        <td class="text-end"><?php echo htmlspecialchars($fs['used']); ?></td>
                        <td class="text-end"><?php echo htmlspecialchars($fs['avail']); ?></td>
                        <td class="text-end"><strong><?php echo htmlspecialchars($fs['pcent']); ?></strong></td>
                    </tr>
                <?php endforeach; ?>
                </tbody>
            </table>
        </div>
    </div>
</div>

<ul class="nav nav-tabs mb-3" role="tablist">
    <li class="nav-item"><a class="nav-link active" data-bs-toggle="tab" href="#du-users" role="tab">Users (<?php echo count($snapshot['users']); ?>)</a></li>
    <li class="nav-item"><a class="nav-link" data-bs-toggle="tab" href="#du-services" role="tab">Services / Logs (<?php echo count($snapshot['services']); ?>)</a></li>
    <li class="nav-item"><a class="nav-link" data-bs-toggle="tab" href="#du-docker" role="tab">Docker (<?php echo count($snapshot['docker']['images']); ?> img / <?php echo count($snapshot['docker']['containers']); ?> ctr)</a></li>
    <li class="nav-item"><a class="nav-link" data-bs-toggle="tab" href="#du-cleanup" role="tab">Cleanup candidates (<?php echo count($snapshot['cleanup_candidates']); ?>)</a></li>
</ul>

<div class="tab-content">
    <!-- Users -->
    <div class="tab-pane fade show active" id="du-users" role="tabpanel">
        <div class="table-responsive">
            <table class="table table-sm table-hover" data-whp-table data-whp-page-size="25">
                <thead><tr><th>User</th><th class="text-end">Total</th><th>Top sites/dirs</th></tr></thead>
                <tbody>
                <?php foreach ($snapshot['users'] as $u): ?>
                    <tr>
                        <td><code><?php echo htmlspecialchars($u['username']); ?></code></td>
                        <td class="text-end"><strong><?php echo htmlspecialchars(whp_du_human_bytes($u['bytes'])); ?></strong></td>
                        <td class="small">
                            <?php
                            $top = array_slice($u['breakdown'] ?? [], 0, 5);
                            $parts = [];
                            foreach ($top as $sub) {
                                $parts[] = htmlspecialchars($sub['name']) . ' (' . htmlspecialchars(whp_du_human_bytes($sub['bytes'])) . ')';
                            }
                            echo implode(' · ', $parts);
                            ?>
                        </td>
                    </tr>
                <?php endforeach; ?>
                </tbody>
            </table>
        </div>
    </div>

    <!-- Services / logs -->
    <div class="tab-pane fade" id="du-services" role="tabpanel">
        <p class="small text-muted">
            "Truncate" zeroes a live log file in place (preserves the daemon's open FD).
            "Clear" deletes rotated/archive files. Both run through <code>api/disk-cleanup.php</code>'s
            re-validating execute path.
        </p>
        <div class="table-responsive">
            <table class="table table-sm table-hover" data-whp-table data-whp-page-size="25">
                <thead><tr><th>Path</th><th>Label</th><th class="text-end">Size</th><th class="text-end" data-whp-nosort>Action</th></tr></thead>
                <tbody>
                <?php foreach ($snapshot['services'] as $s): ?>
                    <tr>
                        <td><code><?php echo htmlspecialchars($s['path']); ?></code></td>
                        <td><?php echo htmlspecialchars($s['label']); ?></td>
                        <td class="text-end"><?php echo htmlspecialchars(whp_du_human_bytes($s['bytes'])); ?></td>
                        <td class="text-end">
                            <?php if ($s['cleanup_hint'] === 'truncate_active'): ?>
                                <button class="btn btn-sm btn-outline-warning du-action"
                                        data-action="truncate"
                                        data-path="<?php echo htmlspecialchars($s['path'], ENT_QUOTES); ?>">
                                    Truncate
                                </button>
                            <?php elseif ($s['cleanup_hint'] === 'rotated_logs'): ?>
                                <button class="btn btn-sm btn-outline-secondary du-action"
                                        data-action="clear_rotated"
                                        data-path="<?php echo htmlspecialchars($s['path'], ENT_QUOTES); ?>">
                                    Clear rotated
                                </button>
                            <?php elseif ($s['cleanup_hint'] === 'safe_clear'): ?>
                                <button class="btn btn-sm btn-outline-danger du-action"
                                        data-action="safe_clear"
                                        data-path="<?php echo htmlspecialchars($s['path'], ENT_QUOTES); ?>">
                                    Clear directory
                                </button>
                            <?php elseif ($s['cleanup_hint'] === 'journal_vacuum'): ?>
                                <button class="btn btn-sm btn-outline-warning du-action"
                                        data-action="journal_vacuum"
                                        data-path="journalctl">
                                    Vacuum to 200M
                                </button>
                            <?php else: ?>
                                <span class="text-muted small">—</span>
                            <?php endif; ?>
                        </td>
                    </tr>
                <?php endforeach; ?>
                </tbody>
            </table>
        </div>
    </div>

    <!-- Docker -->
    <div class="tab-pane fade" id="du-docker" role="tabpanel">
        <?php if ($dangling_count > 0): ?>
        <!-- Reclaim untagged Docker images. Same single-card pattern as
             the page's Refresh card. Prune runs asynchronously (kicked
             off in the background; the page polls for completion) --
             reclaiming tens of GBs of layers exceeds the FPM/HAProxy
             request timeout if we ran it inline. -->
        <div class="card mb-3" id="du-prune-card">
            <div class="card-body d-flex flex-wrap align-items-center justify-content-between gap-2">
                <div>
                    <strong><i class="fa fa-trash text-warning"></i> Reclaim untagged Docker images</strong>
                    <span class="text-muted ms-2">
                        <?php echo intval($dangling_count); ?> dangling image(s)
                        <?php if ($reclaimable !== ''): ?>
                            &middot; ~<?php echo htmlspecialchars($reclaimable); ?> reclaimable
                        <?php endif; ?>
                    </span>
                    <div class="small text-muted mt-1">
                        Removes only <code>&lt;none&gt;:&lt;none&gt;</code> images that no container references.
                        Tagged-but-unused images stay; in-use dangling layers are protected by Docker.
                        Runs as a background job &mdash; takes a few minutes for large reclaims.
                    </div>
                </div>
                <div>
                    <button type="button" class="btn btn-sm btn-warning" id="du-prune-btn">
                        <i class="fa fa-broom"></i> Reclaim now
                    </button>
                </div>
            </div>
            <div class="card-footer d-none small" id="du-prune-status">
                <span id="du-prune-status-msg"></span>
            </div>
        </div>
        <?php endif; ?>

        <h6>System overview</h6>
        <div class="table-responsive">
            <table class="table table-sm" data-whp-table data-whp-page-size="0">
                <thead><tr><th>Type</th><th class="text-end">Total</th><th class="text-end">Active</th><th class="text-end">Size</th><th class="text-end">Reclaimable</th></tr></thead>
                <tbody>
                <?php foreach ($snapshot['docker']['system_df'] as $r): ?>
                    <tr>
                        <td><?php echo htmlspecialchars($r['type']); ?></td>
                        <td class="text-end"><?php echo intval($r['total']); ?></td>
                        <td class="text-end"><?php echo intval($r['active']); ?></td>
                        <td class="text-end"><?php echo htmlspecialchars($r['size']); ?></td>
                        <td class="text-end"><?php echo htmlspecialchars($r['reclaimable']); ?></td>
                    </tr>
                <?php endforeach; ?>
                </tbody>
            </table>
        </div>
        <h6 class="mt-3">Images</h6>
        <div class="table-responsive">
            <table class="table table-sm table-hover" data-whp-table data-whp-page-size="25">
                <thead><tr><th>Repository</th><th>Tag</th><th class="text-end">Size</th><th>Created</th></tr></thead>
                <tbody>
                <?php foreach ($snapshot['docker']['images'] as $r): ?>
                    <tr>
                        <td class="small"><code><?php echo htmlspecialchars($r['repository']); ?></code></td>
                        <td class="small"><?php echo htmlspecialchars($r['tag']); ?></td>
                        <td class="text-end"><?php echo htmlspecialchars($r['size']); ?></td>
                        <td class="small text-muted"><?php echo htmlspecialchars($r['created']); ?></td>
                    </tr>
                <?php endforeach; ?>
                </tbody>
            </table>
        </div>
        <h6 class="mt-3">Containers</h6>
        <div class="table-responsive">
            <table class="table table-sm table-hover" data-whp-table data-whp-page-size="25">
                <thead><tr><th>Name</th><th>Status</th><th>Image</th><th class="text-end">Size</th></tr></thead>
                <tbody>
                <?php foreach ($snapshot['docker']['containers'] as $r): ?>
                    <tr>
                        <td class="small"><code><?php echo htmlspecialchars($r['name']); ?></code></td>
                        <td class="small"><?php echo htmlspecialchars($r['status']); ?></td>
                        <td class="small text-muted"><?php echo htmlspecialchars($r['image']); ?></td>
                        <td class="text-end small"><?php echo htmlspecialchars($r['size']); ?></td>
                    </tr>
                <?php endforeach; ?>
                </tbody>
            </table>
        </div>
        <p class="small mt-2 mb-0">
            For per-image / per-container actions, use the
            <a href="index.php?page=docker-management"><i class="fa fa-cube"></i> Docker Management</a> page.
            The "Reclaim untagged images" card at the top handles dangling-image cleanup.
        </p>
    </div>

    <!-- Cleanup candidates -->
    <div class="tab-pane fade" id="du-cleanup" role="tabpanel">
        <p class="small text-muted">
            Same detection as the critical-disk banner: rotated logs &gt; 30d, oversized
            Docker JSON logs, archived journals. Selecting Execute calls
            <code>api/disk-cleanup.php?action=execute</code> which re-validates each path.
        </p>
        <?php if (empty($snapshot['cleanup_candidates'])): ?>
            <p class="text-muted">No cleanup candidates detected.</p>
        <?php else: ?>
            <div class="mb-2">
                <button class="btn btn-sm btn-outline-secondary" id="du-cleanup-all"><i class="fa fa-check-square-o"></i> Select all</button>
                <button class="btn btn-sm btn-danger" id="du-cleanup-execute"><i class="fa fa-trash"></i> Execute selected</button>
            </div>
            <div class="table-responsive">
                <table class="table table-sm" data-whp-table data-whp-page-size="25">
                    <thead><tr><th data-whp-nosort></th><th>Path</th><th class="text-end">Size</th><th>Reason</th></tr></thead>
                    <tbody id="du-cleanup-tbody">
                    <?php foreach ($snapshot['cleanup_candidates'] as $c): ?>
                        <tr>
                            <td><input type="checkbox" class="du-cleanup-cb" data-path="<?php echo htmlspecialchars($c['path'], ENT_QUOTES); ?>"></td>
                            <td class="small"><code><?php echo htmlspecialchars($c['path']); ?></code></td>
                            <td class="text-end small"><?php echo htmlspecialchars(whp_du_human_bytes($c['size'])); ?></td>
                            <td class="small text-muted"><?php echo htmlspecialchars($c['reason']); ?></td>
                        </tr>
                    <?php endforeach; ?>
                    </tbody>
                </table>
            </div>
        <?php endif; ?>
    </div>
</div>

<?php else: ?>

<div class="alert alert-info">
    <i class="fa fa-info-circle"></i>
    No snapshot has been recorded yet. The cron runs every 6 hours; click
    <strong>Refresh now</strong> above to take one immediately.
</div>

<?php endif; ?>

<script>
(function() {
    var csrf = <?php echo json_encode($_SESSION['csrf_token'] ?? ''); ?>;

    function postJson(url, body) {
        return fetch(url, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrf },
            body: JSON.stringify(body || {})
        }).then(function(r) { return r.json(); });
    }

    var refreshBtn = document.getElementById('du-refresh-btn');
    if (refreshBtn) {
        refreshBtn.addEventListener('click', function() {
            refreshBtn.disabled = true;
            var orig = refreshBtn.textContent;
            refreshBtn.textContent = 'Refreshing...';
            postJson('api/disk-usage.php?action=refresh').then(function(data) {
                if (data.success) {
                    window.location.reload();
                } else {
                    alert('Refresh failed: ' + (data.error || 'unknown'));
                    refreshBtn.disabled = false;
                    refreshBtn.textContent = orig;
                }
            }).catch(function(err) {
                alert('Refresh error: ' + err);
                refreshBtn.disabled = false;
                refreshBtn.textContent = orig;
            });
        });
    }

    // Cleanup-candidate select-all + execute
    var selectAll = document.getElementById('du-cleanup-all');
    if (selectAll) {
        selectAll.addEventListener('click', function() {
            document.querySelectorAll('.du-cleanup-cb').forEach(function(cb) { cb.checked = !cb.checked; });
        });
    }
    var executeBtn = document.getElementById('du-cleanup-execute');
    if (executeBtn) {
        executeBtn.addEventListener('click', function() {
            var paths = [];
            document.querySelectorAll('.du-cleanup-cb:checked').forEach(function(cb) {
                paths.push(cb.dataset.path);
            });
            if (paths.length === 0) { alert('Select at least one item.'); return; }
            if (!confirm('Permanently delete ' + paths.length + ' item(s)?')) return;
            executeBtn.disabled = true;
            postJson('api/disk-cleanup.php?action=execute', { paths: paths }).then(function(data) {
                if (data.success) {
                    alert('Freed ' + (data.freed_mb || 0) + ' MB across ' + (data.deleted || 0) + ' files.');
                    window.location.reload();
                } else {
                    alert('Cleanup failed: ' + (data.error || 'unknown'));
                    executeBtn.disabled = false;
                }
            });
        });
    }

    // Reclaim dangling Docker images. Async: server kicks off a
    // background `docker image prune -f`, returns immediately, page polls
    // for completion. Synchronous would 504 -- pruning ~56GB of layers
    // routinely exceeds the FPM/HAProxy request timeout.
    var pruneBtn = document.getElementById('du-prune-btn');
    var statusBox = document.getElementById('du-prune-status');
    var statusMsg = document.getElementById('du-prune-status-msg');

    function showStatus(msg, kind) {
        statusBox.classList.remove('d-none', 'text-muted', 'text-success', 'text-danger');
        statusBox.classList.add('text-' + (kind || 'muted'));
        statusMsg.textContent = msg;
    }

    function fetchJson(url, opts) {
        return fetch(url, opts || {}).then(function(r) {
            return r.text().then(function(txt) {
                try { return { ok: r.ok, status: r.status, data: JSON.parse(txt) }; }
                catch (_) { return { ok: false, status: r.status, data: null, raw: txt }; }
            });
        });
    }

    function pollPruneStatus() {
        fetchJson('api/disk-cleanup.php?action=prune_status').then(function(res) {
            if (!res.ok || !res.data) {
                showStatus('Status check failed (HTTP ' + res.status + ')', 'danger');
                pruneBtn.disabled = false;
                pruneBtn.innerHTML = '<i class="fa fa-broom"></i> Reclaim now';
                return;
            }
            if (res.data.status === 'running') {
                showStatus('Still running... ' + (res.data.elapsed_seconds || 0) + 's elapsed', 'muted');
                setTimeout(pollPruneStatus, 3000);
            } else if (res.data.status === 'complete') {
                var r = res.data.result || {};
                showStatus((r.message || 'Done') + ' (took ' + (res.data.elapsed_seconds || 0) + 's)', 'success');
                pruneBtn.disabled = false;
                pruneBtn.innerHTML = '<i class="fa fa-broom"></i> Reclaim now';
                setTimeout(function() { window.location.reload(); }, 2500);
            } else {
                showStatus('No prune running.', 'muted');
                pruneBtn.disabled = false;
                pruneBtn.innerHTML = '<i class="fa fa-broom"></i> Reclaim now';
            }
        }).catch(function(err) {
            showStatus('Status check error: ' + err, 'danger');
            pruneBtn.disabled = false;
            pruneBtn.innerHTML = '<i class="fa fa-broom"></i> Reclaim now';
        });
    }

    if (pruneBtn) {
        pruneBtn.addEventListener('click', function() {
            if (!confirm('Reclaim all dangling (untagged) Docker images? Images in use by a container are skipped automatically.')) return;
            pruneBtn.disabled = true;
            pruneBtn.innerHTML = '<i class="fa fa-spinner fa-spin"></i> Pruning...';
            showStatus('Starting background prune...', 'muted');
            fetchJson('api/disk-cleanup.php?action=prune_dangling_images', {
                method: 'POST',
                headers: { 'X-CSRF-Token': csrf, 'Content-Type': 'application/json' },
                body: '{}'
            }).then(function(res) {
                if (!res.ok || !res.data || !res.data.success) {
                    showStatus('Failed to start prune: ' + ((res.data && res.data.error) || 'HTTP ' + res.status), 'danger');
                    pruneBtn.disabled = false;
                    pruneBtn.innerHTML = '<i class="fa fa-broom"></i> Reclaim now';
                    return;
                }
                if (res.data.status === 'already_running') {
                    showStatus('A prune is already in progress.', 'muted');
                } else {
                    showStatus('Background prune started. This typically takes a few minutes.', 'muted');
                }
                setTimeout(pollPruneStatus, 3000);
            }).catch(function(err) {
                showStatus('Start failed: ' + err, 'danger');
                pruneBtn.disabled = false;
                pruneBtn.innerHTML = '<i class="fa fa-broom"></i> Reclaim now';
            });
        });
    }

    // Per-row Truncate / Clear-rotated / Safe-clear / Journal-vacuum.
    document.querySelectorAll('.du-action').forEach(function(btn) {
        btn.addEventListener('click', function() {
            var action = btn.dataset.action;
            var path = btn.dataset.path;
            var msg = {
                truncate:       'Truncate this log file in place (FD preserved)?\n' + path,
                clear_rotated:  'Delete rotated logs older than 30 days under:\n' + path,
                safe_clear:     'Empty this directory completely?\n' + path,
                journal_vacuum: 'Vacuum systemd journal down to 200M?'
            }[action];
            if (!confirm(msg)) return;
            btn.disabled = true;
            postJson('api/disk-cleanup.php?action=' + action, { path: path }).then(function(data) {
                if (data.success) {
                    alert((data.message || 'Done') + (data.freed_mb ? ' Freed ' + data.freed_mb + ' MB.' : ''));
                    window.location.reload();
                } else {
                    alert('Action failed: ' + whpAjaxErrorMessage(data, 'unknown'));
                    btn.disabled = false;
                }
            });
        });
    });
})();
</script>

<script src="/js/whp-table.js?v=<?php echo htmlspecialchars($whp_css_v); ?>"></script>
