diff --git a/app/Mod/Chain/Contracts/HealthCheckable.php b/app/Mod/Chain/Contracts/HealthCheckable.php new file mode 100644 index 0000000..99d4435 --- /dev/null +++ b/app/Mod/Chain/Contracts/HealthCheckable.php @@ -0,0 +1,19 @@ +healthCheck(); + * if ($result['status'] === 'healthy') { ... } + */ +interface HealthCheckable +{ + /** + * @return array{status: string, detail: string, stale?: bool} + */ + public function healthCheck(): array; +} diff --git a/app/Mod/Chain/Services/DaemonRpc.php b/app/Mod/Chain/Services/DaemonRpc.php index 659ce36..01451c5 100644 --- a/app/Mod/Chain/Services/DaemonRpc.php +++ b/app/Mod/Chain/Services/DaemonRpc.php @@ -14,7 +14,7 @@ use Illuminate\Support\Facades\Http; * $info = $rpc->getInfo(); * $block = $rpc->getBlockByHeight(12345); */ -class DaemonRpc implements \Mod\Chain\Contracts\ChainDaemon +class DaemonRpc implements \Mod\Chain\Contracts\ChainDaemon, \Mod\Chain\Contracts\HealthCheckable { private string $endpoint; private int $cacheTtl; @@ -154,4 +154,26 @@ class DaemonRpc implements \Mod\Chain\Contracts\ChainDaemon return ['error' => 'Daemon unreachable', '_offline' => true]; } } + + public function healthCheck(): array + { + $info = $this->getInfo(); + + if (isset($info['_offline'])) { + return ['status' => 'unhealthy', 'detail' => 'Unreachable']; + } + + if (isset($info['_stale'])) { + return [ + 'status' => 'degraded', + 'detail' => 'Stale data — height ' . number_format($info['height'] ?? 0), + 'stale' => true, + ]; + } + + return [ + 'status' => 'healthy', + 'detail' => 'Height ' . number_format($info['height'] ?? 0) . ' — ' . ($info['status'] ?? 'OK'), + ]; + } } diff --git a/app/Mod/Chain/Services/WalletRpc.php b/app/Mod/Chain/Services/WalletRpc.php index f77c5f6..0207da3 100644 --- a/app/Mod/Chain/Services/WalletRpc.php +++ b/app/Mod/Chain/Services/WalletRpc.php @@ -10,7 +10,7 @@ use Illuminate\Support\Facades\Http; * $wallet = app(WalletRpc::class); * $wallet->registerAlias('myname', $address, 'v=lthn1;type=user'); */ -class WalletRpc implements \Mod\Chain\Contracts\ChainWallet +class WalletRpc implements \Mod\Chain\Contracts\ChainWallet, \Mod\Chain\Contracts\HealthCheckable { private string $endpoint; @@ -99,4 +99,19 @@ class WalletRpc implements \Mod\Chain\Contracts\ChainWallet return $data['result'] ?? $data['error'] ?? []; } + + public function healthCheck(): array + { + try { + $balance = $this->getBalance(); + + if (isset($balance['error'])) { + return ['status' => 'unhealthy', 'detail' => 'Unreachable']; + } + + return ['status' => 'healthy', 'detail' => 'Connected']; + } catch (\Throwable $e) { + return ['status' => 'unhealthy', 'detail' => 'Unreachable']; + } + } } diff --git a/app/Website/Lethean/Controllers/HomeController.php b/app/Website/Lethean/Controllers/HomeController.php index 2ccfe08..8876ef5 100644 --- a/app/Website/Lethean/Controllers/HomeController.php +++ b/app/Website/Lethean/Controllers/HomeController.php @@ -159,29 +159,22 @@ class HomeController extends Controller public function status(): \Illuminate\View\View { $chainInfo = $this->rpc->getInfo(); - try { - $walletInfo = $this->wallet->getBalance(); - } catch (\Throwable $e) { - $walletInfo = ['error' => 'Wallet unreachable']; - } + $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' => ! isset($chainInfo['_offline']), - 'stale' => isset($chainInfo['_stale']), - 'detail' => isset($chainInfo['_offline']) - ? 'Unreachable' - : 'Height ' . number_format($chainInfo['height'] ?? 0) . ' — ' . ($chainInfo['status'] ?? 'unknown'), + 'ok' => $chainHealth['status'] !== 'unhealthy', + 'stale' => $chainHealth['stale'] ?? false, + 'detail' => $chainHealth['detail'], ], 'wallet' => [ 'label' => 'Registry Wallet', - 'ok' => ! isset($walletInfo['error']), - 'detail' => isset($walletInfo['error']) - ? 'Unreachable' - : 'Connected', + 'ok' => $walletHealth['status'] === 'healthy', + 'detail' => $walletHealth['detail'], ], 'gateways' => [ 'label' => 'Paired Gateways',