159 lines
8.3 KiB
PHP
159 lines
8.3 KiB
PHP
<?php
|
|
/**
|
|
* C-Relay-PG Admin — Dashboard.
|
|
* Auto-refreshes every 10 seconds via AJAX polling of api/status.php.
|
|
*/
|
|
require_once __DIR__ . '/lib/helpers.php';
|
|
|
|
$pdo = db();
|
|
|
|
// Service state
|
|
$state = $pdo->query("SELECT * FROM caching_service_state WHERE id = 1")->fetch() ?: [];
|
|
|
|
// Active backfill target (table may not exist yet on fresh start)
|
|
$active = ['active_pubkey' => '', 'active_relay' => ''];
|
|
try {
|
|
$active = $pdo->query("SELECT active_pubkey, active_relay FROM caching_backfill_active WHERE id = 1")->fetch() ?: $active;
|
|
} catch (PDOException $e) {}
|
|
|
|
// Error relay summary
|
|
$error_stats = ['auto_completed' => 0, 'error_count' => 0, 'timeout_count' => 0, 'incomplete' => 0, 'total' => 0];
|
|
try {
|
|
$error_stats = $pdo->query("
|
|
SELECT
|
|
COUNT(*) FILTER (WHERE consecutive_errors >= 3) AS auto_completed,
|
|
COUNT(*) FILTER (WHERE last_status LIKE 'error%') AS error_count,
|
|
COUNT(*) FILTER (WHERE last_status = 'timeout') AS timeout_count,
|
|
COUNT(*) FILTER (WHERE complete = false) AS incomplete,
|
|
COUNT(*) AS total
|
|
FROM caching_backfill_relay_progress
|
|
")->fetch() ?: $error_stats;
|
|
} catch (PDOException $e) {}
|
|
|
|
// Inbox stats
|
|
$inbox = $pdo->query("
|
|
SELECT
|
|
COUNT(*) AS pending,
|
|
COUNT(*) FILTER (WHERE source_class = 'live') AS live,
|
|
COUNT(*) FILTER (WHERE source_class = 'backfill') AS backfill,
|
|
COALESCE(EXTRACT(EPOCH FROM NOW())::BIGINT - MIN(received_at), 0) AS oldest_age
|
|
FROM caching_event_inbox
|
|
")->fetch() ?: ['pending' => 0, 'live' => 0, 'backfill' => 0, 'oldest_age' => 0];
|
|
|
|
// Event counts
|
|
$event_stats = $pdo->query("SELECT COUNT(*) AS total, COUNT(DISTINCT pubkey) AS authors FROM events")->fetch() ?: ['total' => 0, 'authors' => 0];
|
|
$kind_stats = $pdo->query("SELECT kind, COUNT(*) AS cnt FROM events GROUP BY kind ORDER BY cnt DESC LIMIT 10")->fetchAll();
|
|
|
|
admin_header('dashboard', 'C-Relay-PG Admin — Dashboard');
|
|
?>
|
|
|
|
<div class="section">
|
|
<div class="section-header">CACHING SERVICE STATUS</div>
|
|
<div class="cards" id="dashboard-cards">
|
|
<div class="card">
|
|
<div class="label">Service State</div>
|
|
<div class="value" id="card-state"><?= e($state['service_state'] ?? 'unknown') ?></div>
|
|
<div class="sub" id="card-heartbeat">Heartbeat: <?= time_ago(intval($state['heartbeat_at'] ?? 0)) ?></div>
|
|
</div>
|
|
<div class="card">
|
|
<div class="label">Followed Authors</div>
|
|
<div class="value" id="card-follows"><?= intval($state['followed_author_count'] ?? 0) ?></div>
|
|
<div class="sub" id="card-relays"><?= intval($state['selected_relay_count'] ?? 0) ?> relays, <?= intval($state['connected_relay_count'] ?? 0) ?> connected</div>
|
|
</div>
|
|
<div class="card">
|
|
<div class="label">Backfill Progress</div>
|
|
<div class="value" id="card-backfill"><?= intval($state['backfill_authors_complete'] ?? 0) ?> / <?= intval($state['backfill_authors_total'] ?? 0) ?></div>
|
|
<div class="sub" id="card-backfill-pct"><?= ($state['backfill_authors_total'] ?? 0) > 0 ? round(intval($state['backfill_authors_complete'] ?? 0) / intval($state['backfill_authors_total']) * 100) : 0 ?>% complete</div>
|
|
</div>
|
|
<div class="card">
|
|
<div class="label">Events Fetched</div>
|
|
<div class="value" id="card-events"><?= number_format(intval($state['events_fetched'] ?? 0)) ?></div>
|
|
<div class="sub" id="card-inserts"><?= number_format(intval($state['inbox_inserts'] ?? 0)) ?> inbox inserts</div>
|
|
</div>
|
|
<div class="card">
|
|
<div class="label">Events in DB</div>
|
|
<div class="value" id="card-db-events"><?= number_format(intval($event_stats['total'] ?? 0)) ?></div>
|
|
<div class="sub"><?= number_format(intval($event_stats['authors'] ?? 0)) ?> authors</div>
|
|
</div>
|
|
<div class="card">
|
|
<div class="label">Inbox Pending</div>
|
|
<div class="value" id="card-inbox"><?= number_format(intval($inbox['pending'] ?? 0)) ?></div>
|
|
<div class="sub" id="card-inbox-detail"><?= intval($inbox['live'] ?? 0) ?> live, <?= intval($inbox['backfill'] ?? 0) ?> backfill</div>
|
|
</div>
|
|
<div class="card">
|
|
<div class="label">Error Relays</div>
|
|
<div class="value status-error" id="card-errors"><?= intval($error_stats['error_count'] ?? 0) ?></div>
|
|
<div class="sub" id="card-errors-detail"><?= intval($error_stats['auto_completed'] ?? 0) ?> auto-completed</div>
|
|
</div>
|
|
<div class="card">
|
|
<div class="label">Config Generation</div>
|
|
<div class="value" id="card-gen"><?= intval($state['config_generation'] ?? 0) ?></div>
|
|
<div class="sub">Updated <?= time_ago(intval($state['updated_at'] ?? 0)) ?></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<div class="section-header">ACTIVE BACKFILL TARGET</div>
|
|
<div id="active-target" style="text-align:center;padding:10px">
|
|
<?php if (!empty($active['active_pubkey'])): ?>
|
|
<p class="status-working">⚡ Working on: <span class="pubkey"><?= e(trunc($active['active_pubkey'], 16)) ?></span> @ <?= e($active['active_relay'] ?? '') ?></p>
|
|
<?php else: ?>
|
|
<p class="status-complete">✓ Caching complete — no active backfill target</p>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<div class="section-header">TOP EVENT KINDS</div>
|
|
<div class="config-table-container">
|
|
<table class="config-table">
|
|
<thead><tr><th>Kind</th><th>Count</th></tr></thead>
|
|
<tbody id="kind-table">
|
|
<?php foreach ($kind_stats as $ks): ?>
|
|
<tr><td><?= intval($ks['kind']) ?></td><td><?= number_format(intval($ks['cnt'])) ?></td></tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
const REFRESH_MS = <?= cfg('refresh_ms', 10000) ?>;
|
|
async function refreshDashboard() {
|
|
console.log('[dashboard] polling api/status.php at', new Date().toLocaleTimeString());
|
|
try {
|
|
const res = await fetch('api/status.php');
|
|
if (!res.ok) { console.warn('[dashboard] status.php returned', res.status); return; }
|
|
const d = await res.json();
|
|
console.log('[dashboard] refreshed at', new Date().toLocaleTimeString(), '— state:', d.service_state, 'follows:', d.followed_count, 'events:', d.db_events);
|
|
const set = (id, val) => { const el = document.getElementById(id); if (el) el.textContent = val; };
|
|
set('card-state', d.service_state || 'unknown');
|
|
set('card-heartbeat', 'Heartbeat: ' + (d.heartbeat_ago || 'never'));
|
|
set('card-follows', d.followed_count ?? 0);
|
|
set('card-relays', (d.selected_relays ?? 0) + ' relays, ' + (d.connected_relays ?? 0) + ' connected');
|
|
set('card-backfill', (d.bf_complete ?? 0) + ' / ' + (d.bf_total ?? 0));
|
|
set('card-backfill-pct', (d.bf_total > 0 ? Math.round(d.bf_complete / d.bf_total * 100) : 0) + '% complete');
|
|
set('card-events', (d.events_fetched ?? 0).toLocaleString());
|
|
set('card-inserts', (d.inbox_inserts ?? 0).toLocaleString() + ' inbox inserts');
|
|
set('card-db-events', (d.db_events ?? 0).toLocaleString());
|
|
set('card-inbox', (d.inbox_pending ?? 0).toLocaleString());
|
|
set('card-inbox-detail', (d.inbox_live ?? 0) + ' live, ' + (d.inbox_backfill ?? 0) + ' backfill');
|
|
set('card-errors', d.error_count ?? 0);
|
|
set('card-errors-detail', (d.auto_completed ?? 0) + ' auto-completed');
|
|
set('card-gen', d.config_generation ?? 0);
|
|
const atEl = document.getElementById('active-target');
|
|
if (d.active_pubkey && d.active_relay) {
|
|
atEl.innerHTML = '<p class="status-working">⚡ Working on: <span class="pubkey">' + d.active_pubkey.substring(0,16) + '…</span> @ ' + d.active_relay + '</p>';
|
|
} else if (d.active_pubkey) {
|
|
atEl.innerHTML = '<p class="status-working">⚡ Working on: <span class="pubkey">' + d.active_pubkey.substring(0,16) + '…</span></p>';
|
|
} else {
|
|
atEl.innerHTML = '<p class="status-complete">✓ Caching complete — no active backfill target</p>';
|
|
}
|
|
} catch (e) { console.error('[dashboard] refresh error:', e); }
|
|
}
|
|
setInterval(refreshDashboard, REFRESH_MS);
|
|
console.log('[dashboard] auto-refresh started, interval:', REFRESH_MS, 'ms');
|
|
</script>
|
|
<?php admin_footer(); ?>
|