HEX
Server: Apache/2.4.46 (Win64) OpenSSL/1.1.1j PHP/8.4.25
System: Windows NT DESKTOP-4TAV2RJ 10.0 build 19045 (Windows 10) AMD64
User: fred (0)
PHP: 8.4.25
Disabled: NONE
Upload Files
File: C:/xampp/htdocs/wordpress0612/dashboard.php
<?php
// --- 1. CONFIGURATION & DATA PROCESSING ---
date_default_timezone_set('Asia/Taipei');
$logFile = 'visitor_log.csv';

$ipSummary = [];
$phpSummary = []; // New: Group by PHP file
$totalGlobalHits = 0;
$hitsToday = 0;
$now = time();
$oneDayAgo = $now - (24 * 60 * 60);

if (file_exists($logFile)) {
    if (($handle = fopen($logFile, "r")) !== FALSE) {
        while (($data = fgetcsv($handle, 1000, ",")) !== FALSE) {
            // CSV: [0]Time, [1]ScriptName, [2]IP, [3]Area
            $timestamp = strtotime($data[0]);
            $script    = $data[1];
            $ip        = $data[2];
            $area      = $data[3];
            $key       = $script . '___' . $ip;

            if (!isset($ipSummary[$key])) {
                $ipSummary[$key] = [
                    'script' => $script,
                    'ip' => $ip,
                    'area' => $area,
                    'total_count' => 0,
                    'count_24h' => 0
                ];
            }
            
            $ipSummary[$key]['total_count']++;
            $totalGlobalHits++;
            
            if ($timestamp >= $oneDayAgo) {
                $ipSummary[$key]['count_24h']++;
                $hitsToday++;
            }
            
            // Group by PHP file
            if (!isset($phpSummary[$script])) {
                $phpSummary[$script] = [
                    'total_count' => 0,
                    'count_24h' => 0,
                    'ips' => []
                ];
            }
            $phpSummary[$script]['total_count']++;
            if ($timestamp >= $oneDayAgo) {
                $phpSummary[$script]['count_24h']++;
            }
        }
        fclose($handle);
    }
}

// Group IP details by script
foreach($ipSummary as $item) {
    $script = $item['script'];
    $phpSummary[$script]['ips'][] = $item;
}

// Sort PHP files by 24h activity, then by total
uasort($phpSummary, function($a, $b) {
    if ($a['count_24h'] == $b['count_24h']) {
        return $b['total_count'] <=> $a['total_count'];
    }
    return $b['count_24h'] <=> $a['count_24h'];
});

// Sort IPs within each PHP file // by 24h activity, then by total
foreach($phpSummary as &$php) {
    uasort($php['ips'], function($a, $b) {
        if ($a['count_24h'] == $b['count_24h']) {
            return $b['total_count'] <=> $a['total_count'];
        }
        return $b['count_24h'] <=> $a['count_24h'];
    });
}
?>

<!DOCTYPE html>
<html lang="zh-Hant">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>PBN Tool Dashboard</title>
    <style>
        body { font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: #f0f2f5; padding: 15px; color: #1c1e21; margin: 0; }
        .container { max-width: 800px; margin: auto; }
        .header { margin: 20px 0; }
        .stat-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 20px; }
        .stat-card { background: white; padding: 15px; border-radius: 12px; text-align: center; box-shadow: 0 2px 4px rgba(0,0,0,0.08); }
        .stat-card span { display: block; font-size: 1.5rem; font-weight: bold; color: #1877f2; }
        .card { background: white; border-radius: 12px; padding: 15px; box-shadow: 0 2px 4px rgba(0,0,0,0.08); margin-bottom: 20px; }
        
        .controls { display: flex; flex-direction: column; gap: 10px; margin-bottom: 15px; }
        .btn-group { display: flex; gap: 8px; }
        button { flex: 1; padding: 12px; border-radius: 8px; border: 1px solid #ddd; background: white; cursor: pointer; font-weight: 600; }
        button.active { background: #1877f2; color: white; border: none; }
        
        input[type="text"] { width: 100%; padding: 14px; border: 1px solid #ddd; border-radius: 8px; font-size: 1rem; box-sizing: border-box; }
        
        table { width: 100%; border-collapse: collapse; margin-top: 10px; font-size: 0.9rem; }
        th { text-align: left; padding: 12px 8px; border-bottom: 2px solid #eee; color: #65676b; font-size: 0.8rem; text-transform: uppercase; }
        td { padding: 12px 8px; border-bottom: 1px solid #f0f2f5; vertical-align: top; }
        
        .php-row { cursor: pointer; background: #f8f9fa; font-weight: 600; }
        .php-row:hover { background: #e9ecef; }
        .php-row td { padding: 15px 8px; }
        .ip-row { display: none; }
        .ip-row.show { display: table-row; }
        .ip-row td { padding-left: 30px; }
        .expand-icon { display: inline-block; transition: transform 0.2s; margin-right: 8px; }
        .expand-icon.expanded { transform: rotate(90deg); }
        
        .badge-24h { background: #ff4d4f; color: white; padding: 2px 8px; border-radius: 6px; font-weight: bold; }
        .badge-total { background: #e4e6eb; color: #050505; padding: 2px 8px; border-radius: 6px; }
        code { background: #f0f2f5; padding: 2px 4px; border-radius: 4px; font-family: monospace; color: #e91e63; }
    </style>
</head>
<body>

<div class="container">
    <div class="header">
        <h1 style="margin:0; font-size: 1.5rem;">PBN Tool Stats</h1>
        <small style="color: #65676b;">Timezone: Asia/Taipei</small>
    </div>

    <div class="stat-grid">
        <div class="stat-card"><span><?php echo $hitsToday; ?></span> 24小時內點擊</div>
        <div class="stat-card"><span><?php echo $totalGlobalHits; ?></span> 總點擊次數</div>
    </div>

    <div class="card">
        <div class="controls">
            <div class="btn-group">
                <button id="btnAll" class="active" onclick="showAll()">所有記錄</button>
                <button id="btn24" onclick="filter24h()">🔥 僅顯示最近 24h</button>
            </div>
            <input type="text" id="dbSearch" onkeyup="filterStats()" placeholder="搜尋 IP 或 檔案名稱...">
        </div>

        <div style="overflow-x: auto;">
            <table id="statsTable">
                <thead>
                    <tr>
                        <th>檔案 / 腳本</th>
                        <th>訪客 IP / 地區</th>
                        <th style="text-align:center;">24h</th>
                        <th style="text-align:center;">總計</th>
                    </tr>
                </thead>
                <tbody>
                    <?php foreach($phpSummary as $phpName => $phpData): ?>
                    <!-- PHP File Row (Tier 1) -->
                    <tr class="php-row" data-24h="<?php echo $phpData['count_24h']; ?>" data-php="<?php echo htmlspecialchars($phpName); ?>" onclick="toggleIPs('<?php echo htmlspecialchars($phpName, ENT_QUOTES); ?>')">
                        <td>
                            <span class="expand-icon">▶</span>
                            <code><?php echo htmlspecialchars($phpName); ?></code>
                        </td>
                        <td style="color:#65676b; font-style: italic;">
                            點擊查看 IP 詳細資料
                        </td>
                        <td style="text-align:center;">
                            <?php if($phpData['count_24h'] > 0): ?>
                                <span class="badge-24h"><?php echo $phpData['count_24h']; ?></span>
                            <?php else: ?>
                                <span style="color:#ccc;">0</span>
                            <?php endif; ?>
                        </td>
                        <td style="text-align:center;">
                            <span class="badge-total"><?php echo $phpData['total_count']; ?></span>
                        </td>
                    </tr>
                    
                    <!-- IP Rows (Tier 2) -->
                    <?php foreach($phpData['ips'] as $item): ?>
                    <tr class="ip-row stat-row" data-php="<?php echo htmlspecialchars($phpName); ?>" data-24h="<?php echo $item['count_24h']; ?>">
                        <td></td>
                        <td>
                            <span style="color:#1877f2; font-weight:600;"><?php echo $item['ip']; ?></span><br>
                            <small style="color:#65676b;"><?php echo htmlspecialchars($item['area']); ?></small>
                        </td>
                        <td style="text-align:center;">
                            <?php if($item['count_24h'] > 0): ?>
                                <span class="badge-24h"><?php echo $item['count_24h']; ?></span>
                            <?php else: ?>
                                <span style="color:#ccc;">0</span>
                            <?php endif; ?>
                        </td>
                        <td style="text-align:center;">
                            <span class="badge-total"><?php echo $item['total_count']; ?></span>
                        </td>
                    </tr>
                    <?php endforeach; ?>
                    <?php endforeach; ?>
                </tbody>
            </table>
        </div>
    </div>
</div>

<script>
function toggleIPs(phpName) {
    let phpRow = document.querySelector('.php-row[data-php="' + phpName + '"]');
    let ipRows = document.querySelectorAll('.ip-row[data-php="' + phpName + '"]');
    let icon = phpRow.querySelector('.expand-icon');
    
    ipRows.forEach(row => {
        row.classList.toggle('show');
    });
    
    icon.classList.toggle('expanded');
}

function filter24h() {
    document.getElementById("btn24").classList.add("active");
    document.getElementById("btnAll").classList.remove("active");
    
    let phpRows = document.querySelectorAll(".php-row");
    phpRows.forEach(row => {
        let count24 = parseInt(row.getAttribute("data-24h"));
        row.style.display = (count24 > 0) ? "" : "none";
        
        // Hide all IP rows when filtering
        let phpName = row.getAttribute("data-php");
        let ipRows = document.querySelectorAll('.ip-row[data-php="' + phpName + '"]');
        ipRows.forEach(ipRow => {
            ipRow.classList.remove('show');
            ipRow.style.display = (count24 > 0) ? "" : "none";
        });
        
        // Reset expand icon
        let icon = row.querySelector('.expand-icon');
        if (icon) icon.classList.remove('expanded');
    });
}

function showAll() {
    document.getElementById("btnAll").classList.add("active");
    document.getElementById("btn24").classList.remove("active");
    
    let phpRows = document.querySelectorAll(".php-row");
    phpRows.forEach(row => {
        row.style.display = "";
        
        // Hide all IP rows
        let phpName = row.getAttribute("data-php");
        let ipRows = document.querySelectorAll('.ip-row[data-php="' + phpName + '"]');
        ipRows.forEach(ipRow => {
            ipRow.classList.remove('show');
            ipRow.style.display = "";
        });
        
        // Reset expand icon
        let icon = row.querySelector('.expand-icon');
        if (icon) icon.classList.remove('expanded');
    });
}

function filterStats() {
    let input = document.getElementById("dbSearch").value.toLowerCase();
    let phpRows = document.querySelectorAll(".php-row");
    
    phpRows.forEach(phpRow => {
        let phpName = phpRow.getAttribute("data-php").toLowerCase();
        let ipRows = document.querySelectorAll('.ip-row[data-php="' + phpRow.getAttribute("data-php") + '"]');
        
        let phpMatch = phpName.includes(input);
        let ipMatch = false;
        
        // Check if any IP matches
        ipRows.forEach(ipRow => {
            let text = ipRow.innerText.toLowerCase();
            if (text.includes(input)) {
                ipMatch = true;
            }
        });
        
        // Show PHP row if either PHP name or any IP matches
        if (phpMatch || ipMatch) {
            phpRow.style.display = "";
            
            // If searching and there's a match, auto-expand IPs
            if (input && (phpMatch || ipMatch)) {
                ipRows.forEach(ipRow => {
                    ipRow.classList.add('show');
                    let ipText = ipRow.innerText.toLowerCase();
                    // Only show matching IPs or show all if PHP name matched
                    ipRow.style.display = (phpMatch || ipText.includes(input)) ? "" : "none";
                });
                phpRow.querySelector('.expand-icon').classList.add('expanded');
            }
        } else {
            phpRow.style.display = "none";
            ipRows.forEach(ipRow => {
                ipRow.style.display = "none";
                ipRow.classList.remove('show');
            });
        }
    });
}
</script>

</body>
</html>