datacenterbingo gets a public page showing whether each datacenter's network answers. The probing lives here, next to the schema and the admin, since the public site only has a read-only role. bin/monitor runs as a second container from the same image. Once a minute it opens one TCP connection to every active target (a handshake or a refusal both count as answered) and folds the results into 5-minute rows per datacenter, plus a per-bucket row for control hosts in Russia, so a lost vantage point isn't mistaken for dark sites. Rows are kept 30 days. Targets are managed at /admin/bingo/monitor; only hosts a public source ties to that one site belong there. Migration 017 also grants the new tables to the bingo role, so the public site can read them without a storage redeploy. Assisted-by: Claude:opus-5-5
60 lines
3.5 KiB
Text
60 lines
3.5 KiB
Text
<%== erb :'admin/_nav', layout: false %>
|
|
<div class="header">
|
|
<h1>Bingo monitor</h1>
|
|
<p class="tagline">
|
|
<a href="/admin/bingo">← bingo</a>
|
|
· last round: <%= @last_round ? "#{@last_round[:bucket_at].strftime('%Y-%m-%d %H:%M')} UTC bucket, controls #{@last_round[:control_ok]}/#{@last_round[:control_checks]}" : 'never (is bin/monitor running?)' %>
|
|
</p>
|
|
</div>
|
|
|
|
<% if @flash %><div class="form-error-base"><div><%= @flash %></div></div><% end %>
|
|
|
|
<% if can?('bingo', 'write') %>
|
|
<section class="section">
|
|
<h2>Add targets</h2>
|
|
<p class="optional">One per line: <code>slug host[:port] source_url label…</code>. Port defaults to 443. Slug <code><%= Bingo::Monitor::CONTROL %></code> adds a control host (in Russia, not in any listed site). Only hosts a public source ties to that one datacenter; one TCP connect per minute each.</p>
|
|
<form class="invoice-form" method="post" action="/admin/bingo/monitor" autocomplete="off">
|
|
<input type="hidden" name="authenticity_token" value="<%= csrf_token %>" />
|
|
<div class="field"><textarea name="targets" rows="6" style="width: 100%; font-family: monospace" placeholder="selectel-dubrovka-1 203.0.113.10:80 https://example.com/test-ips Selectel test IP ru-3"></textarea></div>
|
|
<button type="submit" class="download-button">Add</button>
|
|
</form>
|
|
</section>
|
|
<% end %>
|
|
|
|
<section class="section">
|
|
<h2>Targets (<%= @targets.size %>)</h2>
|
|
<table class="invoice-list">
|
|
<thead><tr><th>Datacenter</th><th>Endpoint</th><th>Label</th><th>Last</th><th class="num">ms</th><th>Since</th><th></th></tr></thead>
|
|
<tbody>
|
|
<% @targets.each do |t| %>
|
|
<tr<%== ' style="opacity: .5"' unless t.active %>>
|
|
<td><%= t.control? ? 'control' : "##{t.datacenter.position} #{t.datacenter.name_ru}" %></td>
|
|
<td class="mono"><%= t.endpoint %></td>
|
|
<td><% if t.source_url %><a href="<%= t.source_url %>" target="_blank" rel="noopener"><%= t.label || 'source' %></a><% else %><%= t.label %><% end %></td>
|
|
<td><%= { true => '✅', false => '❌', nil => '—' }[t.last_ok] %></td>
|
|
<td class="num mono"><%= t.last_rtt_ms %></td>
|
|
<td class="mono"><%= t.last_change_at&.strftime('%m-%d %H:%M') %></td>
|
|
<td class="actions">
|
|
<% if can?('bingo', 'write') %>
|
|
<form method="post" action="/admin/bingo/monitor/<%= t.id %>/toggle">
|
|
<input type="hidden" name="authenticity_token" value="<%= csrf_token %>" />
|
|
<button type="submit" class="link-button"><%= t.active ? 'pause' : 'resume' %></button>
|
|
</form>
|
|
<% end %>
|
|
<% if can?('bingo', 'delete') %>
|
|
<form method="post" action="/admin/bingo/monitor/<%= t.id %>/delete" onsubmit="return confirm('Delete this target?')">
|
|
<input type="hidden" name="authenticity_token" value="<%= csrf_token %>" />
|
|
<button type="submit" class="link-button">delete</button>
|
|
</form>
|
|
<% end %>
|
|
</td>
|
|
</tr>
|
|
<% end %>
|
|
</tbody>
|
|
</table>
|
|
</section>
|
|
|
|
<section class="section">
|
|
<h2>Operational, not monitored (<%= @unmonitored.size %>)</h2>
|
|
<p class="optional"><%= @unmonitored.map { |dc| "##{dc.position} #{dc.slug}" }.join(', ') %></p>
|
|
</section>
|