Services implement healthCheck() returning {status, detail, stale?}.
Status page refactored to use healthCheck() instead of ad-hoc checks.
Statuses: healthy, degraded (stale data), unhealthy (unreachable).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
252 lines
8.1 KiB
PHP
252 lines
8.1 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Website\Lethean\Controllers;
|
|
|
|
use Illuminate\Routing\Controller;
|
|
use Illuminate\Support\Facades\Cache;
|
|
use Mod\Chain\Services\DaemonRpc;
|
|
use Mod\Chain\Services\WalletRpc;
|
|
use Mod\Gateway\Services\GatewayRegistry;
|
|
|
|
/**
|
|
* lthn.io homepage.
|
|
*
|
|
* GET / — TLD landing with live chain stats
|
|
*/
|
|
class HomeController extends Controller
|
|
{
|
|
public function __construct(
|
|
private readonly DaemonRpc $rpc,
|
|
private readonly GatewayRegistry $registry,
|
|
private readonly WalletRpc $wallet,
|
|
) {}
|
|
|
|
public function index(): \Illuminate\View\View
|
|
{
|
|
$info = $this->rpc->getInfo();
|
|
$aliases = $this->rpc->getAllAliases();
|
|
|
|
return view('lethean::home', [
|
|
'height' => number_format($info['height'] ?? 0),
|
|
'aliases' => count($aliases['aliases'] ?? []),
|
|
'transactions' => number_format($info['tx_count'] ?? 0),
|
|
'difficulty' => [
|
|
'pow' => number_format($info['pow_difficulty'] ?? 0),
|
|
'pos' => $info['pos_difficulty'] ?? '0',
|
|
],
|
|
]);
|
|
}
|
|
|
|
public function apiDocs(): \Illuminate\View\View
|
|
{
|
|
return view('lethean::api');
|
|
}
|
|
|
|
public function pricing(): \Illuminate\View\View
|
|
{
|
|
return view('lethean::pricing');
|
|
}
|
|
|
|
public function sunrise(): \Illuminate\View\View
|
|
{
|
|
$result = $this->rpc->getAllAliases();
|
|
$aliases = $result['aliases'] ?? [];
|
|
|
|
$reserved = array_filter($aliases, fn ($a) => str_contains($a['comment'] ?? '', 'type=reserved'));
|
|
usort($reserved, fn ($a, $b) => ($a['alias'] ?? '') <=> ($b['alias'] ?? ''));
|
|
|
|
return view('lethean::sunrise', [
|
|
'names' => array_values($reserved),
|
|
'total' => count($reserved),
|
|
]);
|
|
}
|
|
|
|
public function about(): \Illuminate\View\View
|
|
{
|
|
return view('lethean::about');
|
|
}
|
|
|
|
public function why(): \Illuminate\View\View
|
|
{
|
|
return view('lethean::why');
|
|
}
|
|
|
|
public function network(): \Illuminate\View\View
|
|
{
|
|
$info = $this->rpc->getInfo();
|
|
|
|
return view('lethean::network', [
|
|
'info' => $info,
|
|
]);
|
|
}
|
|
|
|
public function servicesLanding(): \Illuminate\View\View
|
|
{
|
|
$result = $this->rpc->getAllAliases();
|
|
$aliases = $result['aliases'] ?? [];
|
|
|
|
$gateways = array_filter($aliases, fn ($a) => str_contains($a['comment'] ?? '', 'type=gateway'));
|
|
$proxyNodes = array_filter($aliases, fn ($a) =>
|
|
str_contains($a['comment'] ?? '', 'cap=') &&
|
|
(str_contains($a['comment'] ?? '', 'proxy') || str_contains($a['comment'] ?? '', 'vpn'))
|
|
);
|
|
|
|
// Enrich with live pairing data where available
|
|
$liveGateways = $this->registry->liveGateways();
|
|
$liveNames = array_column($liveGateways, 'name');
|
|
|
|
$enriched = array_map(function ($node) use ($liveNames, $liveGateways) {
|
|
$node['live'] = in_array($node['alias'], $liveNames);
|
|
if ($node['live']) {
|
|
$live = $liveGateways[array_search($node['alias'], $liveNames)];
|
|
$node['region'] = $live['region'] ?? 'unknown';
|
|
$node['load'] = $live['current_load'] ?? 0;
|
|
$node['connections'] = $live['current_connections'] ?? 0;
|
|
}
|
|
return $node;
|
|
}, array_values($proxyNodes));
|
|
|
|
return view('lethean::services-landing', [
|
|
'gatewayCount' => count($gateways),
|
|
'proxyNodes' => $enriched,
|
|
'liveCount' => count($liveGateways),
|
|
'totalNames' => count($aliases),
|
|
]);
|
|
}
|
|
|
|
public function sslCertificates(): \Illuminate\View\View
|
|
{
|
|
return view('lethean::services-ssl');
|
|
}
|
|
|
|
public function dnsHosting(): \Illuminate\View\View
|
|
{
|
|
return view('lethean::services-dns');
|
|
}
|
|
|
|
public function residentialProxy(): \Illuminate\View\View
|
|
{
|
|
$result = $this->rpc->getAllAliases();
|
|
$nodes = array_filter($result['aliases'] ?? [], fn ($a) =>
|
|
str_contains($a['comment'] ?? '', 'proxy') || str_contains($a['comment'] ?? '', 'vpn')
|
|
);
|
|
|
|
return view('lethean::services-residential', ['nodes' => array_values($nodes)]);
|
|
}
|
|
|
|
public function mobileProxy(): \Illuminate\View\View
|
|
{
|
|
$result = $this->rpc->getAllAliases();
|
|
$nodes = array_filter($result['aliases'] ?? [], fn ($a) =>
|
|
str_contains($a['comment'] ?? '', 'proxy') || str_contains($a['comment'] ?? '', 'vpn')
|
|
);
|
|
|
|
return view('lethean::services-mobile', ['nodes' => array_values($nodes)]);
|
|
}
|
|
|
|
public function seoTraffic(): \Illuminate\View\View
|
|
{
|
|
$result = $this->rpc->getAllAliases();
|
|
$nodes = array_filter($result['aliases'] ?? [], fn ($a) =>
|
|
str_contains($a['comment'] ?? '', 'proxy') || str_contains($a['comment'] ?? '', 'exit')
|
|
);
|
|
|
|
return view('lethean::services-seo', ['nodes' => array_values($nodes)]);
|
|
}
|
|
|
|
public function status(): \Illuminate\View\View
|
|
{
|
|
$chainInfo = $this->rpc->getInfo();
|
|
$chainHealth = $this->rpc->healthCheck();
|
|
$walletHealth = $this->wallet->healthCheck();
|
|
$liveGateways = $this->registry->liveGateways();
|
|
$pendingClaims = \Mod\Names\Models\NameClaim::pending()->count();
|
|
|
|
$checks = [
|
|
'chain' => [
|
|
'label' => 'Blockchain Daemon',
|
|
'ok' => $chainHealth['status'] !== 'unhealthy',
|
|
'stale' => $chainHealth['stale'] ?? false,
|
|
'detail' => $chainHealth['detail'],
|
|
],
|
|
'wallet' => [
|
|
'label' => 'Registry Wallet',
|
|
'ok' => $walletHealth['status'] === 'healthy',
|
|
'detail' => $walletHealth['detail'],
|
|
],
|
|
'gateways' => [
|
|
'label' => 'Paired Gateways',
|
|
'ok' => true,
|
|
'detail' => count($liveGateways) . ' live',
|
|
],
|
|
'names' => [
|
|
'label' => 'Name Registry',
|
|
'ok' => ! isset($chainInfo['_offline']),
|
|
'detail' => ($chainInfo['alias_count'] ?? 0) . ' registered, ' . $pendingClaims . ' claims pending',
|
|
],
|
|
'consensus' => [
|
|
'label' => 'Consensus',
|
|
'ok' => ! isset($chainInfo['_offline']),
|
|
'detail' => isset($chainInfo['_offline'])
|
|
? 'Unknown'
|
|
: (($chainInfo['pos_allowed'] ?? false) ? 'PoW + PoS (hybrid)' : 'PoW only'),
|
|
],
|
|
];
|
|
|
|
$allOk = ! in_array(false, array_column($checks, 'ok'));
|
|
|
|
return view('lethean::status', [
|
|
'checks' => $checks,
|
|
'allOk' => $allOk,
|
|
'info' => $chainInfo,
|
|
'liveGateways' => $liveGateways,
|
|
'uptime' => $this->uptimeString(),
|
|
]);
|
|
}
|
|
|
|
private function uptimeString(): string
|
|
{
|
|
$started = Cache::get('app.started_at');
|
|
if (! $started) {
|
|
Cache::put('app.started_at', now()->toIso8601String(), 86400 * 365);
|
|
return 'Just started';
|
|
}
|
|
|
|
$diff = now()->diff(\Carbon\Carbon::parse($started));
|
|
$parts = [];
|
|
if ($diff->d > 0) $parts[] = $diff->d . 'd';
|
|
if ($diff->h > 0) $parts[] = $diff->h . 'h';
|
|
$parts[] = $diff->i . 'm';
|
|
|
|
return implode(' ', $parts);
|
|
}
|
|
|
|
public function services(): \Illuminate\View\View
|
|
{
|
|
$result = $this->rpc->getAllAliases();
|
|
$aliases = $result['aliases'] ?? [];
|
|
|
|
$gateways = [];
|
|
$services = [];
|
|
$exits = [];
|
|
|
|
foreach ($aliases as $alias) {
|
|
$comment = $alias['comment'] ?? '';
|
|
if (str_contains($comment, 'type=gateway')) {
|
|
$gateways[] = $alias;
|
|
} elseif (str_contains($comment, 'type=exit')) {
|
|
$exits[] = $alias;
|
|
} elseif (str_contains($comment, 'type=service')) {
|
|
$services[] = $alias;
|
|
}
|
|
}
|
|
|
|
return view('lethean::services', [
|
|
'gateways' => $gateways,
|
|
'services' => $services,
|
|
'exits' => $exits,
|
|
]);
|
|
}
|
|
}
|