1
1
mirror of https://github.com/xodivorce/isdowndetectordown.git synced 2025-12-20 10:29:34 +05:30

(chore): initial MVP release

This commit is contained in:
2025-11-21 14:41:07 +05:30
commit ebaf5cb698
117 changed files with 15381 additions and 0 deletions

628
dashboard.php Normal file
View File

@@ -0,0 +1,628 @@
<?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>