@@ -23,6 +23,12 @@ function env_bool($name, $default) {
2323 'theme ' => getenv ('THEME ' ) ?: 'dark ' , // Default theme: dark, light, nord, solarized, or dracula
2424 'show_theme_switcher ' => env_bool ('SHOW_THEME_SWITCHER ' , true ), // Set false to lock the theme above and hide the dropdown
2525 'refresh_seconds ' => getenv ('REFRESH_SECONDS ' ) !== false ? (int ) getenv ('REFRESH_SECONDS ' ) : 60 , // Auto-refresh interval in seconds (0 disables)
26+ 'enable_json ' => env_bool ('ENABLE_JSON ' , true ), // Set false to disable the ?format=json API (e.g. for public hosts)
27+ // "Connect to this node" box, for advertising a public node. These are operator-provided
28+ // public details (not the internal RPC settings above), and only show when an address is set.
29+ 'show_connect ' => env_bool ('SHOW_CONNECT ' , false ), // Show the "Connect to this Node" box
30+ 'connect_address ' => getenv ('CONNECT_ADDRESS ' ) ?: '' , // Public address to advertise, e.g. node.example.com:18089
31+ 'connect_note ' => getenv ('CONNECT_NOTE ' ) ?: '' , // Optional note shown in the box (e.g. "Restricted RPC" or a Tor address)
2632];
2733
2834$ network = strtoupper ($ dashboard_config ['network ' ]);
@@ -31,6 +37,10 @@ function env_bool($name, $default) {
3137$ theme = $ dashboard_config ['theme ' ] ?? 'dark ' ;
3238$ show_theme_switcher = $ dashboard_config ['show_theme_switcher ' ] ?? true ;
3339$ refresh_seconds = max (0 , (int ) ($ dashboard_config ['refresh_seconds ' ] ?? 60 ));
40+ $ enable_json = $ dashboard_config ['enable_json ' ] ?? true ;
41+ $ show_connect = ($ dashboard_config ['show_connect ' ] ?? false ) && ($ dashboard_config ['connect_address ' ] ?? '' ) !== '' ;
42+ $ connect_address = $ dashboard_config ['connect_address ' ] ?? '' ;
43+ $ connect_note = $ dashboard_config ['connect_note ' ] ?? '' ;
3444
3545$ errors = [];
3646
@@ -422,7 +432,68 @@ function format_hashrate($hashps) {
422432 $ errors [] = 'Failed to fetch Monero fee estimates. ' ;
423433 }
424434 }
425-
435+
436+ }
437+
438+
439+ // Machine-readable snapshot for JSON consumers (Home Assistant, Grafana, scripts, etc.).
440+ // Request it with ?format=json. Values are raw/unformatted for easy parsing. The `?? null`
441+ // guards keep it warning-free when a section is disabled or its RPC call failed.
442+ $ stats = [
443+ 'network ' => $ network ,
444+ 'coin ' => $ coin ,
445+ 'unit ' => $ profile ['unit ' ],
446+ 'family ' => $ family ,
447+ 'updated ' => time (),
448+ ];
449+
450+ if ($ family === 'bitcoin ' ) {
451+ $ stats += [
452+ 'version ' => $ netinfo ['subversion ' ] ?? null ,
453+ 'chain ' => $ blockinfo ['chain ' ] ?? null ,
454+ 'pruned ' => $ blockinfo ['pruned ' ] ?? null ,
455+ 'connections ' => $ netinfo ['connections ' ] ?? null ,
456+ 'block_height ' => $ blockinfo ['blocks ' ] ?? null ,
457+ 'headers ' => $ blockinfo ['headers ' ] ?? null ,
458+ 'verification_progress ' => $ blockinfo ['verificationprogress ' ] ?? null ,
459+ 'size_on_disk ' => $ blockinfo ['size_on_disk ' ] ?? null ,
460+ 'mempool_txns ' => $ mempoolinfo ['size ' ] ?? null ,
461+ 'mempool_bytes ' => $ mempoolinfo ['bytes ' ] ?? null ,
462+ 'mempool_total_fee ' => $ mempoolinfo ['total_fee ' ] ?? null ,
463+ 'difficulty ' => $ blockinfo ['difficulty ' ] ?? null ,
464+ 'network_hashps ' => $ mininginfo ['networkhashps ' ] ?? null ,
465+ 'total_transactions ' => $ chaintxstats ['txcount ' ] ?? null ,
466+ 'tx_rate ' => $ chaintxstats ['txrate ' ] ?? null ,
467+ 'window_tx_count ' => $ chaintxstats ['window_tx_count ' ] ?? null ,
468+ 'fees_sat_vb ' => ['fast ' => $ fastfees ?? null , 'medium ' => $ mediumfees ?? null , 'slow ' => $ slowfees ?? null ],
469+ ];
470+ } elseif ($ family === 'monero ' ) {
471+ $ stats += [
472+ 'version ' => $ getinfo ['version ' ] ?? null ,
473+ 'chain ' => $ getinfo ['nettype ' ] ?? null ,
474+ 'connections ' => isset ($ getinfo ['incoming_connections_count ' ], $ getinfo ['outgoing_connections_count ' ])
475+ ? $ getinfo ['incoming_connections_count ' ] + $ getinfo ['outgoing_connections_count ' ] : null ,
476+ 'block_height ' => $ getinfo ['height ' ] ?? ($ block_count ['count ' ] ?? null ),
477+ 'synchronized ' => $ getinfo ['synchronized ' ] ?? null ,
478+ 'database_size ' => $ getinfo ['database_size ' ] ?? null ,
479+ 'mempool_txns ' => $ mempoolinfo ['pool_stats ' ]['txs_total ' ] ?? null ,
480+ 'mempool_bytes ' => $ mempoolinfo ['pool_stats ' ]['bytes_total ' ] ?? null ,
481+ 'mempool_total_fee ' => isset ($ mempoolinfo ['pool_stats ' ]['fee_total ' ]) ? $ mempoolinfo ['pool_stats ' ]['fee_total ' ] / 1e12 : null ,
482+ 'difficulty ' => $ getinfo ['difficulty ' ] ?? null ,
483+ 'total_transactions ' => $ getinfo ['tx_count ' ] ?? null ,
484+ 'supply ' => isset ($ mininginfo ['already_generated_coins ' ]) ? $ mininginfo ['already_generated_coins ' ] / 1e12 : null ,
485+ 'fees_per_kb ' => $ getfees ['fees ' ] ?? null ,
486+ ];
487+ }
488+
489+ if (!empty ($ errors )) {
490+ $ stats ['errors ' ] = $ errors ;
491+ }
492+
493+ if ($ enable_json && ($ _GET ['format ' ] ?? '' ) === 'json ' ) {
494+ header ('Content-Type: application/json ' );
495+ echo json_encode ($ stats , JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES );
496+ exit ;
426497}
427498
428499?>
@@ -582,6 +653,19 @@ function format_hashrate($hashps) {
582653 text-underline-offset: 3px;
583654 }
584655
656+ /* The "Connect to this Node" box spans the full width as a banner above the grid */
657+ .section.connect-box {
658+ flex: 0 0 100%;
659+ max-width: 100%;
660+ text-align: center;
661+ }
662+
663+ .section.connect-box .stat-row {
664+ justify-content: center;
665+ gap: 0.5rem;
666+ border-bottom: none;
667+ }
668+
585669 .dashboard-header {
586670 position: fixed;
587671 top: 0;
@@ -689,6 +773,16 @@ function setTheme(theme) {
689773 <div id="dashboard-content">
690774 <div class="compact-dashboard<?= $ show_theme_switcher ? '' : ' no-switcher ' ?> ">
691775
776+ <?php if ($ show_connect ): ?>
777+ <div class="section connect-box">
778+ <div class="section-title">Connect to this Node</div>
779+ <div class="stat-row"><span title="The public address to point your wallet or client at.">Address</span><span><?= htmlspecialchars ($ connect_address ) ?> </span></div>
780+ <?php if ($ connect_note !== '' ): ?>
781+ <div class="stat-row"><span title="Additional connection details from the node operator.">Note</span><span><?= htmlspecialchars ($ connect_note ) ?> </span></div>
782+ <?php endif ; ?>
783+ </div>
784+ <?php endif ; ?>
785+
692786 <?php if ($ dashboard_config ['show_node_info ' ]): ?>
693787 <div class="section">
694788 <div class="section-title">Node Info</div>
0 commit comments