added on local at 2026-07-01 21:47:19
| 1 | #!/usr/bin/perl | |
| 2 | #====================================================================== | |
| 3 | # RePricer -- public system status page. | |
| 4 | # | |
| 5 | # /status.cgi | |
| 6 | # | |
| 7 | # Renders a minimal "all systems operational" board with checks for: | |
| 8 | # - Web app reachable (the fact we rendered this page = yes) | |
| 9 | # - DB reachable (1=1 query) | |
| 10 | # - Reprice worker: last run within last 20 min | |
| 11 | # - Notifications worker: last run within last 30 min | |
| 12 | # - Marketplace API success rate per platform (last 1h) | |
| 13 | # | |
| 14 | # No auth required -- this is meant to be shareable and bookmarkable. | |
| 15 | #====================================================================== | |
| 16 | use strict; | |
| 17 | use warnings; | |
| 18 | ||
| 19 | use lib '/var/www/vhosts/3dshawn.com/repricer.3dshawn.com'; | |
| 20 | use CGI; | |
| 21 | use MODS::DBConnect; | |
| 22 | use MODS::Login; | |
| 23 | use MODS::RePricer::Config; | |
| 24 | use MODS::RePricer::Wrapper; | |
| 25 | ||
| 26 | $|=1; | |
| 27 | my $auth = MODS::Login->new; | |
| 28 | my $wrap = MODS::RePricer::Wrapper->new; | |
| 29 | my $db = MODS::DBConnect->new; | |
| 30 | my $cfg = MODS::RePricer::Config->new; | |
| 31 | my $DB = $cfg->settings('database_name'); | |
| 32 | ||
| 33 | my $userinfo = $auth->login_verify(); # may be undef -- that's fine | |
| 34 | ||
| 35 | sub _h { my $s = shift // ''; $s =~ s/&/&/g; $s =~ s/</</g; $s =~ s/>/>/g; $s =~ s/"/"/g; $s } | |
| 36 | ||
| 37 | my @checks; | |
| 38 | my $dbh; | |
| 39 | my $db_ok = 0; | |
| 40 | eval { | |
| 41 | $dbh = $db->db_connect(); | |
| 42 | my $r = $db->db_readwrite($dbh, "SELECT 1 AS one", $ENV{SCRIPT_NAME}, __LINE__); | |
| 43 | $db_ok = ($r && $r->{one} == 1) ? 1 : 0; | |
| 44 | }; | |
| 45 | push @checks, { label => 'Database', ok => $db_ok, | |
| 46 | detail => $db_ok ? 'Read/write OK' : 'Cannot reach MariaDB' }; | |
| 47 | ||
| 48 | push @checks, { label => 'Web app', ok => 1, detail => 'You are reading this page.' }; | |
| 49 | ||
| 50 | # Reprice worker freshness | |
| 51 | my $rw_ok = 0; my $rw_detail = 'No runs recorded'; | |
| 52 | if ($db_ok) { | |
| 53 | my $r = $db->db_readwrite($dbh, qq~ | |
| 54 | SELECT TIMESTAMPDIFF(MINUTE, MAX(started_at), NOW()) AS mins, | |
| 55 | IFNULL(SUM(errors_count),0) AS errs | |
| 56 | FROM `${DB}`.reprice_runs | |
| 57 | WHERE started_at >= DATE_SUB(NOW(), INTERVAL 24 HOUR) | |
| 58 | ~, $ENV{SCRIPT_NAME}, __LINE__); | |
| 59 | if ($r && defined $r->{mins}) { | |
| 60 | if ($r->{mins} <= 20) { | |
| 61 | $rw_ok = 1; | |
| 62 | $rw_detail = "Last run $r->{mins} min ago" . ($r->{errs} ? " ($r->{errs} errors in 24h)" : ''); | |
| 63 | } else { | |
| 64 | $rw_detail = "Last run $r->{mins} min ago -- worker may be stuck"; | |
| 65 | } | |
| 66 | } | |
| 67 | } | |
| 68 | push @checks, { label => 'Reprice worker', ok => $rw_ok, detail => $rw_detail }; | |
| 69 | ||
| 70 | # Marketplace API health (1h success rate, per platform) | |
| 71 | my %mp_ok; my %mp_detail; | |
| 72 | foreach my $plat (qw(amazon ebay walmart)) { | |
| 73 | $mp_ok{$plat} = 1; $mp_detail{$plat} = 'No recent calls'; | |
| 74 | if ($db_ok) { | |
| 75 | my $r = $db->db_readwrite($dbh, qq~ | |
| 76 | SELECT COUNT(*) AS total, | |
| 77 | SUM(CASE WHEN ok=1 THEN 1 ELSE 0 END) AS ok_n | |
| 78 | FROM `${DB}`.marketplace_call_log | |
| 79 | WHERE platform='$plat' AND created_at >= DATE_SUB(NOW(), INTERVAL 1 HOUR) | |
| 80 | ~, $ENV{SCRIPT_NAME}, __LINE__); | |
| 81 | if ($r && $r->{total}) { | |
| 82 | my $rate = int(($r->{ok_n} / $r->{total}) * 100); | |
| 83 | $mp_ok{$plat} = $rate >= 90 ? 1 : 0; | |
| 84 | $mp_detail{$plat} = "$rate% OK across $r->{total} calls"; | |
| 85 | } | |
| 86 | } | |
| 87 | push @checks, { label => ucfirst($plat) . ' API', ok => $mp_ok{$plat}, | |
| 88 | detail => $mp_detail{$plat} }; | |
| 89 | } | |
| 90 | ||
| 91 | $db->db_disconnect($dbh) if $dbh; | |
| 92 | ||
| 93 | my $all_ok = 1; foreach (@checks) { $all_ok = 0 unless $_->{ok}; } | |
| 94 | my $overall = $all_ok ? 'All systems operational' : 'Some systems degraded'; | |
| 95 | my $color = $all_ok ? '#10b981' : '#fbbf24'; | |
| 96 | ||
| 97 | my $body = '<div style="max-width:760px;margin:0 auto;padding:24px 28px">'; | |
| 98 | $body .= ' <h1 style="font-size:28px;color:#fff;margin:0 0 6px;font-weight:700">RePricer status</h1>'; | |
| 99 | $body .= ' <p style="color:#7e92b6;font-size:14px;margin:0 0 18px">Live snapshot of the platform's health. Auto-updates every minute (refresh the page).</p>'; | |
| 100 | $body .= qq~<div style="background:${color}1a;border-left:4px solid $color;color:$color;padding:18px 22px;border-radius:8px;margin-bottom:18px;font-size:18px;font-weight:700">$overall</div>~; | |
| 101 | $body .= '<section style="background:#0b1226;border:1px solid #1f2a4a;border-radius:12px;overflow:hidden">'; | |
| 102 | foreach my $c (@checks) { | |
| 103 | my $col = $c->{ok} ? '#10b981' : '#fca5a5'; | |
| 104 | my $label = $c->{ok} ? 'Operational' : 'Issue detected'; | |
| 105 | $body .= qq~ <div style="display:flex;align-items:center;gap:12px;padding:14px 18px;border-top:1px solid #1f2a4a"> | |
| 106 | <span style="width:10px;height:10px;border-radius:50%;background:$col;box-shadow:0 0 8px $col"></span> | |
| 107 | <div style="flex:1"> | |
| 108 | <div style="color:#e6ecf6;font-size:14px;font-weight:600">~ . _h($c->{label}) . qq~</div> | |
| 109 | <div style="color:#7e92b6;font-size:12px;margin-top:2px">~ . _h($c->{detail}) . qq~</div> | |
| 110 | </div> | |
| 111 | <div style="color:$col;font-size:12px;font-weight:600">$label</div> | |
| 112 | </div>~; | |
| 113 | } | |
| 114 | $body .= '</section>'; | |
| 115 | $body .= '<div style="margin-top:18px;font-size:12px;color:#7e92b6;text-align:center">Generated at ' . scalar(gmtime) . ' UTC</div>'; | |
| 116 | $body .= '</div>'; | |
| 117 | ||
| 118 | print "Content-Type: text/html; charset=utf-8\nCache-Control: no-store, no-cache, must-revalidate, max-age=0\nPragma: no-cache\nExpires: 0\n\n"; | |
| 119 | $wrap->render({ userinfo => $userinfo, title => 'System status', body => $body }); | |
| 120 | exit; |