bingo: add a reachability monitor for the status page
All checks were successful
Build and Deploy to Production / test (push) Successful in 26s
Build and Deploy to Production / build (push) Successful in 1m11s
Build and Deploy to Production / deploy (push) Successful in 37s

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
This commit is contained in:
Sergei Poljanski 2026-10-18 10:31:00 +04:00
commit 0cc3e1dece
Signed by: asxpi
GPG key ID: 4F8851660FA4121B
10 changed files with 415 additions and 1 deletions

View file

@ -226,6 +226,46 @@ module BingoAdmin
redirect '/admin/bingo'
end
# Reachability targets for the public status page (Bingo::Monitor).
app.get '/admin/bingo/monitor' do
@page_title = 'Monitor · bingo admin'
@noindex = true
@targets = Bingo::Monitor::Target.eager(:datacenter).all
.sort_by { |t| [t.datacenter&.position || 0, t.host, t.port] }
@unmonitored = Bingo::Datacenter.where(status: 'operational')
.exclude(id: Bingo::Monitor::Target.exclude(datacenter_id: nil).select(:datacenter_id))
.order(:position).all
@last_round = DB.connection[:bingo_monitor_buckets].order(Sequel.desc(:bucket_at)).first
@flash = session.delete(:bingo_flash)
erb :'admin/bingo/monitor'
end
app.post '/admin/bingo/monitor' do
attrs, errors = Bingo::Monitor.parse(params[:targets])
if errors.empty?
attrs.each { |a| Bingo::Monitor::Target.create(a) }
audit!('bingo.monitor_add', nil, "#{attrs.size} target(s)")
session[:bingo_flash] = "Added #{attrs.size} target(s)."
else
session[:bingo_flash] = "Nothing added: #{errors.join('; ')}"
end
redirect '/admin/bingo/monitor'
end
app.post '/admin/bingo/monitor/:id/toggle' do
t = Bingo::Monitor::Target[params[:id].to_i] or halt 404
t.update(active: !t.active)
audit!('bingo.monitor_toggle', t.endpoint, t.active ? 'on' : 'off')
redirect '/admin/bingo/monitor'
end
app.post '/admin/bingo/monitor/:id/delete' do
t = Bingo::Monitor::Target[params[:id].to_i] or halt 404
t.destroy
audit!('bingo.monitor_delete', t.endpoint, t.datacenter&.slug)
redirect '/admin/bingo/monitor'
end
app.post '/admin/bingo/incidents/:id/delete' do
inc = Bingo::Incident[params[:id].to_i]
inc&.destroy # hook bumps the datacenter's updated_at