1
1
mirror of https://github.com/xodivorce/isdowndetectordown.git synced 2025-12-20 09:19:33 +05:30
Files
isdowndetectordown/dashboard.php

628 lines
26 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
require __DIR__ . '/core/connection.php';
function findClosestRegionTimezone(PDO $pdo, float $lat, float $lon): ?string
{
$stmt = $pdo->query(
'SELECT timezone, lat, lon
FROM regions
WHERE timezone IS NOT NULL AND timezone <> ""'
);
$regions = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (!$regions) return null;
$closest = null;
$min = PHP_FLOAT_MAX;
foreach ($regions as $r) {
$dLat = $lat - (float)$r['lat'];
$dLon = $lon - (float)$r['lon'];
$dist = $dLat * $dLat + $dLon * $dLon;
if ($dist < $min) {
$min = $dist;
$closest = $r['timezone'];
}
}
return $closest;
}
$clientTz = isset($_GET['tz']) ? (string) $_GET['tz'] : null;
if ($clientTz && in_array($clientTz, timezone_identifiers_list(), true)) {
date_default_timezone_set($clientTz);
} else {
$fallback = 'UTC';
if (isset($_GET['lat'], $_GET['lon'])) {
$lat = (float) $_GET['lat'];
$lon = (float) $_GET['lon'];
$closestTz = findClosestRegionTimezone(db(), $lat, $lon);
if ($closestTz && in_array($closestTz, timezone_identifiers_list(), true)) {
$fallback = $closestTz;
}
}
date_default_timezone_set($fallback);
}
function checkSite(string $region, string $url, int $rankGroup, string $timezone): array
{
$start = microtime(true);
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_NOBODY => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_TIMEOUT => 15,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_USERAGENT => "XoStatusChecker/1.0",
CURLOPT_SSL_VERIFYPEER => true,
]);
curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
$latency = (int) ((microtime(true) - $start) * 1000);
curl_close($ch);
$up = false;
$note = null;
if ($error) {
$up = false;
$note = 'cannot_fetch_web';
} else {
if ($code >= 200 && $code < 500) {
$up = true;
if ($code === 403) {
$note = 'blocked_by_protection';
} elseif ($code === 429) {
$note = 'too_many_requests';
}
} elseif ($code >= 500 && $code <= 599) {
$up = false;
$note = 'cloudflare_origin_error';
}
}
try {
$dt = new DateTime('now', new DateTimeZone($timezone));
} catch (Exception $e) {
$dt = new DateTime('now', new DateTimeZone('UTC'));
}
return [
'region' => $region,
'url' => $url,
'rank_group' => $rankGroup,
'up' => $up,
'http' => $code ?: null,
'latency' => $latency,
'error' => $note,
'date' => $dt->format('Y-m-d'),
'time' => $dt->format('H:i:s'),
];
}
function haversineDistance(float $lat1, float $lon1, float $lat2, float $lon2): float
{
$earthRadius = 6371.0;
$lat1Rad = deg2rad($lat1);
$lon1Rad = deg2rad($lon1);
$lat2Rad = deg2rad($lat2);
$lat2Rad = deg2rad($lat2);
$lon2Rad = deg2rad($lon2);
$dLat = $lat2Rad - $lat1Rad;
$dLon = $lon2Rad - $lon1Rad;
$a = sin($dLat / 2) ** 2 +
cos($lat1Rad) * cos($lat2Rad) * sin($dLon / 2) ** 2;
$c = 2 * asin(min(1.0, sqrt($a)));
return $earthRadius * $c;
}
$geoStatus = isset($_GET['geo']) ? (string) $_GET['geo'] : null;
$userLat = isset($_GET['lat']) ? (float) $_GET['lat'] : null;
$userLon = isset($_GET['lon']) ? (float) $_GET['lon'] : null;
$hasUserLocation = ($geoStatus === '1' && $userLat !== 0.0 && $userLon !== 0.0);
$maxRank = isset($_GET['max_rank']) ? (int) $_GET['max_rank'] : 1;
if ($maxRank < 1)
$maxRank = 1;
if ($maxRank > 6)
$maxRank = 6;
$allStmt = db()->prepare("
SELECT id, region, url, rank_group, sort_order, rank_points, lat, lon, timezone
FROM regions
ORDER BY id ASC
");
$allStmt->execute();
$allRows = $allStmt->fetchAll();
foreach ($allRows as $i => $row) {
$allRows[$i]['user_distance'] = null;
}
$closestThree = [];
if ($hasUserLocation && !empty($allRows)) {
$distanceList = [];
foreach ($allRows as $i => $row) {
if ($row['lat'] === null || $row['lon'] === null) {
continue;
}
$distKm = haversineDistance(
$userLat,
$userLon,
(float) $row['lat'],
(float) $row['lon']
);
$allRows[$i]['user_distance'] = $distKm;
$distanceList[] = [
'idx' => $i,
'dist' => $distKm,
];
}
if (!empty($distanceList)) {
usort($distanceList, fn($a, $b) => $a['dist'] <=> $b['dist']);
$top3 = array_slice($distanceList, 0, 3);
$updateStmt = db()->prepare("
UPDATE regions
SET rank_points = rank_points + :points
WHERE id = :id
");
foreach ($top3 as $pos => $item) {
$points = 0;
if ($pos === 0) {
$points = 1;
} elseif ($pos === 1) {
$points = 1;
} elseif ($pos === 2) {
$points = 1;
}
$idx = $item['idx'];
$allRows[$idx]['rank_points'] = (int) $allRows[$idx]['rank_points'] + $points;
$updateStmt->execute([
':points' => $points,
':id' => $allRows[$idx]['id'],
]);
$row = $allRows[$idx];
$tz = (string) $row['timezone'];
$check = checkSite($row['region'], $row['url'], (int) $row['rank_group'], $tz);
$check['user_distance'] = $row['user_distance'];
$check['rank_points'] = $row['rank_points'];
$check['id'] = $row['id'];
$check['timezone'] = $tz;
$closestThree[] = $check;
}
}
}
$dynamicTiers = [];
if (!empty($allRows)) {
$sortedByPoints = $allRows;
usort($sortedByPoints, function (array $a, array $b): int {
$cmp = (int) $b['rank_points'] <=> (int) $a['rank_points'];
if ($cmp !== 0) {
return $cmp;
}
$cmp = (int) $a['rank_group'] <=> (int) $b['rank_group'];
if ($cmp !== 0) {
return $cmp;
}
return (int) $a['sort_order'] <=> (int) $b['sort_order'];
});
foreach ($sortedByPoints as $index => $row) {
$rankPosition = $index + 1;
$tier = intdiv($rankPosition - 1, 10) + 1;
if ($tier > 6) {
$tier = 6;
}
$dynamicTiers[$row['id']] = $tier;
}
}
foreach ($closestThree as &$r) {
$id = $r['id'] ?? null;
if ($id !== null && isset($dynamicTiers[$id])) {
$r['tier'] = $dynamicTiers[$id];
} else {
$r['tier'] = 6;
}
}
unset($r);
$rows = $allRows;
foreach ($rows as &$row) {
$id = $row['id'];
$row['tier'] = $dynamicTiers[$id] ?? 6;
}
unset($row);
usort($rows, function (array $a, array $b): int {
$cmp = (int) $b['rank_points'] <=> (int) $a['rank_points'];
if ($cmp !== 0) {
return $cmp;
}
$cmp = (int) $a['rank_group'] <=> (int) $b['rank_group'];
if ($cmp !== 0) {
return $cmp;
}
return (int) $a['sort_order'] <=> (int) $b['sort_order'];
});
$allUp = true;
$totalChecked = 0;
$checks = [];
foreach ($rows as $row) {
if ($row['tier'] > $maxRank) {
continue;
}
$tz = (string) $row['timezone'];
$check = checkSite($row['region'], $row['url'], (int) $row['rank_group'], $tz);
$check['user_distance'] = null;
$check['rank_points'] = $row['rank_points'];
$check['tier'] = $row['tier'];
$check['timezone'] = $tz;
$checks[] = $check;
if (!$check['up']) {
$allUp = false;
}
$totalChecked++;
}
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Is DownDetector down?</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="./src/output.css">
<base href="/Php-devs/isdowndetectordown/">
<link rel="icon" type="image/png" href="assets/favicon/favicon-96x96.png" sizes="96x96">
<link rel="icon" type="image/svg+xml" href="assets/favicon/favicon.svg">
<link rel="shortcut icon" href="assets/favicon/favicon.ico">
<link rel="apple-touch-icon" href="assets/favicon/apple-touch-icon.png">
<link rel="manifest" href="assets/favicon/site.webmanifest">
<meta name="apple-mobile-web-app-title" content="isdowndetectordown">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Lexend+Deca:wght@300;400;500;600;700&display=swap"
rel="stylesheet">
<style>
body {
font-family: 'Lexend Deca', system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
</style>
</head>
<body class="bg-gray-950 text-gray-200 p-6">
<div class="max-w-6xl mx-auto">
<div class="bg-gray-950 rounded-xl p-5 shadow-2xl border border-gray-800">
<h1 class="text-3xl font-medium mb-2">Is DownDetector down?</h1>
<p class="text-sm text-gray-400 mb-3 wrap-break-words leading-relaxed">
What if DownDetector goes down? Bro, you're supposed to detect outages,
not join them. Well, thats why this detector exists
<a href="#" class="text-blue-400 no-underline hover:underline"
onclick="location.reload(); return false;">
isdowndetectordown.rf.gd
</a> :)
</p>
<?php if ($geoStatus === 'denied'): ?>
<div class="text-xs text-yellow-300 mb-3">
Location access denied, using the default global rankings instead. To view the nearest regions, enable
location access in your browser settings.
</div>
<?php elseif ($geoStatus === 'unsupported'): ?>
<div class="text-xs text-gray-500 mb-3">
Your browser does not support geolocation, using the default global rankings instead.
</div>
<?php endif; ?>
<div
class="<?= $allUp ? 'bg-emerald-950 border-green-600' : 'bg-red-950 border-red-600' ?> rounded-lg px-4 py-4 text-sm mb-5 flex items-center gap-2.5 border">
<div>
<strong>
<?= $allUp
? 'Tier ' . ($maxRank > 1 ? '1' . (int) $maxRank : '1') . ' countries/regions are functioning properly.'
: 'Tier ' . ($maxRank > 1 ? '1' . (int) $maxRank : '1') . ' countries/regions are malfunctioning.'
?>
</strong><br>
<span class="text-xs">
Countries/Regions are organized by tier points and managed by the global userbase,
<?= (int) $totalChecked ?> webs were reviewed.
</span>
</div>
</div>
<?php if ($hasUserLocation && !empty($closestThree)): ?>
<div class="mb-5">
<div class="text-sm font-semibold mb-2 text-gray-200">
Nearest regions to you >
</div>
<div class="rounded-lg border border-gray-800 overflow-x-auto">
<table class="w-full border-collapse text-xs mt-2.5">
<thead class="bg-gray-950 border-b border-gray-800">
<tr>
<th class="text-left px-1.5 py-2 w-[20%]">Country/Region</th>
<th class="text-left px-1.5 py-2 w-[21%]">Domain</th>
<th class="text-left px-1.5 py-2 w-[24%]">Status</th>
<th class="text-left px-1.5 py-2 w-[8%]">HTTP</th>
<th class="text-left px-1.5 py-2 w-[9%]">Latency</th>
<th class="text-left px-1.5 py-2 w-[9%]">Date</th>
<th class="text-left px-1.5 py-2 w-[9%]">Time</th>
</tr>
</thead>
<tbody>
<?php foreach ($closestThree as $r): ?>
<tr class="bg-gray-950 border-t border-gray-900">
<td class="px-1.5 py-2 overflow-hidden text-ellipsis whitespace-nowrap">
<span
class="inline-flex items-center justify-center min-w-[22px] px-1.5 py-0.5 rounded-full text-[11px] bg-gray-800 text-gray-400 mr-1">
T<?= (int) $r['tier'] ?>
</span>
<?= htmlspecialchars($r['region'], ENT_QUOTES, 'UTF-8') ?>
</td>
<td class="px-1.5 py-2 overflow-hidden text-ellipsis whitespace-nowrap break-all">
<a href="<?= htmlspecialchars($r['url'], ENT_QUOTES, 'UTF-8') ?>" target="_blank"
class="text-blue-400 hover:underline">
<?= htmlspecialchars(parse_url($r['url'], PHP_URL_HOST) ?? $r['url'], ENT_QUOTES, 'UTF-8') ?>
</a>
</td>
<td class="px-1.5 py-2 overflow-hidden text-ellipsis whitespace-nowrap">
<?php if ($r['up']): ?>
<span
class="inline-flex items-center px-2 py-0.5 rounded-full text-[11px] font-medium bg-green-500/10 text-green-400">UP</span>
<?php
if ($r['error'] === 'blocked_by_protection') {
echo '<span class="inline-flex items-center px-2 py-0.5 rounded-full text-[10px] bg-blue-500/10 text-blue-300 ml-1.5">blocked by protection</span>';
} elseif ($r['error'] === 'too_many_requests') {
echo '<span class="inline-flex items-center px-2 py-0.5 rounded-full text-[10px] bg-blue-500/10 text-blue-300 ml-1.5">too many requests</span>';
} elseif ($r['error'] === null) {
echo '<span class="inline-flex items-center px-2 py-0.5 rounded-full text-[10px] bg-blue-500/10 text-blue-300 ml-1.5">no protection found</span>';
}
?>
<?php else: ?>
<span
class="inline-flex items-center px-2 py-0.5 rounded-full text-[11px] font-medium bg-red-500/10 text-red-300">DOWN</span>
<?php if ($r['error'] === 'cloudflare_origin_error'): ?>
<span
class="inline-flex items-center px-2 py-0.5 rounded-full text-[10px] bg-red-500/20 text-red-200 ml-1.5">cloudflare
origin error</span>
<?php elseif ($r['error'] === 'cannot_fetch_web'): ?>
<span
class="inline-flex items-center px-2 py-0.5 rounded-full text-[10px] bg-red-500/20 text-red-200 ml-1.5">cann't
fetch web</span>
<?php endif; ?>
<?php endif; ?>
</td>
<td class="px-1.5 py-2 whitespace-nowrap font-mono">
<?= $r['http'] !== null ? (int) $r['http'] : '-' ?>
</td>
<td class="px-1.5 py-2 whitespace-nowrap">
<?= $r['latency'] ?: '-' ?>
</td>
<td class="px-1.5 py-2 whitespace-nowrap">
<?= htmlspecialchars($r['date']) ?>
</td>
<td class="px-1.5 py-2 whitespace-nowrap">
<?= htmlspecialchars($r['time']) ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<?php endif; ?>
<div class="text-sm font-semibold mb-2 text-gray-200">
Globally top regions >
</div>
<div class="rounded-lg border border-gray-800 mb-4 overflow-x-auto">
<table class="w-full border-collapse text-xs mt-2.5">
<thead class="bg-gray-950 border-b border-gray-800">
<tr>
<th class="text-left px-1.5 py-2 w-[20%]">Country/Region</th>
<th class="text-left px-1.5 py-2 w-[21%]">Domain</th>
<th class="text-left px-1.5 py-2 w-[24%]">Status</th>
<th class="text-left px-1.5 py-2 w-[8%]">HTTP</th>
<th class="text-left px-1.5 py-2 w-[9%]">Latency</th>
<th class="text-left px-1.5 py-2 w-[9%]">Date</th>
<th class="text-left px-1.5 py-2 w-[9%]">Time</th>
</tr>
</thead>
<tbody>
<?php foreach ($checks as $r): ?>
<tr class="bg-gray-950 border-t border-gray-900">
<td class="px-1.5 py-2 overflow-hidden text-ellipsis whitespace-nowrap">
<span
class="inline-flex items-center justify-center min-w-[22px] px-1.5 py-0.5 rounded-full text-[11px] bg-gray-800 text-gray-400 mr-1">
T<?= (int) $r['tier'] ?>
</span>
<?= htmlspecialchars($r['region'], ENT_QUOTES, 'UTF-8') ?>
</td>
<td class="px-1.5 py-2 overflow-hidden text-ellipsis whitespace-nowrap break-all">
<a href="<?= htmlspecialchars($r['url'], ENT_QUOTES, 'UTF-8') ?>" target="_blank"
class="text-blue-400 hover:underline">
<?= htmlspecialchars(parse_url($r['url'], PHP_URL_HOST) ?? $r['url'], ENT_QUOTES, 'UTF-8') ?>
</a>
</td>
<td class="px-1.5 py-2 overflow-hidden text-ellipsis whitespace-nowrap">
<?php if ($r['up']): ?>
<span
class="inline-flex items-center px-2 py-0.5 rounded-full text-[11px] font-medium bg-green-500/10 text-green-400">UP</span>
<?php
if ($r['error'] === 'blocked_by_protection') {
echo '<span class="inline-flex items-center px-2 py-0.5 rounded-full text-[10px] bg-blue-500/10 text-blue-300 ml-1.5">blocked by protection</span>';
} elseif ($r['error'] === 'too_many_requests') {
echo '<span class="inline-flex items-center px-2 py-0.5 rounded-full text-[10px] bg-blue-500/10 text-blue-300 ml-1.5">too many requests</span>';
} elseif ($r['error'] === null) {
echo '<span class="inline-flex items-center px-2 py-0.5 rounded-full text-[10px] bg-blue-500/10 text-blue-300 ml-1.5">no protection found</span>';
}
?>
<?php else: ?>
<span
class="inline-flex items-center px-2 py-0.5 rounded-full text-[11px] font-medium bg-red-500/10 text-red-300">DOWN</span>
<?php if ($r['error'] === 'cloudflare_origin_error'): ?>
<span
class="inline-flex items-center px-2 py-0.5 rounded-full text-[10px] bg-red-500/20 text-red-200 ml-1.5">cloudflare
origin error</span>
<?php elseif ($r['error'] === 'cannot_fetch_web'): ?>
<span
class="inline-flex items-center px-2 py-0.5 rounded-full text-[10px] bg-red-500/20 text-red-200 ml-1.5">cann't
fetch web</span>
<?php endif; ?>
<?php endif; ?>
</td>
<td class="px-1.5 py-2 whitespace-nowrap font-mono">
<?= $r['http'] !== null ? (int) $r['http'] : '-' ?>
</td>
<td class="px-1.5 py-2 whitespace-nowrap">
<?= $r['latency'] ?: '-' ?>
</td>
<td class="px-1.5 py-2 whitespace-nowrap">
<?= htmlspecialchars($r['date']) ?>
</td>
<td class="px-1.5 py-2 whitespace-nowrap">
<?= htmlspecialchars($r['time']) ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php if ($maxRank < 6): ?>
<div class="mt-3 text-center">
<a href="?max_rank=<?= (int) ($maxRank + 1) ?>" class="inline-flex items-center justify-center
px-3 py-1.5 text-[13.5px] /* mobile size */
sm:px-3.5 sm:py-2 sm:text-sm /* desktop size */
rounded-full border border-gray-700 bg-gray-900 text-gray-200
hover:bg-gray-800 transition-colors">
View more ( Tier <?= (int) ($maxRank + 1) ?> )
</a>
</div>
<?php endif; ?>
<div
class="mt-4 rounded-xl border border-gray-800 bg-[#0c0f17] p-5 text-[11px] text-gray-400 leading-relaxed">
<h2 class="text-sm font-semibold text-gray-200 mb-3">Technical Information</h2>
<p class="mb-2">
<span class="sm underline">Response Codes</span>
> 200299 OK. 300399 redirects.
403 = protected, 429 = too many requests.
500599 = Cloudflare's origin error.
</p>
<p class="mb-2">
<span class="sm underline">Protection</span>
> Uses standard HEAD checks only; no bypassing of Cloudflare, WAF, or rate-limits.
Some protected sites may return 403, but this means they are still fully operational.
</p>
<p>
<span class="sm underline">Latency</span>
> Includes DNS, SSL/TLS handshake, and server response.
Distance + routing can affect timing.
</p>
</div>
<div
class="mt-4 rounded-xl border border-gray-800 bg-[#0c0f17] p-5 text-[11px] text-gray-400 leading-relaxed text-center">
<p class="text-gray-400 mb-1">
This website is owned by
<a href="https://www.xodivorce.in" target="_blank"
class="text-blue-400 no-underline hover:underline">
xodivorce,
</a>
<br>
and managed by
<a href="https://neosubhamoy.com" target="_blank"
class="text-blue-400 no-underline hover:underline">
neosubhamoy.
</a>
</p>
<div class="flex items-center justify-center gap-4 mb-2 text-[11px]">
<a href="https://github.com/xodivorce/isdowndetectordown" target="_blank"
class="text-blue-400 no-underline hover:underline">
GitHub
</a>
<span class="text-gray-600">|</span>
<a href="https://gitea.neosubhamoy.com/xodivorce/isdowndetectordown" target="_blank"
class="text-green-400 no-underline hover:underline">
Gitea
</a>
</div>
<p class="text-gray-300 font-medium">
<span id="copyright-text"></span>
</p>
</div>
</div>
</body>
<script>
window.APP_CONFIG = {
startYear: <?= json_encode($_ENV['START_YEAR'] ?? 2025) ?>
};
</script>
<script src="./assets/js/dashboard.js"></script>
</html>