403Webshell
Server IP : 213.255.246.8  /  Your IP : 216.73.216.139
Web Server : Apache
System : Linux dublin.stapolin.com 5.14.0-362.18.1.el9_3.x86_64 #1 SMP PREEMPT_DYNAMIC Mon Jan 29 07:05:48 EST 2024 x86_64
User : stapolin ( 1019)
PHP Version : 8.4.24
Disable Function : exec,passthru,shell_exec,system
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : OFF  |  Sudo : ON  |  Pkexec : ON
Directory :  /home/stapolin/public_html/dash.stapolin.com/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /home/stapolin/public_html/dash.stapolin.com/website-dashboard.php
<?php
/**
 * Website dashboard: per-site view with Dashboard, Backups, Themes, Plugins, Users, Uptime tabs.
 * Uses the website sidebar (replaces main nav). Set $websiteForDashboard before including header.
 */

require_once __DIR__ . '/includes/functions.php';

$websiteId = isset($_GET['id']) ? (int)$_GET['id'] : 0;
$tab = isset($_GET['tab']) ? $_GET['tab'] : 'dashboard';
$validTabs = ['dashboard', 'backups', 'themes', 'plugins', 'users', 'uptime', 'history', 'extra'];
if (!in_array($tab, $validTabs)) {
    $tab = 'dashboard';
}

if (!$websiteId) {
    header('Location: updates.php');
    exit;
}

try {
    $db = getDb();
    $website = $db->fetchOne(
        "SELECT w.*, c.client_name
         FROM st_websites w
         LEFT JOIN st_clients c ON w.client_id = c.client_id
         WHERE w.website_id = ?",
        [$websiteId]
    );
    if (!$website) {
        header('Location: updates.php');
        exit;
    }

    // Latest uptime status for is_down
    $latestUptime = $db->fetchOne("SELECT status FROM st_uptime_checks WHERE website_id = ? ORDER BY checked_at DESC LIMIT 1", [$websiteId]);
    $website['is_down'] = $latestUptime && strtolower($latestUptime['status']) === 'down';

    // Update status (has_updates, core/plugin/theme counts)
    $updateStatus = null;
    $updateStatusPlugins = [];
    $updateStatusThemes = [];
    $coreNeedsUpdate = 0;
    try {
        $updateStatus = $db->fetchOne("SELECT * FROM st_update_status WHERE website_id = ?", [$websiteId]);
        if ($updateStatus) {
            $coreNeedsUpdate = !empty($updateStatus['core_needs_update']) ? 1 : 0;
            if (!empty($updateStatus['plugins_json'])) {
                $decoded = json_decode($updateStatus['plugins_json'], true);
                $updateStatusPlugins = is_array($decoded) ? array_filter($decoded, function ($p) { return !empty($p['needs_update']); }) : [];
            }
            if (!empty($updateStatus['themes_json'])) {
                $decoded = json_decode($updateStatus['themes_json'], true);
                $updateStatusThemes = is_array($decoded) ? array_filter($decoded, function ($t) { return !empty($t['needs_update']); }) : [];
            }
        }
    } catch (Exception $e) {}
    $website['has_updates'] = $coreNeedsUpdate || count($updateStatusPlugins) > 0 || count($updateStatusThemes) > 0;

    // Site snapshot (installed plugins, themes, users)
    $siteSnapshot = null;
    $snapshotPlugins = [];
    $snapshotThemes = [];
    $snapshotUsers = [];
    try {
        $siteSnapshot = $db->fetchOne("SELECT * FROM st_site_snapshot WHERE website_id = ?", [$websiteId]);
        if ($siteSnapshot) {
            if (!empty($siteSnapshot['plugins_json'])) {
                $decoded = json_decode($siteSnapshot['plugins_json'], true);
                $snapshotPlugins = is_array($decoded) ? $decoded : [];
            }
            if (!empty($siteSnapshot['themes_json'])) {
                $decoded = json_decode($siteSnapshot['themes_json'], true);
                $snapshotThemes = is_array($decoded) ? $decoded : [];
            }
            if (!empty($siteSnapshot['users_json'])) {
                $decoded = json_decode($siteSnapshot['users_json'], true);
                $snapshotUsers = is_array($decoded) ? $decoded : [];
            }
        }
    } catch (Exception $e) {}

    // Last 5 backups
    $lastBackups = [];
    try {
        $lastBackups = $db->fetchAll("SELECT * FROM st_backups WHERE website_id = ? ORDER BY modified DESC LIMIT 5", [$websiteId]);
    } catch (Exception $e) {}

    // All backups (for Backups tab)
    $allBackups = [];
    try {
        $allBackups = $db->fetchAll("SELECT * FROM st_backups WHERE website_id = ? ORDER BY modified DESC", [$websiteId]);
    } catch (Exception $e) {}

    // Uptime chart data (7 days for dashboard card, 30 for Uptime tab)
    $uptimeChartData7 = getUptimeChartData($websiteId, 7);
    $uptimeChartData30 = getUptimeChartData($websiteId, 30);

    // Update history (for History tab): from st_activity (Core, Plugins, Themes)
    $updateHistory = [];
    $updateHistoryUsers = [];
    $filterType = isset($_GET['filter_type']) ? trim($_GET['filter_type']) : '';
    $filterUser = isset($_GET['filter_user']) ? trim($_GET['filter_user']) : '';
    if (in_array($filterType, ['', 'Core', 'Plugin', 'Theme'], true)) {
        $params = [$websiteId];
        $typeCond = '';
        if ($filterType !== '') {
            $typeCond = ' AND object_type = ?';
            $params[] = $filterType === 'Plugin' ? 'Plugins' : ($filterType === 'Theme' ? 'Themes' : $filterType);
        }
        $userCond = '';
        if ($filterUser !== '') {
            $userCond = ' AND username = ?';
            $params[] = $filterUser;
        }
        $sql = "SELECT object_name AS name, object_type AS type, COALESCE(action, '') AS action, hist_time, COALESCE(username, '') AS username
                FROM st_activity
                WHERE website_id = ? AND object_type IN ('Core','Plugins','Themes')" . $typeCond . $userCond . " ORDER BY hist_time DESC LIMIT 500";
        $updateHistory = $db->fetchAll($sql, $params);
    }
    $updateHistoryUsers = $db->fetchAll(
        "SELECT DISTINCT username FROM st_activity WHERE website_id = ? AND object_type IN ('Core','Plugins','Themes') AND username IS NOT NULL AND username != '' ORDER BY username",
        [$websiteId]
    );

    // Extra work (for Extra tab)
    $extraWork = [];
    try {
        $extraWork = $db->fetchAll("SELECT * FROM st_extra_work WHERE website_id = ? ORDER BY work_date DESC", [$websiteId]);
    } catch (Exception $e) {}

    $websiteForDashboard = $website;
    $websiteDashboardTab = $tab;
    $pageTitle = $website['website_name'] ?: $website['website_url'];
} catch (Exception $e) {
    header('Location: updates.php');
    exit;
}

require_once __DIR__ . '/includes/header.php';

$baseUrl = 'website-dashboard.php?id=' . $websiteId;
?>

<!-- Breadcrumb (in main content area) -->
<div class="flex items-center gap-2 text-sm text-muted-foreground mb-6">
    <a href="updates.php" class="hover:text-foreground">Updates</a>
    <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/></svg>
    <span class="text-foreground"><?= sanitize($website['website_name'] ?: $website['website_url']) ?></span>
</div>

<?php if ($tab === 'dashboard'): ?>
<!-- Dashboard tab: Updates card (tabs), Uptime card, Last 5 backups, Analytics placeholder -->
<?php
    $updateSubTab = isset($_GET['sub']) ? $_GET['sub'] : 'core';
    if (!in_array($updateSubTab, ['core', 'plugins', 'themes'])) $updateSubTab = 'core';
?>
<div class="space-y-6">
    <!-- Updates card with sub-tabs -->
    <div class="card rounded-lg overflow-hidden">
        <div class="border-b">
            <nav class="flex -mb-px">
                <a href="<?= $baseUrl ?>&sub=core" class="px-6 py-4 text-sm font-medium border-b-2 <?= $updateSubTab === 'core' ? 'border-primary text-primary' : 'border-transparent text-muted-foreground' ?> flex items-center gap-2">
                    <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>
                    Core <span class="badge <?= $coreNeedsUpdate ? 'badge-info' : 'badge-secondary' ?>"><?= $coreNeedsUpdate ?></span>
                </a>
                <a href="<?= $baseUrl ?>&sub=plugins" class="px-6 py-4 text-sm font-medium border-b-2 <?= $updateSubTab === 'plugins' ? 'border-primary text-primary' : 'border-transparent text-muted-foreground' ?> flex items-center gap-2">
                    <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 4a2 2 0 114 0v1a1 1 0 001 1h3a1 1 0 011 1v3a1 1 0 01-1 1h-1a2 2 0 100 4h1a1 1 0 011 1v3a1 1 0 01-1 1h-3a1 1 0 01-1-1v-1a2 2 0 10-4 0v1a1 1 0 01-1 1H7a1 1 0 01-1-1v-3a1 1 0 00-1-1H4a2 2 0 110-4h1a1 1 0 001-1V7a1 1 0 011-1h3a1 1 0 001-1V4z"/></svg>
                    Plugins <span class="badge <?= count($updateStatusPlugins) > 0 ? 'badge-success' : 'badge-secondary' ?>"><?= count($updateStatusPlugins) ?></span>
                </a>
                <a href="<?= $baseUrl ?>&sub=themes" class="px-6 py-4 text-sm font-medium border-b-2 <?= $updateSubTab === 'themes' ? 'border-primary text-primary' : 'border-transparent text-muted-foreground' ?> flex items-center gap-2">
                    <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01"/></svg>
                    Themes <span class="badge <?= count($updateStatusThemes) > 0 ? 'badge-warning' : 'badge-secondary' ?>"><?= count($updateStatusThemes) ?></span>
                </a>
            </nav>
        </div>
        <div class="p-6">
            <?php if ($updateSubTab === 'core'): ?>
                <?php if ($coreNeedsUpdate && $updateStatus): ?>
                <p class="text-sm mb-3">WordPress core: <span class="font-mono"><?= sanitize($updateStatus['core_current'] ?? '—') ?></span> → <span class="font-mono"><?= sanitize($updateStatus['core_latest'] ?? '—') ?></span></p>
                <button type="button" class="btn-primary px-3 py-1.5 text-sm font-medium rounded-md remote-update-btn" data-action="core" data-website-id="<?= (int)$websiteId ?>">Update WordPress</button>
                <?php else: ?>
                <p class="text-muted-foreground text-sm">WordPress core is up to date<?= $updateStatus && !empty($updateStatus['core_current']) ? ' (' . sanitize($updateStatus['core_current']) . ')' : '' ?>.</p>
                <?php endif; ?>
            <?php elseif ($updateSubTab === 'plugins'): ?>
                <?php if (empty($updateStatusPlugins)): ?>
                <p class="text-muted-foreground text-sm">All plugins are up to date.</p>
                <?php else: ?>
                <div class="space-y-2 mb-4">
                    <?php foreach ($updateStatusPlugins as $p): $pluginPath = $p['plugin'] ?? ''; $canUpdate = $pluginPath !== ''; ?>
                    <label class="flex items-center gap-2 text-sm <?= $canUpdate ? '' : 'opacity-75' ?>">
                        <?php if ($canUpdate): ?>
                        <input type="checkbox" class="remote-update-plugin-cb rounded border-border" value="<?= sanitize($pluginPath) ?>">
                        <?php endif; ?>
                        <span><?= sanitize($p['name'] ?? 'Plugin') ?> — <?= sanitize($p['current'] ?? '') ?> → <?= sanitize($p['latest'] ?? '') ?></span>
                    </label>
                    <?php endforeach; ?>
                </div>
                <div class="flex gap-2">
                    <button type="button" class="btn-secondary px-3 py-1.5 text-sm font-medium rounded-md remote-update-selected-btn" data-action="plugins" data-website-id="<?= (int)$websiteId ?>">Update selected</button>
                    <button type="button" class="btn-primary px-3 py-1.5 text-sm font-medium rounded-md remote-update-btn" data-action="plugins" data-website-id="<?= (int)$websiteId ?>" data-plugins="all">Update all plugins</button>
                </div>
                <?php endif; ?>
            <?php else: ?>
                <?php if (empty($updateStatusThemes)): ?>
                <p class="text-muted-foreground text-sm">All themes are up to date.</p>
                <?php else: ?>
                <div class="space-y-2 mb-4">
                    <?php foreach ($updateStatusThemes as $t): $themeSlug = $t['slug'] ?? ''; $canUpdate = $themeSlug !== ''; ?>
                    <label class="flex items-center gap-2 text-sm <?= $canUpdate ? '' : 'opacity-75' ?>">
                        <?php if ($canUpdate): ?>
                        <input type="checkbox" class="remote-update-theme-cb rounded border-border" value="<?= sanitize($themeSlug) ?>">
                        <?php endif; ?>
                        <span><?= sanitize($t['name'] ?? 'Theme') ?> — <?= sanitize($t['current'] ?? '') ?> → <?= sanitize($t['latest'] ?? '') ?></span>
                    </label>
                    <?php endforeach; ?>
                </div>
                <div class="flex gap-2">
                    <button type="button" class="btn-secondary px-3 py-1.5 text-sm font-medium rounded-md remote-update-selected-btn" data-action="themes" data-website-id="<?= (int)$websiteId ?>">Update selected</button>
                    <button type="button" class="btn-primary px-3 py-1.5 text-sm font-medium rounded-md remote-update-btn" data-action="themes" data-website-id="<?= (int)$websiteId ?>" data-themes="all">Update all themes</button>
                </div>
                <?php endif; ?>
            <?php endif; ?>
        </div>
    </div>

    <!-- Uptime and Backups side by side -->
    <div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
        <!-- Uptime card (7 days) -->
        <div class="card rounded-lg p-6">
            <h3 class="text-lg font-semibold mb-4">Uptime (last 7 days)</h3>
            <?php
            $hasUptime7 = !empty($uptimeChartData7['labels']) && (array_filter($uptimeChartData7['uptime_pct']) !== [] || array_filter($uptimeChartData7['response_time_ms']) !== []);
            ?>
            <?php if (!$hasUptime7): ?>
            <p class="text-muted-foreground text-sm">No uptime data yet. Run the uptime cron to collect data.</p>
            <?php else: ?>
            <div class="space-y-4">
                <div><p class="text-sm text-muted-foreground mb-2">Uptime %</p><div class="h-48"><canvas id="dashboard-uptime-chart-7" aria-label="Uptime 7d"></canvas></div></div>
                <div><p class="text-sm text-muted-foreground mb-2">Response time (ms)</p><div class="h-48"><canvas id="dashboard-response-chart-7" aria-label="Response 7d"></canvas></div></div>
            </div>
            <script>window.__uptimeChartData7 = <?= json_encode($uptimeChartData7) ?>;</script>
            <?php endif; ?>
        </div>

        <!-- Last 5 backups -->
        <div class="card rounded-lg p-6">
            <h3 class="text-lg font-semibold mb-4">Last 5 backups</h3>
            <?php if (empty($lastBackups)): ?>
            <p class="text-muted-foreground text-sm">No backups recorded yet.</p>
            <?php else: ?>
            <ul class="space-y-2 text-sm">
                <?php foreach ($lastBackups as $b): ?>
                <li class="flex items-center justify-between gap-2">
                    <span class="truncate"><?= sanitize($b['filename'] ?? $b['destination'] ?? 'Backup') ?></span>
                    <span class="text-muted-foreground flex-shrink-0"><?= !empty($b['modified']) ? formatDateTime($b['modified']) : '' ?></span>
                </li>
                <?php endforeach; ?>
            </ul>
            <p class="mt-3"><a href="<?= $baseUrl ?>&tab=backups" class="text-sm text-primary hover:underline">View all backups</a></p>
            <?php endif; ?>
        </div>
    </div>

    <!-- Analytics placeholder -->
    <div class="card rounded-lg p-6">
        <h3 class="text-lg font-semibold mb-4">Analytics</h3>
        <p class="text-muted-foreground text-sm">Analytics will be available in a future update.</p>
    </div>
</div>

<?php if ($hasUptime7): ?>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
<script>
(function(){
    var d = window.__uptimeChartData7;
    if (!d || !d.labels || !d.labels.length) return;
    var opts = { responsive: true, maintainAspectRatio: false };
    new Chart(document.getElementById('dashboard-uptime-chart-7'), { type: 'bar', data: { labels: d.labels, datasets: [{ label: 'Uptime %', data: d.uptime_pct, backgroundColor: 'rgba(16, 185, 129, 0.6)', borderColor: 'rgb(16, 185, 129)', borderWidth: 1 }] }, options: Object.assign({}, opts, { scales: { y: { min: 0, max: 100 } } }) });
    new Chart(document.getElementById('dashboard-response-chart-7'), { type: 'line', data: { labels: d.labels, datasets: [{ label: 'Response (ms)', data: d.response_time_ms, borderColor: 'rgb(59, 130, 246)', backgroundColor: 'rgba(59, 130, 246, 0.1)', fill: true, tension: 0.2 }] }, options: Object.assign({}, opts, { scales: { y: { min: 0 } } }) });
})();
</script>
<?php endif; ?>

<?php elseif ($tab === 'backups'): ?>
<div class="card rounded-lg overflow-hidden">
    <h3 class="text-lg font-semibold p-6 border-b">Backups</h3>
    <?php if (empty($allBackups)): ?>
    <div class="p-6 text-muted-foreground text-sm">No backups recorded for this site.</div>
    <?php else: ?>
    <div class="overflow-x-auto">
        <table class="w-full text-sm">
            <thead class="bg-muted/50"><tr><th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase">Destination</th><th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase">Filename</th><th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase">Status</th><th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase">Modified</th></tr></thead>
            <tbody class="divide-y">
            <?php foreach ($allBackups as $b): ?>
            <tr><td class="px-6 py-3"><?= sanitize($b['destination'] ?? '') ?></td><td class="px-6 py-3"><?= sanitize($b['filename'] ?? '') ?></td><td class="px-6 py-3"><?= sanitize($b['status'] ?? '') ?></td><td class="px-6 py-3"><?= !empty($b['modified']) ? formatDateTime($b['modified']) : '' ?></td></tr>
            <?php endforeach; ?>
            </tbody>
        </table>
    </div>
    <?php endif; ?>
</div>

<?php elseif ($tab === 'themes'): ?>
<?php
$themeSlugByName = [];
foreach ($updateStatusThemes as $t) {
    $n = $t['name'] ?? '';
    if ($n !== '' && isset($t['slug'])) $themeSlugByName[$n] = $t['slug'];
}
$themeSlugsWithUpdate = array_column(array_filter($updateStatusThemes, function ($t) { return !empty($t['slug']); }), 'slug');
$activeThemes = [];
$inactiveThemes = [];
foreach ($snapshotThemes as $t) {
    $active = is_array($t) && !empty($t['active']);
    if ($active) $activeThemes[] = $t; else $inactiveThemes[] = $t;
}
$themesSub = isset($_GET['themes_list']) && $_GET['themes_list'] === 'inactive' ? 'inactive' : 'active';
?>
<div class="card rounded-lg overflow-hidden">
    <h3 class="text-lg font-semibold p-6 border-b">Themes</h3>
    <?php if (empty($snapshotThemes)): ?>
    <div class="p-6 text-muted-foreground text-sm">No theme data yet. Site snapshot is sent by the Stapolin Activity plugin.</div>
    <?php else: ?>
    <div class="border-b px-6 flex gap-4">
        <a href="<?= $baseUrl ?>&tab=themes&themes_list=active" class="py-3 text-sm font-medium border-b-2 <?= $themesSub === 'active' ? 'border-primary text-primary' : 'border-transparent text-muted-foreground hover:text-foreground' ?>">Active <span class="badge badge-secondary ml-1"><?= count($activeThemes) ?></span></a>
        <a href="<?= $baseUrl ?>&tab=themes&themes_list=inactive" class="py-3 text-sm font-medium border-b-2 <?= $themesSub === 'inactive' ? 'border-primary text-primary' : 'border-transparent text-muted-foreground hover:text-foreground' ?>">Inactive <span class="badge badge-secondary ml-1"><?= count($inactiveThemes) ?></span></a>
    </div>
    <?php if ($themesSub === 'active'): ?>
    <div class="overflow-x-auto">
        <table class="w-full text-sm">
            <thead class="bg-muted/50"><tr><th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase">Name</th><th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase">Version</th><th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase">Update</th><th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase">Actions</th></tr></thead>
            <tbody class="divide-y">
            <?php foreach ($activeThemes as $t):
                $name = is_array($t) ? ($t['name'] ?? $t['Name'] ?? '') : '';
                $ver = is_array($t) ? ($t['version'] ?? $t['Version'] ?? '') : '';
                $slug = $themeSlugByName[$name] ?? ($t['slug'] ?? '');
                $needs = $slug !== '' && in_array($slug, $themeSlugsWithUpdate);
            ?>
            <tr>
                <td class="px-6 py-3"><?= sanitize($name) ?></td>
                <td class="px-6 py-3 font-mono"><?= sanitize($ver) ?></td>
                <td class="px-6 py-3"><?= $needs ? '<span class="badge badge-warning">Available</span>' : '—' ?></td>
                <td class="px-6 py-3"><?= $needs ? '<button type="button" class="remote-update-one-btn btn-primary px-2 py-1 text-xs font-medium rounded-md" data-action="themes" data-website-id="' . (int)$websiteId . '" data-themes="' . htmlspecialchars($slug, ENT_QUOTES, 'UTF-8') . '">Update</button>' : '<span class="text-muted-foreground text-xs">Currently active</span>' ?></td>
            </tr>
            <?php endforeach; ?>
            </tbody>
        </table>
    </div>
    <?php else: ?>
    <div class="overflow-x-auto">
        <table class="w-full text-sm">
            <thead class="bg-muted/50"><tr><th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase">Name</th><th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase">Version</th><th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase">Update</th><th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase">Actions</th></tr></thead>
            <tbody class="divide-y">
            <?php foreach ($inactiveThemes as $t):
                $name = is_array($t) ? ($t['name'] ?? $t['Name'] ?? '') : '';
                $ver = is_array($t) ? ($t['version'] ?? $t['Version'] ?? '') : '';
                $slug = $themeSlugByName[$name] ?? ($t['slug'] ?? '');
                $needs = $slug !== '' && in_array($slug, $themeSlugsWithUpdate);
                $canActivate = $slug !== '';
            ?>
            <tr>
                <td class="px-6 py-3"><?= sanitize($name) ?></td>
                <td class="px-6 py-3 font-mono"><?= sanitize($ver) ?></td>
                <td class="px-6 py-3"><?= $needs ? '<span class="badge badge-warning">Available</span>' : '—' ?></td>
                <td class="px-6 py-3 flex gap-2">
                    <?php if ($canActivate): ?><button type="button" class="plugin-theme-action-btn btn-primary px-2 py-1 text-xs font-medium rounded-md" data-type="theme" data-action="activate" data-website-id="<?= (int)$websiteId ?>" data-theme="<?= htmlspecialchars($slug, ENT_QUOTES, 'UTF-8') ?>">Activate</button><?php endif; ?>
                    <?php if ($canActivate): ?><button type="button" class="plugin-theme-action-btn border border-border px-2 py-1 text-xs font-medium rounded-md text-red-600 hover:bg-red-50" data-type="theme" data-action="delete" data-website-id="<?= (int)$websiteId ?>" data-theme="<?= htmlspecialchars($slug, ENT_QUOTES, 'UTF-8') ?>">Delete</button><?php endif; ?>
                    <?php if ($needs): ?><button type="button" class="remote-update-one-btn btn-primary px-2 py-1 text-xs font-medium rounded-md" data-action="themes" data-website-id="<?= (int)$websiteId ?>" data-themes="<?= htmlspecialchars($slug, ENT_QUOTES, 'UTF-8') ?>">Update</button><?php endif; ?>
                </td>
            </tr>
            <?php endforeach; ?>
            </tbody>
        </table>
    </div>
    <?php endif; ?>
    <?php endif; ?>
</div>

<?php elseif ($tab === 'plugins'): ?>
<?php
$pluginPathByName = [];
foreach ($updateStatusPlugins as $p) {
    $n = $p['name'] ?? '';
    if ($n !== '' && isset($p['plugin'])) $pluginPathByName[$n] = $p['plugin'];
}
$pluginPathsWithUpdate = array_column(array_filter($updateStatusPlugins, function ($p) { return !empty($p['plugin']); }), 'plugin');
$activePlugins = [];
$inactivePlugins = [];
foreach ($snapshotPlugins as $p) {
    $active = is_array($p) && !empty($p['active']);
    if ($active) $activePlugins[] = $p; else $inactivePlugins[] = $p;
}
$pluginsSub = isset($_GET['plugins_list']) && $_GET['plugins_list'] === 'inactive' ? 'inactive' : 'active';
?>
<div class="card rounded-lg overflow-hidden">
    <h3 class="text-lg font-semibold p-6 border-b">Plugins</h3>
    <?php if (empty($snapshotPlugins)): ?>
    <div class="p-6 text-muted-foreground text-sm">No plugin data yet. Site snapshot is sent by the Stapolin Activity plugin.</div>
    <?php else: ?>
    <div class="border-b px-6 flex gap-4">
        <a href="<?= $baseUrl ?>&tab=plugins&plugins_list=active" class="py-3 text-sm font-medium border-b-2 <?= $pluginsSub === 'active' ? 'border-primary text-primary' : 'border-transparent text-muted-foreground hover:text-foreground' ?>">Active <span class="badge badge-secondary ml-1"><?= count($activePlugins) ?></span></a>
        <a href="<?= $baseUrl ?>&tab=plugins&plugins_list=inactive" class="py-3 text-sm font-medium border-b-2 <?= $pluginsSub === 'inactive' ? 'border-primary text-primary' : 'border-transparent text-muted-foreground hover:text-foreground' ?>">Inactive <span class="badge badge-secondary ml-1"><?= count($inactivePlugins) ?></span></a>
    </div>
    <?php if ($pluginsSub === 'active'): ?>
    <div class="overflow-x-auto">
        <table class="w-full text-sm">
            <thead class="bg-muted/50"><tr><th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase">Name</th><th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase">Version</th><th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase">Update</th><th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase">Actions</th></tr></thead>
            <tbody class="divide-y">
            <?php foreach ($activePlugins as $p):
                $name = is_array($p) ? ($p['name'] ?? $p['Name'] ?? '') : '';
                $ver = is_array($p) ? ($p['version'] ?? $p['Version'] ?? '') : '';
                $pluginPath = $pluginPathByName[$name] ?? ($p['plugin'] ?? '');
                $needs = $pluginPath !== '' && in_array($pluginPath, $pluginPathsWithUpdate);
                $canDeactivate = $pluginPath !== '';
            ?>
            <tr>
                <td class="px-6 py-3"><?= sanitize($name) ?></td>
                <td class="px-6 py-3 font-mono"><?= sanitize($ver) ?></td>
                <td class="px-6 py-3"><?= $needs ? '<span class="badge badge-success">Available</span>' : '—' ?></td>
                <td class="px-6 py-3 flex gap-2">
                    <?php if ($canDeactivate): ?><button type="button" class="plugin-theme-action-btn border border-border px-2 py-1 text-xs font-medium rounded-md hover:bg-muted" data-type="plugin" data-action="deactivate" data-website-id="<?= (int)$websiteId ?>" data-plugin="<?= htmlspecialchars($pluginPath, ENT_QUOTES, 'UTF-8') ?>">Deactivate</button><?php endif; ?>
                    <?php if ($needs): ?><button type="button" class="remote-update-one-btn btn-primary px-2 py-1 text-xs font-medium rounded-md" data-action="plugins" data-website-id="<?= (int)$websiteId ?>" data-plugins="<?= htmlspecialchars($pluginPath, ENT_QUOTES, 'UTF-8') ?>">Update</button><?php endif; ?>
                </td>
            </tr>
            <?php endforeach; ?>
            </tbody>
        </table>
    </div>
    <?php else: ?>
    <div class="overflow-x-auto">
        <table class="w-full text-sm">
            <thead class="bg-muted/50"><tr><th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase">Name</th><th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase">Version</th><th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase">Update</th><th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase">Actions</th></tr></thead>
            <tbody class="divide-y">
            <?php foreach ($inactivePlugins as $p):
                $name = is_array($p) ? ($p['name'] ?? $p['Name'] ?? '') : '';
                $ver = is_array($p) ? ($p['version'] ?? $p['Version'] ?? '') : '';
                $pluginPath = $pluginPathByName[$name] ?? ($p['plugin'] ?? '');
                $needs = $pluginPath !== '' && in_array($pluginPath, $pluginPathsWithUpdate);
                $canActivate = $pluginPath !== '';
            ?>
            <tr>
                <td class="px-6 py-3"><?= sanitize($name) ?></td>
                <td class="px-6 py-3 font-mono"><?= sanitize($ver) ?></td>
                <td class="px-6 py-3"><?= $needs ? '<span class="badge badge-success">Available</span>' : '—' ?></td>
                <td class="px-6 py-3 flex gap-2">
                    <?php if ($canActivate): ?><button type="button" class="plugin-theme-action-btn btn-primary px-2 py-1 text-xs font-medium rounded-md" data-type="plugin" data-action="activate" data-website-id="<?= (int)$websiteId ?>" data-plugin="<?= htmlspecialchars($pluginPath, ENT_QUOTES, 'UTF-8') ?>">Activate</button><?php endif; ?>
                    <?php if ($canActivate): ?><button type="button" class="plugin-theme-action-btn border border-border px-2 py-1 text-xs font-medium rounded-md text-red-600 hover:bg-red-50" data-type="plugin" data-action="delete" data-website-id="<?= (int)$websiteId ?>" data-plugin="<?= htmlspecialchars($pluginPath, ENT_QUOTES, 'UTF-8') ?>">Delete</button><?php endif; ?>
                    <?php if ($needs): ?><button type="button" class="remote-update-one-btn btn-primary px-2 py-1 text-xs font-medium rounded-md" data-action="plugins" data-website-id="<?= (int)$websiteId ?>" data-plugins="<?= htmlspecialchars($pluginPath, ENT_QUOTES, 'UTF-8') ?>">Update</button><?php endif; ?>
                </td>
            </tr>
            <?php endforeach; ?>
            </tbody>
        </table>
    </div>
    <?php endif; ?>
    <?php endif; ?>
</div>

<?php elseif ($tab === 'users'): ?>
<div class="card rounded-lg overflow-hidden">
    <h3 class="text-lg font-semibold p-6 border-b">Users</h3>
    <?php if (empty($snapshotUsers)): ?>
    <div class="p-6 text-muted-foreground text-sm">No user data yet. Site snapshot is sent by the Stapolin Activity plugin.</div>
    <?php else: ?>
    <div class="overflow-x-auto">
        <table class="w-full text-sm">
            <thead class="bg-muted/50"><tr><th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase">Username</th><th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase">Email</th><th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase">Role</th><th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase">Registered</th><th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase">Last signed in</th></tr></thead>
            <tbody class="divide-y">
            <?php foreach ($snapshotUsers as $u): 
                $username = is_array($u) ? ($u['username'] ?? $u['login'] ?? $u['user_login'] ?? '') : '';
                $email = is_array($u) ? ($u['email'] ?? $u['user_email'] ?? '') : '';
                $role = is_array($u) ? ($u['role'] ?? $u['roles'] ?? '') : '';
                if (is_array($role)) $role = implode(', ', $role);
                $registered = is_array($u) ? ($u['registered'] ?? $u['registered_date'] ?? '') : '';
                $lastLogin = is_array($u) ? ($u['last_login'] ?? '') : '';
            ?>
            <tr><td class="px-6 py-3"><?= sanitize($username) ?></td><td class="px-6 py-3"><?= sanitize($email) ?></td><td class="px-6 py-3"><?= sanitize($role) ?></td><td class="px-6 py-3"><?= $registered ? formatDateTime($registered) : '—' ?></td><td class="px-6 py-3"><?= $lastLogin ? formatDateTime($lastLogin) : '—' ?></td></tr>
            <?php endforeach; ?>
            </tbody>
        </table>
    </div>
    <?php endif; ?>
</div>

<?php elseif ($tab === 'uptime'): ?>
<div class="card rounded-lg p-6">
    <h3 class="text-lg font-semibold mb-4">Uptime monitoring (last 30 days)</h3>
    <?php
    $hasUptime30 = !empty($uptimeChartData30['labels']) && (array_filter($uptimeChartData30['uptime_pct']) !== [] || array_filter($uptimeChartData30['response_time_ms']) !== []);
    ?>
    <?php if (!$hasUptime30): ?>
    <p class="text-muted-foreground text-sm">No uptime data yet. Run the uptime cron to collect data.</p>
    <?php else: ?>
    <div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
        <div><p class="text-sm text-muted-foreground mb-2">Uptime %</p><div class="h-64"><canvas id="uptime-chart-30" aria-label="Uptime 30d"></canvas></div></div>
        <div><p class="text-sm text-muted-foreground mb-2">Response time (ms)</p><div class="h-64"><canvas id="response-chart-30" aria-label="Response 30d"></canvas></div></div>
    </div>
    <script>window.__uptimeChartData30 = <?= json_encode($uptimeChartData30) ?>;</script>
    <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
    <script>
    (function(){
        var d = window.__uptimeChartData30;
        if (!d || !d.labels || !d.labels.length) return;
        var opts = { responsive: true, maintainAspectRatio: false };
        new Chart(document.getElementById('uptime-chart-30'), { type: 'bar', data: { labels: d.labels, datasets: [{ label: 'Uptime %', data: d.uptime_pct, backgroundColor: 'rgba(16, 185, 129, 0.6)', borderColor: 'rgb(16, 185, 129)', borderWidth: 1 }] }, options: Object.assign({}, opts, { scales: { y: { min: 0, max: 100 } } }) });
        new Chart(document.getElementById('response-chart-30'), { type: 'line', data: { labels: d.labels, datasets: [{ label: 'Response (ms)', data: d.response_time_ms, borderColor: 'rgb(59, 130, 246)', backgroundColor: 'rgba(59, 130, 246, 0.1)', fill: true, tension: 0.2 }] }, options: Object.assign({}, opts, { scales: { y: { min: 0 } } }) });
    })();
    </script>
    <?php endif; ?>
</div>
<?php elseif ($tab === 'history'): ?>
<?php
$historyBaseUrl = $baseUrl . '&tab=history';
$historyFilterType = isset($_GET['filter_type']) ? trim($_GET['filter_type']) : '';
$historyFilterUser = isset($_GET['filter_user']) ? trim($_GET['filter_user']) : '';
?>
<div class="card rounded-lg overflow-hidden">
    <h3 class="text-lg font-semibold p-6 border-b">Update history</h3>
    <div class="p-6 border-b bg-muted/30 flex flex-wrap items-center gap-4">
        <form method="get" action="website-dashboard.php" class="flex flex-wrap items-center gap-4">
            <input type="hidden" name="id" value="<?= (int)$websiteId ?>">
            <input type="hidden" name="tab" value="history">
            <label class="flex items-center gap-2 text-sm">
                <span class="text-muted-foreground">Type</span>
                <select name="filter_type" class="rounded-md border border-border bg-background px-3 py-1.5 text-sm" onchange="this.form.submit()">
                    <option value=""<?= $historyFilterType === '' ? ' selected' : '' ?>>All</option>
                    <option value="Core"<?= $historyFilterType === 'Core' ? ' selected' : '' ?>>Core</option>
                    <option value="Plugin"<?= $historyFilterType === 'Plugin' ? ' selected' : '' ?>>Plugin</option>
                    <option value="Theme"<?= $historyFilterType === 'Theme' ? ' selected' : '' ?>>Theme</option>
                </select>
            </label>
            <label class="flex items-center gap-2 text-sm">
                <span class="text-muted-foreground">User</span>
                <select name="filter_user" class="rounded-md border border-border bg-background px-3 py-1.5 text-sm" onchange="this.form.submit()">
                    <option value=""<?= $historyFilterUser === '' ? ' selected' : '' ?>>All</option>
                    <?php foreach ($updateHistoryUsers as $u): $un = $u['username'] ?? ''; ?>
                    <option value="<?= sanitize($un) ?>"<?= $historyFilterUser === $un ? ' selected' : '' ?>><?= sanitize($un) ?></option>
                    <?php endforeach; ?>
                </select>
            </label>
            <?php if ($historyFilterType !== '' || $historyFilterUser !== ''): ?>
            <a href="<?= $historyBaseUrl ?>" class="text-sm text-primary hover:underline">Clear filters</a>
            <?php endif; ?>
        </form>
    </div>
    <?php if (empty($updateHistory)): ?>
    <div class="p-6 text-muted-foreground text-sm">No update history recorded for this site. History is sent by the Stapolin Activity plugin when updates are run.</div>
    <?php else: ?>
    <div class="overflow-x-auto">
        <table class="w-full text-sm">
            <thead class="bg-muted/50">
                <tr>
                    <th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase">Name / Description</th>
                    <th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase">Type</th>
                    <th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase">Action</th>
                    <th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase">Date</th>
                    <th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase">User</th>
                </tr>
            </thead>
            <tbody class="divide-y">
            <?php foreach ($updateHistory as $row):
                $actionLabel = trim($row['action'] ?? '');
                if ($actionLabel === '') $actionLabel = '—';
                else $actionLabel = ucfirst(strtolower($actionLabel));
            ?>
                <tr>
                    <td class="px-6 py-3"><?= sanitize($row['name'] ?? '') ?></td>
                    <td class="px-6 py-3"><span class="badge <?= ($row['type'] ?? '') === 'Core' ? 'badge-info' : (($row['type'] ?? '') === 'Plugin' ? 'badge-success' : 'badge-warning') ?>"><?= sanitize($row['type'] ?? '') ?></span></td>
                    <td class="px-6 py-3"><?= sanitize($actionLabel) ?></td>
                    <td class="px-6 py-3 text-muted-foreground"><?= !empty($row['hist_time']) ? formatDateTime($row['hist_time']) : '—' ?></td>
                    <td class="px-6 py-3"><?= sanitize($row['username'] ?? '—') ?></td>
                </tr>
            <?php endforeach; ?>
            </tbody>
        </table>
    </div>
    <p class="p-4 text-xs text-muted-foreground border-t">Showing up to 500 most recent updates.</p>
    <?php endif; ?>
</div>

<?php elseif ($tab === 'extra'): ?>
<div class="card rounded-lg overflow-hidden">
    <h3 class="text-lg font-semibold p-6 border-b">Extra work</h3>
    <div class="p-6 border-b bg-muted/30">
        <form id="extra-work-form" class="grid grid-cols-1 md:grid-cols-12 gap-4 items-end">
            <input type="hidden" name="website_id" value="<?= (int)$websiteId ?>">
            <div class="md:col-span-2">
                <label for="extra_work_date" class="block text-sm font-medium text-muted-foreground mb-1">Date</label>
                <input type="date" id="extra_work_date" name="work_date" required class="w-full px-3 py-2 rounded-md text-sm border border-border bg-background" value="<?= date('Y-m-d') ?>">
            </div>
            <div class="md:col-span-5">
                <label for="extra_work_description" class="block text-sm font-medium text-muted-foreground mb-1">Description</label>
                <input type="text" id="extra_work_description" name="description" required class="w-full px-3 py-2 rounded-md text-sm border border-border bg-background" placeholder="Describe the work performed...">
            </div>
            <div class="md:col-span-2">
                <label for="extra_work_time" class="block text-sm font-medium text-muted-foreground mb-1">Time (minutes)</label>
                <input type="number" id="extra_work_time" name="time_spent" required min="1" class="w-full px-3 py-2 rounded-md text-sm border border-border bg-background" placeholder="e.g. 30">
            </div>
            <div class="md:col-span-3">
                <button type="submit" id="extra-work-submit" class="btn-primary px-4 py-2 rounded-md text-sm font-medium w-full md:w-auto">Add entry</button>
            </div>
        </form>
    </div>
    <?php if (empty($extraWork)): ?>
    <div class="p-6 text-muted-foreground text-sm">No extra work recorded for this site yet. Use the form above to add an entry.</div>
    <?php else: ?>
    <div class="overflow-x-auto">
        <table class="w-full text-sm">
            <thead class="bg-muted/50">
                <tr>
                    <th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase">Description</th>
                    <th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase">Date</th>
                    <th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase">Time</th>
                </tr>
            </thead>
            <tbody class="divide-y" id="extra-work-tbody">
            <?php foreach ($extraWork as $work): ?>
                <tr>
                    <td class="px-6 py-3"><?= sanitize($work['description'] ?? '') ?></td>
                    <td class="px-6 py-3 text-muted-foreground whitespace-nowrap"><?= !empty($work['work_date']) ? formatDate($work['work_date']) : '—' ?></td>
                    <td class="px-6 py-3 font-mono whitespace-nowrap"><?= isset($work['time_spent']) ? formatDuration((int)$work['time_spent']) : '—' ?></td>
                </tr>
            <?php endforeach; ?>
            </tbody>
        </table>
    </div>
    <?php endif; ?>
</div>
<?php endif; ?>

<script>
(function() {
    function doRemoteUpdate(websiteId, action, plugins, themes, btn) {
        var origText = btn ? btn.textContent : '';
        if (btn) { btn.disabled = true; btn.textContent = 'Updating…'; }
        var payload = { website_id: websiteId, action: action };
        if (plugins) payload.plugins = Array.isArray(plugins) ? plugins : [plugins];
        if (themes) payload.themes = Array.isArray(themes) ? themes : [themes];
        fetch('api/remote-update.php', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(payload)
        })
        .then(function(r) { return r.json(); })
        .then(function(data) {
            if (btn) { btn.disabled = false; btn.textContent = origText; }
            if (data.success) {
                if (typeof showToast === 'function') showToast(data.message || 'Update completed');
                else alert(data.message || 'Update completed');
                // Refresh update status from site so lists no longer show the updated item as needing update
                fetch('api/refresh-update-status.php?website_id=' + websiteId)
                    .then(function(r) { return r.json(); })
                    .then(function() { setTimeout(function() { window.location.reload(); }, 800); })
                    .catch(function() { setTimeout(function() { window.location.reload(); }, 1500); });
            } else {
                if (typeof showToast === 'function') showToast(data.error || 'Update failed', 'error');
                else alert(data.error || 'Update failed');
            }
        })
        .catch(function() {
            if (btn) { btn.disabled = false; btn.textContent = origText; }
            if (typeof showToast === 'function') showToast('Request failed', 'error');
            else alert('Request failed');
        });
    }
    document.querySelectorAll('.remote-update-btn').forEach(function(btn) {
        btn.addEventListener('click', function() {
            var action = btn.getAttribute('data-action');
            var websiteId = btn.getAttribute('data-website-id');
            var plugins = btn.getAttribute('data-plugins');
            var themes = btn.getAttribute('data-themes');
            doRemoteUpdate(
                parseInt(websiteId, 10),
                action,
                plugins === 'all' ? ['all'] : null,
                themes === 'all' ? ['all'] : null,
                btn
            );
        });
    });
    document.querySelectorAll('.remote-update-selected-btn').forEach(function(btn) {
        btn.addEventListener('click', function() {
            var action = btn.getAttribute('data-action');
            var websiteId = parseInt(btn.getAttribute('data-website-id'), 10);
            var list = action === 'plugins' 
                ? document.querySelectorAll('.remote-update-plugin-cb:checked')
                : document.querySelectorAll('.remote-update-theme-cb:checked');
            var values = [].map.call(list, function(cb) { return cb.value; });
            if (values.length === 0) {
                if (typeof showToast === 'function') showToast('Select at least one item', 'error');
                else alert('Select at least one item');
                return;
            }
            doRemoteUpdate(websiteId, action, action === 'plugins' ? values : null, action === 'themes' ? values : null, btn);
        });
    });
    document.querySelectorAll('.remote-update-one-btn').forEach(function(btn) {
        btn.addEventListener('click', function() {
            var action = btn.getAttribute('data-action');
            var websiteId = parseInt(btn.getAttribute('data-website-id'), 10);
            var plugins = btn.getAttribute('data-plugins');
            var themes = btn.getAttribute('data-themes');
            doRemoteUpdate(websiteId, action, action === 'plugins' ? [plugins] : null, action === 'themes' ? [themes] : null, btn);
        });
    });

    function doPluginThemeAction(websiteId, type, action, plugin, theme, btn) {
        var origText = btn ? btn.textContent : '';
        if (btn) { btn.disabled = true; btn.textContent = '…'; }
        var payload = { website_id: websiteId, type: type, action: action };
        if (type === 'plugin') payload.plugin = plugin; else payload.theme = theme;
        fetch('api/remote-plugin-theme-action.php', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(payload)
        })
        .then(function(r) { return r.json(); })
        .then(function(data) {
            if (btn) { btn.disabled = false; btn.textContent = origText; }
            if (data.success) {
                if (typeof showToast === 'function') showToast(data.message || 'Done');
                else alert(data.message || 'Done');
                fetch('api/refresh-snapshot.php?website_id=' + websiteId)
                    .then(function(r) { return r.json(); })
                    .then(function() { setTimeout(function() { window.location.reload(); }, 800); })
                    .catch(function() { setTimeout(function() { window.location.reload(); }, 1500); });
            } else {
                if (typeof showToast === 'function') showToast(data.error || 'Action failed', 'error');
                else alert(data.error || 'Action failed');
            }
        })
        .catch(function() {
            if (btn) { btn.disabled = false; btn.textContent = origText; }
            if (typeof showToast === 'function') showToast('Request failed', 'error');
            else alert('Request failed');
        });
    }
    document.querySelectorAll('.plugin-theme-action-btn').forEach(function(btn) {
        btn.addEventListener('click', function() {
            var websiteId = parseInt(btn.getAttribute('data-website-id'), 10);
            var type = btn.getAttribute('data-type');
            var action = btn.getAttribute('data-action');
            var plugin = btn.getAttribute('data-plugin') || '';
            var theme = btn.getAttribute('data-theme') || '';
            doPluginThemeAction(websiteId, type, action, plugin, theme, btn);
        });
    });

    var extraWorkForm = document.getElementById('extra-work-form');
    if (extraWorkForm) {
        extraWorkForm.addEventListener('submit', function(e) {
            e.preventDefault();
            var btn = document.getElementById('extra-work-submit');
            var origText = btn ? btn.textContent : '';
            if (btn) { btn.disabled = true; btn.textContent = 'Saving…'; }
            var payload = {
                website_id: parseInt(extraWorkForm.querySelector('input[name="website_id"]').value, 10),
                work_date: extraWorkForm.querySelector('#extra_work_date').value,
                description: extraWorkForm.querySelector('#extra_work_description').value.trim(),
                time_spent: parseInt(extraWorkForm.querySelector('#extra_work_time').value, 10) || 0
            };
            if (!payload.description || payload.time_spent < 1) {
                if (btn) { btn.disabled = false; btn.textContent = origText; }
                if (typeof showToast === 'function') showToast('Please enter description and time (at least 1 minute).', 'error');
                else alert('Please enter description and time (at least 1 minute).');
                return;
            }
            fetch('api/extra-work.php', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify(payload)
            })
            .then(function(r) { return r.json(); })
            .then(function(data) {
                if (btn) { btn.disabled = false; btn.textContent = origText; }
                if (data.success) {
                    if (typeof showToast === 'function') showToast(data.message || 'Entry added');
                    else alert(data.message || 'Entry added');
                    window.location.reload();
                } else {
                    if (typeof showToast === 'function') showToast(data.message || 'Failed to add entry', 'error');
                    else alert(data.message || 'Failed to add entry');
                }
            })
            .catch(function() {
                if (btn) { btn.disabled = false; btn.textContent = origText; }
                if (typeof showToast === 'function') showToast('Request failed', 'error');
                else alert('Request failed');
            });
        });
    }
})();
</script>
<?php require_once __DIR__ . '/includes/footer.php'; ?>

Youez - 2016 - github.com/yon3zu
LinuXploit