lthn.io/app/Website/Lethean/Controllers/HomeController.php
Claude 774e9be207
feat: /status page with live system health checks
Shows blockchain daemon, wallet, gateways, name registry, and consensus
status with green/amber indicators. Chain stats, hardfork status, and
gateway details. Footer link added.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 08:33:14 +01:00

256 lines
8.3 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();
$walletInfo = $this->wallet->getBalance();
$liveGateways = $this->registry->liveGateways();
$claims = Cache::get('name_claims', []);
$pendingClaims = count(array_filter($claims, fn ($c) => $c['status'] === 'pending'));
$checks = [
'chain' => [
'label' => 'Blockchain Daemon',
'ok' => ! isset($chainInfo['_offline']),
'stale' => isset($chainInfo['_stale']),
'detail' => isset($chainInfo['_offline'])
? 'Unreachable'
: 'Height ' . number_format($chainInfo['height'] ?? 0) . ' — ' . ($chainInfo['status'] ?? 'unknown'),
],
'wallet' => [
'label' => 'Registry Wallet',
'ok' => ! isset($walletInfo['error']),
'detail' => isset($walletInfo['error'])
? 'Unreachable'
: 'Connected',
],
'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,
]);
}
}