403Webshell
Server IP : 213.255.246.8  /  Your IP : 216.73.217.138
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-detail.php
<?php
/**
 * Website Detail Page with Tabbed Updates View
 */

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

// Get website ID
$websiteId = isset($_GET['id']) ? (int)$_GET['id'] : 0;
if (!$websiteId) {
    redirect('websites.php');
}

// Get current tab
$currentTab = isset($_GET['tab']) ? $_GET['tab'] : 'plugins';
$validTabs = ['plugins', 'themes', 'core', 'extra', 'users', 'backups'];
if (!in_array($currentTab, $validTabs)) {
    $currentTab = 'plugins';
}

// Update status tab (for the "Updates available" card)
$updateStatusTab = isset($_GET['update_tab']) && in_array($_GET['update_tab'], ['core', 'plugins', 'themes'], true)
    ? $_GET['update_tab']
    : 'core';

// Snapshot tab (for the "Installed on this site" card)
$snapshotTab = isset($_GET['snapshot_tab']) && in_array($_GET['snapshot_tab'], ['core', 'plugins', 'themes', 'users'], true)
    ? $_GET['snapshot_tab']
    : 'core';

// Main tab: which card is visible (Updates Available | Installed on this site | Website Activity)
$mainTab = isset($_GET['main_tab']) && in_array($_GET['main_tab'], ['updates', 'installed', 'activity'], true)
    ? $_GET['main_tab']
    : 'updates';

// Get website details
try {
    $db = getDb();
    $website = $db->fetchOne(
        "SELECT w.*, c.client_name, c.client_id 
         FROM st_websites w 
         LEFT JOIN st_clients c ON w.client_id = c.client_id 
         WHERE w.website_id = ?",
        [$websiteId]
    );
    
    if (!$website) {
        redirect('websites.php');
    }
    
    // Get update counts
    $pluginCount = $db->fetchOne("SELECT COUNT(*) as count FROM st_activity WHERE website_id = ? AND object_type = 'Plugins'", [$websiteId])['count'];
    $themeCount = $db->fetchOne("SELECT COUNT(*) as count FROM st_activity WHERE website_id = ? AND object_type = 'Themes'", [$websiteId])['count'];
    $coreCount = $db->fetchOne("SELECT COUNT(*) as count FROM st_activity WHERE website_id = ? AND object_type = 'Core'", [$websiteId])['count'];
    $extraCount = $db->fetchOne("SELECT COUNT(*) as count FROM st_extra_work WHERE website_id = ?", [$websiteId])['count'];
    $userCount = $db->fetchOne("SELECT COUNT(*) as count FROM st_activity WHERE website_id = ? AND object_type = 'Users'", [$websiteId])['count'];
    $backupCount = $db->fetchOne("SELECT COUNT(*) as count FROM st_backups WHERE website_id = ?", [$websiteId])['count'];
    
    // Uptime chart data (last 7 days)
    $uptimeChartData = getUptimeChartData($websiteId, 7);
    
    // Update status (what needs updating - from plugin report)
    $updateStatus = null;
    $updateStatusPlugins = [];
    $updateStatusThemes = [];
    try {
        $updateStatus = $db->fetchOne("SELECT * FROM st_update_status WHERE website_id = ?", [$websiteId]);
        if ($updateStatus) {
            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 ($p) { return !empty($p['needs_update']); }) : [];
            }
        }
    } catch (Exception $e) {
        // Table may not exist
    }
    
    // Site snapshot (installed plugins, themes, core, users from plugin)
    $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) {
        // Table may not exist
    }
    
    // Get updates based on current tab
    switch ($currentTab) {
        case 'themes':
            $updates = $db->fetchAll(
                "SELECT activity_id, website_id, histid, action, object_name AS theme_name, object_subtype AS theme_version, user_id, username, user_caps, hist_ip, hist_time FROM st_activity WHERE website_id = ? AND object_type = 'Themes' ORDER BY hist_time DESC",
                [$websiteId]
            );
            break;
        case 'core':
            $updates = $db->fetchAll(
                "SELECT activity_id, website_id, histid, action, object_subtype AS version, user_id, username, user_caps, hist_ip, hist_time FROM st_activity WHERE website_id = ? AND object_type = 'Core' ORDER BY hist_time DESC",
                [$websiteId]
            );
            break;
        case 'extra':
            $updates = $db->fetchAll(
                "SELECT * FROM st_extra_work WHERE website_id = ? ORDER BY work_date DESC",
                [$websiteId]
            );
            break;
        case 'users':
            $updates = $db->fetchAll(
                "SELECT username AS performed_by_username, user_caps, object_name AS target_username, hist_time, action, action_location, hist_ip FROM st_activity WHERE website_id = ? AND object_type = 'Users' ORDER BY hist_time DESC",
                [$websiteId]
            );
            break;
        case 'backups':
            $updates = $db->fetchAll(
                "SELECT destination, filename, status, backup_trigger, modified FROM st_backups WHERE website_id = ? ORDER BY modified DESC",
                [$websiteId]
            );
            break;
        default: // plugins
            $updates = $db->fetchAll(
                "SELECT activity_id, website_id, histid, action, object_name AS plugin_name, object_subtype AS plugin_version, user_id, username, user_caps, hist_ip, hist_time FROM st_activity WHERE website_id = ? AND object_type = 'Plugins' ORDER BY hist_time DESC",
                [$websiteId]
            );
    }
    
    // Get clients for reassignment
    $clients = getClients();
    
} catch (Exception $e) {
    $website = null;
    $updates = [];
    $clients = [];
    $pluginCount = $themeCount = $coreCount = $extraCount = $userCount = $backupCount = 0;
    $uptimeChartData = ['labels' => [], 'uptime_pct' => [], 'response_time_ms' => []];
    $updateStatus = null;
    $updateStatusPlugins = [];
    $updateStatusThemes = [];
    $siteSnapshot = null;
    $snapshotPlugins = [];
    $snapshotThemes = [];
    $snapshotUsers = [];
}

$pageTitle = $website ? $website['website_url'] : 'Website Details';
require_once __DIR__ . '/includes/header.php';

// Handle success message
$successMessage = isset($_GET['success']) ? $_GET['success'] : null;
?>

<!-- Breadcrumb -->
<div class="flex items-center gap-2 text-sm text-muted-foreground mb-6">
    <a href="websites.php" class="hover:text-foreground" data-testid="link-back-websites">Websites</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_url']) ?></span>
</div>

<?php if ($successMessage): ?>
<div class="mb-6 p-4 rounded-md bg-green-500/10 border border-green-500/20 text-green-600 dark:text-green-400" data-testid="alert-success">
    <?= sanitize($successMessage) ?>
</div>
<?php endif; ?>

<?php
$newMaintenanceSecret = isset($_GET['new_maintenance_secret']) ? trim((string) $_GET['new_maintenance_secret']) : '';
if ($newMaintenanceSecret !== ''):
?>
<div class="mb-6 p-4 rounded-md bg-amber-500/10 border border-amber-500/20" data-testid="alert-maintenance-secret">
    <p class="text-sm font-medium text-amber-800 dark:text-amber-200 mb-2">Connection secret — copy this into the WordPress site</p>
    <p class="text-xs text-muted-foreground mb-2">Paste it in the site under <strong>Settings → Stapolin Maintenance</strong>. This value is not shown again.</p>
    <div class="flex items-center gap-2">
        <input type="text" 
               id="new_maintenance_secret_value" 
               value="<?= sanitize($newMaintenanceSecret) ?>" 
               readonly 
               class="flex-1 px-3 py-2 rounded-md text-sm font-mono bg-muted">
        <button type="button" 
                onclick="navigator.clipboard.writeText(document.getElementById('new_maintenance_secret_value').value).then(function(){ this.textContent='Copied!'; }.bind(this))"
                class="btn-secondary px-3 py-2 rounded-md text-sm font-medium">
            Copy
        </button>
    </div>
</div>
<script>if (window.history && window.history.replaceState) { var u = new URL(window.location.href); u.searchParams.delete('new_maintenance_secret'); window.history.replaceState({}, '', u.toString()); }</script>
<?php endif; ?>

<!-- Website Info Card -->
<div class="card rounded-lg p-6 mb-6">
    <div class="flex items-start justify-between gap-4">
        <div class="flex items-center gap-4">
            <div class="w-16 h-16 rounded-lg bg-blue-500/10 flex items-center justify-center flex-shrink-0">
                <svg class="w-8 h-8 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9"/>
                </svg>
            </div>
            <div>
                <h2 class="text-2xl font-semibold mb-1" data-testid="text-website-url"><?= sanitize($website['website_url']) ?></h2>
                <div class="flex items-center gap-4 text-sm">
                    <?php if (!empty($website['website_name'])): ?>
                    <span class="text-muted-foreground"><?= sanitize($website['website_name']) ?></span>
                    <?php endif; ?>
                    <?php if ($website['client_id']): ?>
                    <a href="client-detail.php?id=<?= $website['client_id'] ?>" 
                       class="flex items-center gap-1 text-primary hover:underline"
                       data-testid="link-client">
                        <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="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/>
                        </svg>
                        <?= sanitize($website['client_name']) ?>
                    </a>
                    <?php else: ?>
                    <span class="text-muted-foreground">No client assigned</span>
                    <?php endif; ?>
                </div>
            </div>
        </div>
        <div class="flex items-center gap-2">
            <a href="<?= sanitize($website['website_url']) ?>" 
               target="_blank" 
               class="btn-secondary px-3 py-2 rounded-md text-sm font-medium flex items-center gap-2"
               data-testid="button-visit-website">
                <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="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"/>
                </svg>
                Visit
            </a>
            <button onclick="document.getElementById('editWebsiteModal').classList.remove('hidden')" 
                    class="btn-secondary px-3 py-2 rounded-md text-sm font-medium flex items-center gap-2"
                    data-testid="button-edit-website">
                <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 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
                </svg>
                Edit
            </button>
        </div>
    </div>
</div>

<!-- Uptime & response time -->
<?php $hasUptimeData = !empty($uptimeChartData['labels']) && (array_filter($uptimeChartData['uptime_pct']) !== [] || array_filter($uptimeChartData['response_time_ms']) !== []); ?>
<div class="card rounded-lg p-6 mb-6">
    <h3 class="text-lg font-semibold mb-4 flex items-center gap-2">
        <svg class="w-5 h-5 text-emerald-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/>
        </svg>
        Uptime & response time
    </h3>
    <?php if (!$hasUptimeData): ?>
    <p class="text-muted-foreground text-sm">No uptime data yet for this site. Data is collected when the uptime cron runs (e.g. <code class="text-xs bg-muted px-1 rounded">api/cron-uptime.php</code>).</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 % (last 7 days)</p>
            <div class="h-64">
                <canvas id="uptimeChart" aria-label="Uptime percentage by day"></canvas>
            </div>
        </div>
        <div>
            <p class="text-sm text-muted-foreground mb-2">Response time (ms, last 7 days)</p>
            <div class="h-64">
                <canvas id="responseTimeChart" aria-label="Average response time by day"></canvas>
            </div>
        </div>
    </div>
    <script>
        window.__uptimeChartData = <?= json_encode($uptimeChartData) ?>;
    </script>
    <?php endif; ?>
</div>

<?php
$baseParams = 'id=' . $websiteId . '&tab=' . urlencode($currentTab) . '&update_tab=' . urlencode($updateStatusTab) . '&snapshot_tab=' . urlencode($snapshotTab);
$updatesParams = $baseParams . '&main_tab=updates';
$installedParams = $baseParams . '&main_tab=installed';
$activityParams = $baseParams . '&main_tab=activity';
?>
<!-- Single tabbed card: Updates Available | Installed on this site | Website Activity -->
<div class="card rounded-lg overflow-hidden mb-6">
    <div class="border-b">
        <nav class="flex -mb-px" aria-label="Main section tabs">
            <a href="?<?= $updatesParams ?>"
               class="px-6 py-4 text-sm font-medium border-b-2 <?= $mainTab === 'updates' ? 'border-primary text-primary' : 'border-transparent text-muted-foreground hover:text-foreground hover:border-border' ?>">
                Updates available
            </a>
            <a href="?<?= $installedParams ?>"
               class="px-6 py-4 text-sm font-medium border-b-2 <?= $mainTab === 'installed' ? 'border-primary text-primary' : 'border-transparent text-muted-foreground hover:text-foreground hover:border-border' ?>">
                Installed on this site
            </a>
            <a href="?<?= $activityParams ?>"
               class="px-6 py-4 text-sm font-medium border-b-2 <?= $mainTab === 'activity' ? 'border-primary text-primary' : 'border-transparent text-muted-foreground hover:text-foreground hover:border-border' ?>">
                Website Activity
            </a>
        </nav>
    </div>

    <?php if ($mainTab === 'updates'): ?>
    <!-- Updates available content -->
    <div class="px-6 py-4 border-b flex items-center justify-between gap-4 flex-wrap bg-muted/20">
        <?php if ($updateStatus && !empty($updateStatus['checked_at'])): ?>
        <span class="text-sm text-muted-foreground">Last checked: <?= formatDateTime($updateStatus['checked_at']) ?></span>
        <?php endif; ?>
    </div>
    <div class="border-b">
        <nav class="flex -mb-px" aria-label="Update status tabs">
            <a href="?<?= $updatesParams ?>&update_tab=core"
               class="px-6 py-4 text-sm font-medium border-b-2 <?= $updateStatusTab === 'core' ? 'border-primary text-primary' : 'border-transparent text-muted-foreground hover:text-foreground hover:border-border' ?>">
                <span class="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
                    <?php if ($updateStatus && !empty($updateStatus['core_needs_update'])): ?>
                    <span class="badge badge-info">1</span>
                    <?php else: ?>
                    <span class="badge badge-secondary">0</span>
                    <?php endif; ?>
                </span>
            </a>
            <a href="?<?= $updatesParams ?>&update_tab=plugins"
               class="px-6 py-4 text-sm font-medium border-b-2 <?= $updateStatusTab === 'plugins' ? 'border-primary text-primary' : 'border-transparent text-muted-foreground hover:text-foreground hover:border-border' ?>">
                <span class="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>
                </span>
            </a>
            <a href="?<?= $updatesParams ?>&update_tab=themes"
               class="px-6 py-4 text-sm font-medium border-b-2 <?= $updateStatusTab === 'themes' ? 'border-primary text-primary' : 'border-transparent text-muted-foreground hover:text-foreground hover:border-border' ?>">
                <span class="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>
                </span>
            </a>
        </nav>
    </div>
    <div class="overflow-x-auto">
        <?php if (!$updateStatus): ?>
        <div class="p-12 text-center">
            <p class="text-muted-foreground">No update status reported for this site yet. Data is sent by the Stapolin Activity plugin when it runs its update check.</p>
        </div>
        <?php elseif ($updateStatusTab === 'core'): ?>
        <div class="p-6">
            <?php if (!empty($updateStatus['core_needs_update'])): ?>
            <table class="w-full text-sm">
                <thead class="bg-muted/50">
                    <tr>
                        <th class="px-4 py-2 text-left text-xs font-medium text-muted-foreground uppercase">Current</th>
                        <th class="px-4 py-2 text-left text-xs font-medium text-muted-foreground uppercase">Latest</th>
                    </tr>
                </thead>
                <tbody>
                    <tr>
                        <td class="px-4 py-3 font-mono"><?= sanitize($updateStatus['core_current'] ?? '—') ?></td>
                        <td class="px-4 py-3 font-mono"><?= sanitize($updateStatus['core_latest'] ?? '—') ?></td>
                    </tr>
                </tbody>
            </table>
            <p class="mt-3 text-sm text-amber-600 dark:text-amber-400">WordPress core has an update available.</p>
            <?php else: ?>
            <p class="text-muted-foreground">WordPress core is up to date.</p>
            <?php if (!empty($updateStatus['core_current'])): ?>
            <p class="text-sm font-mono mt-1">Version: <?= sanitize($updateStatus['core_current']) ?></p>
            <?php endif; ?>
            <?php endif; ?>
        </div>
        <?php elseif ($updateStatusTab === 'plugins'): ?>
        <div class="p-6">
            <?php if (empty($updateStatusPlugins)): ?>
            <p class="text-muted-foreground">All plugins are up to date.</p>
            <?php else: ?>
            <table class="w-full text-sm">
                <thead class="bg-muted/50">
                    <tr>
                        <th class="px-4 py-2 text-left text-xs font-medium text-muted-foreground uppercase">Plugin</th>
                        <th class="px-4 py-2 text-left text-xs font-medium text-muted-foreground uppercase">Current</th>
                        <th class="px-4 py-2 text-left text-xs font-medium text-muted-foreground uppercase">Latest</th>
                    </tr>
                </thead>
                <tbody class="divide-y">
                    <?php foreach ($updateStatusPlugins as $p): ?>
                    <tr>
                        <td class="px-4 py-3 font-medium"><?= sanitize($p['name'] ?? '—') ?></td>
                        <td class="px-4 py-3 font-mono"><?= sanitize($p['current'] ?? '—') ?></td>
                        <td class="px-4 py-3 font-mono"><?= sanitize($p['latest'] ?? '—') ?></td>
                    </tr>
                    <?php endforeach; ?>
                </tbody>
            </table>
            <?php endif; ?>
        </div>
        <?php else: ?>
        <div class="p-6">
            <?php if (empty($updateStatusThemes)): ?>
            <p class="text-muted-foreground">All themes are up to date.</p>
            <?php else: ?>
            <table class="w-full text-sm">
                <thead class="bg-muted/50">
                    <tr>
                        <th class="px-4 py-2 text-left text-xs font-medium text-muted-foreground uppercase">Theme</th>
                        <th class="px-4 py-2 text-left text-xs font-medium text-muted-foreground uppercase">Current</th>
                        <th class="px-4 py-2 text-left text-xs font-medium text-muted-foreground uppercase">Latest</th>
                    </tr>
                </thead>
                <tbody class="divide-y">
                    <?php foreach ($updateStatusThemes as $t): ?>
                    <tr>
                        <td class="px-4 py-3 font-medium"><?= sanitize($t['name'] ?? '—') ?></td>
                        <td class="px-4 py-3 font-mono"><?= sanitize($t['current'] ?? '—') ?></td>
                        <td class="px-4 py-3 font-mono"><?= sanitize($t['latest'] ?? '—') ?></td>
                    </tr>
                    <?php endforeach; ?>
                </tbody>
            </table>
            <?php endif; ?>
        </div>
        <?php endif; ?>
    </div>
    <?php elseif ($mainTab === 'installed'): ?>
    <!-- Installed on this site content -->
    <div class="px-6 py-4 border-b flex items-center justify-between gap-4 flex-wrap bg-muted/20">
        <?php if ($siteSnapshot && !empty($siteSnapshot['checked_at'])): ?>
        <span class="text-sm text-muted-foreground">Last synced: <?= formatDateTime($siteSnapshot['checked_at']) ?></span>
        <?php endif; ?>
    </div>
    <div class="border-b">
        <nav class="flex -mb-px" aria-label="Installed snapshot tabs">
            <a href="?<?= $installedParams ?>&snapshot_tab=core" class="px-6 py-4 text-sm font-medium border-b-2 <?= $snapshotTab === 'core' ? 'border-primary text-primary' : 'border-transparent text-muted-foreground hover:text-foreground hover:border-border' ?>">
                <span class="flex items-center gap-2">Core</span>
            </a>
            <a href="?<?= $installedParams ?>&snapshot_tab=plugins" class="px-6 py-4 text-sm font-medium border-b-2 <?= $snapshotTab === 'plugins' ? 'border-primary text-primary' : 'border-transparent text-muted-foreground hover:text-foreground hover:border-border' ?>">
                <span class="flex items-center gap-2">Plugins <span class="badge badge-success"><?= count($snapshotPlugins) ?></span></span>
            </a>
            <a href="?<?= $installedParams ?>&snapshot_tab=themes" class="px-6 py-4 text-sm font-medium border-b-2 <?= $snapshotTab === 'themes' ? 'border-primary text-primary' : 'border-transparent text-muted-foreground hover:text-foreground hover:border-border' ?>">
                <span class="flex items-center gap-2">Themes <span class="badge badge-warning"><?= count($snapshotThemes) ?></span></span>
            </a>
            <a href="?<?= $installedParams ?>&snapshot_tab=users" class="px-6 py-4 text-sm font-medium border-b-2 <?= $snapshotTab === 'users' ? 'border-primary text-primary' : 'border-transparent text-muted-foreground hover:text-foreground hover:border-border' ?>">
                <span class="flex items-center gap-2">Users <span class="badge badge-info"><?= count($snapshotUsers) ?></span></span>
            </a>
        </nav>
    </div>
    <div class="overflow-x-auto">
        <?php if (!$siteSnapshot): ?>
        <div class="p-12 text-center">
            <p class="text-muted-foreground">No snapshot for this site yet. The Stapolin Activity plugin sends installed plugins, themes, core version and users every hour.</p>
        </div>
        <?php elseif ($snapshotTab === 'core'): ?>
        <div class="p-6">
            <p class="text-muted-foreground">WordPress version: <span class="font-mono font-medium"><?= sanitize($siteSnapshot['core_version'] ?? '—') ?></span></p>
        </div>
        <?php elseif ($snapshotTab === 'plugins'): ?>
        <div class="p-6">
            <?php if (empty($snapshotPlugins)): ?>
            <p class="text-muted-foreground">No plugins reported in snapshot.</p>
            <?php else: ?>
            <table class="w-full text-sm">
                <thead class="bg-muted/50">
                    <tr>
                        <th class="px-4 py-2 text-left text-xs font-medium text-muted-foreground uppercase">Plugin</th>
                        <th class="px-4 py-2 text-left text-xs font-medium text-muted-foreground uppercase">Version</th>
                    </tr>
                </thead>
                <tbody class="divide-y">
                    <?php foreach ($snapshotPlugins as $p): ?>
                    <tr>
                        <td class="px-4 py-3 font-medium"><?= sanitize($p['name'] ?? '—') ?></td>
                        <td class="px-4 py-3 font-mono"><?= sanitize($p['version'] ?? '—') ?></td>
                    </tr>
                    <?php endforeach; ?>
                </tbody>
            </table>
            <?php endif; ?>
        </div>
        <?php elseif ($snapshotTab === 'themes'): ?>
        <div class="p-6">
            <?php if (empty($snapshotThemes)): ?>
            <p class="text-muted-foreground">No themes reported in snapshot.</p>
            <?php else: ?>
            <table class="w-full text-sm">
                <thead class="bg-muted/50">
                    <tr>
                        <th class="px-4 py-2 text-left text-xs font-medium text-muted-foreground uppercase">Theme</th>
                        <th class="px-4 py-2 text-left text-xs font-medium text-muted-foreground uppercase">Version</th>
                    </tr>
                </thead>
                <tbody class="divide-y">
                    <?php foreach ($snapshotThemes as $t): ?>
                    <tr>
                        <td class="px-4 py-3 font-medium"><?= sanitize($t['name'] ?? '—') ?></td>
                        <td class="px-4 py-3 font-mono"><?= sanitize($t['version'] ?? '—') ?></td>
                    </tr>
                    <?php endforeach; ?>
                </tbody>
            </table>
            <?php endif; ?>
        </div>
        <?php else: ?>
        <div class="p-6">
            <?php if (empty($snapshotUsers)): ?>
            <p class="text-muted-foreground">No users reported in snapshot.</p>
            <?php else: ?>
            <table class="w-full text-sm">
                <thead class="bg-muted/50">
                    <tr>
                        <th class="px-4 py-2 text-left text-xs font-medium text-muted-foreground uppercase">Username</th>
                        <th class="px-4 py-2 text-left text-xs font-medium text-muted-foreground uppercase">Email</th>
                        <th class="px-4 py-2 text-left text-xs font-medium text-muted-foreground uppercase">Roles</th>
                    </tr>
                </thead>
                <tbody class="divide-y">
                    <?php foreach ($snapshotUsers as $u): ?>
                    <tr>
                        <td class="px-4 py-3 font-medium"><?= sanitize($u['username'] ?? '—') ?></td>
                        <td class="px-4 py-3"><?= sanitize($u['email'] ?? '—') ?></td>
                        <td class="px-4 py-3"><?= sanitize(is_array($u['roles'] ?? null) ? implode(', ', $u['roles']) : '—') ?></td>
                    </tr>
                    <?php endforeach; ?>
                </tbody>
            </table>
            <?php endif; ?>
        </div>
        <?php endif; ?>
    </div>
    <?php elseif ($mainTab === 'activity'): ?>
    <!-- Website Activity content -->
    <div class="border-b">
        <nav class="flex -mb-px" aria-label="Tabs">
            <a href="?<?= $activityParams ?>&tab=plugins" 
               class="px-6 py-4 text-sm font-medium border-b-2 <?= $currentTab === 'plugins' ? 'border-primary text-primary' : 'border-transparent text-muted-foreground hover:text-foreground hover:border-border' ?>"
               data-testid="tab-plugins">
                <span class="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 badge-success"><?= $pluginCount ?></span>
                </span>
            </a>
<a href="?<?= $activityParams ?>&tab=themes"
               class="px-6 py-4 text-sm font-medium border-b-2 <?= $currentTab === 'themes' ? 'border-primary text-primary' : 'border-transparent text-muted-foreground hover:text-foreground hover:border-border' ?>"
               data-testid="tab-themes">
                <span class="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 badge-warning"><?= $themeCount ?></span>
                </span>
            </a>
<a href="?<?= $activityParams ?>&tab=core"
               class="px-6 py-4 text-sm font-medium border-b-2 <?= $currentTab === 'core' ? 'border-primary text-primary' : 'border-transparent text-muted-foreground hover:text-foreground hover:border-border' ?>"
               data-testid="tab-core">
                <span class="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 Updates
                    <span class="badge badge-info"><?= $coreCount ?></span>
                </span>
            </a>
<a href="?<?= $activityParams ?>&tab=extra"
               class="px-6 py-4 text-sm font-medium border-b-2 <?= $currentTab === 'extra' ? 'border-primary text-primary' : 'border-transparent text-muted-foreground hover:text-foreground hover:border-border' ?>"
               data-testid="tab-extra">
                <span class="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="M12 6v6m0 0v6m0-6h6m-6 0H6"/>
                    </svg>
                    Extra Work
                    <span class="badge badge-primary"><?= $extraCount ?></span>
                </span>
            </a>
<a href="?<?= $activityParams ?>&tab=users"
               class="px-6 py-4 text-sm font-medium border-b-2 <?= $currentTab === 'users' ? 'border-primary text-primary' : 'border-transparent text-muted-foreground hover:text-foreground hover:border-border' ?>"
               data-testid="tab-users">
                <span class="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="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"/>
                    </svg>
                    User Activity
                    <span class="badge badge-secondary"><?= $userCount ?></span>
                </span>
            </a>
<a href="?<?= $activityParams ?>&tab=backups"
               class="px-6 py-4 text-sm font-medium border-b-2 <?= $currentTab === 'backups' ? 'border-primary text-primary' : 'border-transparent text-muted-foreground hover:text-foreground hover:border-border' ?>"
               data-testid="tab-backups">
                <span class="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 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4"/>
                    </svg>
                    Backups
                    <span class="badge badge-info"><?= $backupCount ?></span>
                </span>
            </a>
        </nav>
    </div>
    
    <!-- Tab Content -->
    <div class="overflow-x-auto">
        <?php if (empty($updates)): ?>
        <div class="p-12 text-center">
            <svg class="w-16 h-16 mx-auto text-muted-foreground/50 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4"/>
            </svg>
            <h4 class="text-lg font-medium mb-2">No <?= $currentTab === 'users' ? 'user activity' : ($currentTab === 'backups' ? 'backups' : $currentTab . ' updates') ?> found</h4>
            <p class="text-muted-foreground"><?= $currentTab === 'users' ? 'User login activity will appear here once tracked' : ($currentTab === 'backups' ? 'Backup records will appear here once tracked' : 'Updates will appear here once they are tracked') ?></p>
        </div>
        <?php else: ?>
        <table class="w-full">
            <thead class="bg-muted/50">
                <tr>
                    <?php if ($currentTab === 'plugins'): ?>
                    <th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">Plugin Name</th>
                    <th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">Version</th>
                    <th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">Action</th>
                    <th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">Date</th>
                    <?php elseif ($currentTab === 'themes'): ?>
                    <th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">Theme Name</th>
                    <th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">Version</th>
                    <th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">Action</th>
                    <th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">Date</th>
                    <?php elseif ($currentTab === 'core'): ?>
                    <th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">Version</th>
                    <th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">Action</th>
                    <th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">Date</th>
                    <?php elseif ($currentTab === 'users'): ?>
                    <th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">Performed By</th>
                    <th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">User Role</th>
                    <th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">Target User</th>
                    <th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">Action</th>
                    <th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">Location</th>
                    <th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">IP Address</th>
                    <th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">Date/Time</th>
                    <?php elseif ($currentTab === 'backups'): ?>
                    <th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">Destination</th>
                    <th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">Filename</th>
                    <th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">Status</th>
                    <th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">Trigger</th>
                    <th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">Modified</th>
                    <?php else: ?>
                    <th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">Description</th>
                    <th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">Duration</th>
                    <th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">Date</th>
                    <?php endif; ?>
                </tr>
            </thead>
            <tbody class="divide-y">
                <?php foreach ($updates as $update): ?>
                <tr class="table-row">
                    <?php if ($currentTab === 'plugins'): ?>
                    <td class="px-6 py-4 font-medium"><?= sanitize($update['plugin_name']) ?></td>
                    <td class="px-6 py-4 font-mono text-sm"><?= sanitize($update['plugin_version'] ?? '-') ?></td>
                    <td class="px-6 py-4">
                        <span class="badge <?= $update['action'] === 'update' ? 'badge-success' : ($update['action'] === 'install' ? 'badge-info' : 'badge-warning') ?>">
                            <?= sanitize(ucfirst($update['action'] ?? 'update')) ?>
                        </span>
                    </td>
                    <td class="px-6 py-4 text-sm text-muted-foreground"><?= formatDateTime($update['hist_time']) ?></td>
                    <?php elseif ($currentTab === 'themes'): ?>
                    <td class="px-6 py-4 font-medium"><?= sanitize($update['theme_name']) ?></td>
                    <td class="px-6 py-4 font-mono text-sm"><?= sanitize($update['theme_version'] ?? '-') ?></td>
                    <td class="px-6 py-4">
                        <span class="badge <?= $update['action'] === 'update' ? 'badge-success' : ($update['action'] === 'install' ? 'badge-info' : 'badge-warning') ?>">
                            <?= sanitize(ucfirst($update['action'] ?? 'update')) ?>
                        </span>
                    </td>
                    <td class="px-6 py-4 text-sm text-muted-foreground"><?= formatDateTime($update['hist_time']) ?></td>
                    <?php elseif ($currentTab === 'core'): ?>
                    <td class="px-6 py-4 font-mono"><?= sanitize($update['version']) ?></td>
                    <td class="px-6 py-4">
                        <span class="badge badge-info"><?= sanitize(ucfirst($update['action'] ?? 'update')) ?></span>
                    </td>
                    <td class="px-6 py-4 text-sm text-muted-foreground"><?= formatDateTime($update['hist_time']) ?></td>
                    <?php elseif ($currentTab === 'users'): ?>
                    <td class="px-6 py-4 font-medium"><?= sanitize($update['performed_by_username'] ?? '-') ?></td>
                    <td class="px-6 py-4 text-sm"><?= sanitize($update['user_caps'] ?? '-') ?></td>
                    <td class="px-6 py-4"><?= sanitize($update['target_username'] ?? '-') ?></td>
                    <td class="px-6 py-4">
                        <?php 
                        $action = $update['action'] ?? '';
                        $actionLabel = str_replace('_', ' ', ucfirst($action));
                        $badgeClass = 'badge-secondary';
                        if ($action === 'logged_in') $badgeClass = 'badge-success';
                        elseif ($action === 'logged_out') $badgeClass = 'badge-warning';
                        elseif ($action === 'registered') $badgeClass = 'badge-info';
                        elseif ($action === 'updated') $badgeClass = 'badge-primary';
                        ?>
                        <span class="badge <?= $badgeClass ?>"><?= sanitize($actionLabel) ?></span>
                    </td>
                    <td class="px-6 py-4 text-sm"><?= sanitize($update['action_location'] ?? '-') ?></td>
                    <td class="px-6 py-4 text-sm font-mono"><?= sanitize($update['hist_ip'] ?? '-') ?></td>
                    <td class="px-6 py-4 text-sm text-muted-foreground"><?= formatDateTime($update['hist_time']) ?></td>
                    <?php elseif ($currentTab === 'backups'): ?>
                    <td class="px-6 py-4">
                        <span class="badge badge-secondary"><?= sanitize(strtoupper($update['destination'] ?? '-')) ?></span>
                    </td>
                    <td class="px-6 py-4 font-mono text-sm"><?= sanitize($update['filename'] ?? '-') ?></td>
                    <td class="px-6 py-4">
                        <?php 
                        $status = strtolower($update['status'] ?? '');
                        $statusLabel = ucfirst($status);
                        $statusClass = 'badge-secondary';
                        if ($status === 'completed' || $status === 'success') $statusClass = 'badge-success';
                        elseif ($status === 'failed' || $status === 'error') $statusClass = 'badge-destructive';
                        elseif ($status === 'pending' || $status === 'running') $statusClass = 'badge-warning';
                        ?>
                        <span class="badge <?= $statusClass ?>"><?= sanitize($statusLabel) ?></span>
                    </td>
                    <td class="px-6 py-4 text-sm"><?= sanitize(str_replace('_', ' ', ucfirst($update['backup_trigger'] ?? '-'))) ?></td>
                    <td class="px-6 py-4 text-sm text-muted-foreground"><?= formatDateTime($update['modified']) ?></td>
                    <?php else: ?>
                    <td class="px-6 py-4"><?= sanitize($update['description']) ?></td>
                    <td class="px-6 py-4"><?= formatDuration($update['duration_minutes'] ?? 0) ?></td>
                    <td class="px-6 py-4 text-sm text-muted-foreground"><?= formatDate($update['work_date']) ?></td>
                    <?php endif; ?>
                </tr>
                <?php endforeach; ?>
            </tbody>
        </table>
        <?php endif; ?>
    </div>
    <?php endif; ?>
</div>

<!-- Edit Website Modal -->
<div id="editWebsiteModal" class="hidden fixed inset-0 z-50 overflow-y-auto" aria-modal="true">
    <div class="flex items-center justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:block sm:p-0">
        <div class="fixed inset-0 bg-black/50 transition-opacity" onclick="document.getElementById('editWebsiteModal').classList.add('hidden')"></div>
        
        <div class="inline-block align-bottom card rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full">
            <form action="api/websites.php" method="POST">
                <input type="hidden" name="action" value="update">
                <input type="hidden" name="website_id" value="<?= $website['website_id'] ?>">
                <input type="hidden" name="redirect" value="website-detail.php?id=<?= $website['website_id'] ?>">
                
                <div class="px-6 py-4 border-b">
                    <h3 class="text-lg font-semibold">Edit Website</h3>
                </div>
                
                <div class="px-6 py-4 space-y-4">
                    <div>
                        <label for="edit_website_url" class="block text-sm font-medium mb-1">Website URL <span class="text-destructive">*</span></label>
                        <input type="url" 
                               id="edit_website_url" 
                               name="website_url" 
                               required
                               value="<?= sanitize($website['website_url']) ?>"
                               class="w-full px-3 py-2 rounded-md text-sm"
                               data-testid="input-edit-website-url">
                    </div>
                    
                    <div>
                        <label for="edit_website_name" class="block text-sm font-medium mb-1">Website Name</label>
                        <input type="text" 
                               id="edit_website_name" 
                               name="website_name"
                               value="<?= sanitize($website['website_name'] ?? '') ?>"
                               class="w-full px-3 py-2 rounded-md text-sm"
                               data-testid="input-edit-website-name">
                    </div>
                    
                    <div>
                        <label for="edit_client_id" class="block text-sm font-medium mb-1">Client</label>
                        <select id="edit_client_id" 
                                name="client_id"
                                class="w-full px-3 py-2 rounded-md text-sm"
                                data-testid="select-edit-client">
                            <option value="">No client assigned</option>
                            <?php foreach ($clients as $client): ?>
                            <option value="<?= $client['client_id'] ?>" <?= $client['client_id'] == $website['client_id'] ? 'selected' : '' ?>>
                                <?= sanitize($client['client_name']) ?>
                            </option>
                            <?php endforeach; ?>
                        </select>
                    </div>
                    
                    <div class="border-t pt-4 mt-4">
                        <p class="text-xs text-muted-foreground mb-2">Optional: use for hosts that require an admin session for remote updates (e.g. some WP Engine setups). Create an <a href="https://wordpress.org/support/article/application-passwords/" target="_blank" rel="noopener" class="text-primary hover:underline">Application Password</a> under Users → Profile on the WordPress site.</p>
                        <div class="space-y-2">
                            <label for="edit_wp_auth_username" class="block text-sm font-medium">WordPress admin username</label>
                            <input type="text" 
                                   id="edit_wp_auth_username" 
                                   name="wp_auth_username"
                                   value="<?= sanitize($website['wp_auth_username'] ?? '') ?>"
                                   class="w-full px-3 py-2 rounded-md text-sm"
                                   placeholder="admin"
                                   autocomplete="off">
                            <label for="edit_wp_auth_app_password" class="block text-sm font-medium">Application password</label>
                            <input type="password" 
                                   id="edit_wp_auth_app_password" 
                                   name="wp_auth_app_password"
                                   value=""
                                   class="w-full px-3 py-2 rounded-md text-sm"
                                   placeholder="Leave blank to keep existing"
                                   autocomplete="new-password">
                        </div>
                    </div>
                    
                    <div class="border-t pt-4 mt-4">
                        <p class="text-xs text-muted-foreground mb-2"><strong>Stapolin Maintenance</strong>: no Authorization header. Install the plugin on the site and paste the connection secret in Settings → Stapolin Maintenance. Use this for WP Engine or when Application Password fails.</p>
                        <p class="text-sm mb-2">Connection secret: <?= !empty($website['maintenance_secret']) ? '<span class="text-green-600 dark:text-green-400">Set</span>' : '<span class="text-muted-foreground">Not set</span>' ?></p>
                        <label class="flex items-center gap-2 cursor-pointer">
                            <input type="checkbox" name="regenerate_maintenance_secret" value="1" id="regenerate_maintenance_secret" class="rounded">
                            <span class="text-sm">Regenerate connection secret (new secret shown after save; paste into the site)</span>
                        </label>
                    </div>

                    <div class="border-t pt-4 mt-4">
                        <p class="text-xs text-muted-foreground mb-2"><strong>WP Manager Companion</strong>: paste the Pairing Token from the plugin settings on the WordPress site. The dashboard will complete the pairing and use WP Manager for updates on this site.</p>
                        <p class="text-sm mb-2">
                            WP Manager status:
                            <?php if (!empty($website['wp_manager_client_id'] ?? '')): ?>
                                <span class="text-green-600 dark:text-green-400">Paired</span>
                            <?php else: ?>
                                <span class="text-muted-foreground">Not paired</span>
                            <?php endif; ?>
                        </p>
                        <div class="space-y-2">
                            <label for="edit_wp_manager_pairing_token" class="block text-sm font-medium">Pairing Token</label>
                            <input type="text"
                                   id="edit_wp_manager_pairing_token"
                                   name="wp_manager_pairing_token"
                                   value=""
                                   class="w-full px-3 py-2 rounded-md text-sm"
                                   placeholder="Paste token from WP Manager Companion settings"
                                   autocomplete="off">
                            <?php if (!empty($website['wp_manager_client_id'] ?? '')): ?>
                            <label class="flex items-center gap-2 cursor-pointer mt-2">
                                <input type="checkbox" name="clear_wp_manager" value="1" class="rounded">
                                <span class="text-sm">Clear existing WP Manager connection (remove client credentials)</span>
                            </label>
                            <?php endif; ?>
                        </div>
                    </div>
                </div>
                
                <div class="px-6 py-4 border-t flex items-center justify-end gap-3">
                    <button type="button" 
                            onclick="document.getElementById('editWebsiteModal').classList.add('hidden')"
                            class="btn-secondary px-4 py-2 rounded-md text-sm font-medium">
                        Cancel
                    </button>
                    <button type="submit" 
                            class="btn-primary px-4 py-2 rounded-md text-sm font-medium"
                            data-testid="button-submit-edit-website">
                        Save Changes
                    </button>
                </div>
            </form>
        </div>
    </div>
</div>

<?php if (!empty($hasUptimeData)): ?>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
<script>
(function() {
    var d = window.__uptimeChartData;
    if (!d || !d.labels || !d.labels.length) return;
    var opts = { responsive: true, maintainAspectRatio: false };
    new Chart(document.getElementById('uptimeChart'), {
        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('responseTimeChart'), {
        type: 'line',
        data: {
            labels: d.labels,
            datasets: [{ label: 'Response time (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 require_once __DIR__ . '/includes/footer.php'; ?>

Youez - 2016 - github.com/yon3zu
LinuXploit