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>