mirror of
https://github.com/xodivorce/isdowndetectordown.git
synced 2025-12-20 06:59:33 +05:30
628 lines
26 KiB
PHP
628 lines
26 KiB
PHP
<?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, that’s 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>
|
||
> 200–299 OK. 300–399 redirects.
|
||
403 = protected, 429 = too many requests.
|
||
500–599 = 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>
|