Diff -- /var/www/vhosts/3dshawn.com/site1/dashboard.cgi

O Operator
Diff

/var/www/vhosts/3dshawn.com/site1/dashboard.cgi

modified on local at 2026-07-10 23:21:52

Added
+16
lines
Removed
-7
lines
Context
164
unchanged
Blobs
from dc2eb4ad0c0f
to d9e3ea458c4f
Restore this content →
Unified diff
Naive LCS-based line diff. Additions in emerald, deletions in rose. Both sides decompressed live from BLOB_STORE.
11#!/usr/bin/perl
22#======================================================================
33# DriftSense -- /dashboard.cgi
44#
55# Landing page. Shows portfolio-wide health at a glance:
66# * KPI tiles: file changes today | schema changes 7d | named releases | drift alerts
77# * Recent activity table (last 30 file + schema changes, merged, newest first)
88# * Named releases strip (last 5 bookmarks)
99# * Servers status strip
1010#======================================================================
1111use strict;
1212use warnings;
1313use CGI ();
1414use MODS::Config;
1515use MODS::DBConnect;
1616use MODS::Template;
1717use MODS::PageWrapper;
1818use MODS::Charts;
1919
2020my $cfg = MODS::Config->new;
2121my $db = MODS::DBConnect->new;
2222my $tpl = MODS::Template->new;
2323my $wrap = MODS::PageWrapper->new;
2424
2525# Headers
2626$|=1;
2727print "Content-Type: text/html; charset=utf-8\nCache-Control: no-cache\n\n";
2828
2929my $dbh = $db->db_connect
3030 or die "DriftSense: cannot connect to storage DB. Check /etc/drift_sense/drift_sense.conf.\n";
3131
3232# ----- KPI queries ----------------------------------------------------
3333my $file_today = $db->db_readwrite($dbh, q~
3434 SELECT COUNT(*) AS n FROM FILE_CHANGES
3535 WHERE date_time >= DATE_SUB(NOW(), INTERVAL 1 DAY)
3636~, __FILE__, __LINE__);
3737
3838my $schema_7d = $db->db_readwrite($dbh, q~
3939 SELECT COUNT(*) AS n FROM SCHEMA_CHANGE
4040 WHERE change_datetime >= DATE_SUB(NOW(), INTERVAL 7 DAY)
4141~, __FILE__, __LINE__);
4242
4343my $releases_ct = $db->db_readwrite($dbh, q~
4444 SELECT COUNT(*) AS n FROM NAMED_RELEASES
4545~, __FILE__, __LINE__);
4646
4747my $drifted_ct = $db->db_readwrite($dbh, q~
4848 SELECT COUNT(*) AS n FROM CONFIG_DRIFT_BASELINES WHERE is_drifted = 1
4949~, __FILE__, __LINE__);
5050
5151# ----- Recent activity (merged file + schema changes, newest first) --
5252my @file_recent = $db->db_readwrite_multiple($dbh, q~
5353 SELECT 'file' AS kind,
5454 file_changes_id AS id,
5555 file_name AS what,
5656 status AS detail,
5757 date_time AS ts,
58 UNIX_TIMESTAMP(date_time) AS ts_epoch,
5859 server_id
5960 FROM FILE_CHANGES
6061 ORDER BY date_time DESC
6162 LIMIT 20
6263~, __FILE__, __LINE__);
6364my @schema_recent = $db->db_readwrite_multiple($dbh, q~
6465 SELECT 'schema' AS kind,
6566 schema_change_id AS id,
6667 CONCAT(database_name, '.', table_name) AS what,
6768 changes AS detail,
6869 change_datetime AS ts,
70 UNIX_TIMESTAMP(change_datetime) AS ts_epoch,
6971 server_id
7072 FROM SCHEMA_CHANGE
7173 ORDER BY change_datetime DESC
7274 LIMIT 20
7375~, __FILE__, __LINE__);
7476my @recent = sort { ($b->{ts} || '') cmp ($a->{ts} || '') } (@file_recent, @schema_recent);
7577@recent = splice(@recent, 0, 20) if @recent > 20;
7678foreach my $r (@recent) {
7779 my $d = $r->{detail} // '';
7880 $d =~ s/\s+/ /g;
7981 $r->{detail} = length($d) > 90 ? substr($d, 0, 87) . '...' : $d;
8082 $r->{what} = length($r->{what} // '') > 60 ? substr($r->{what}, 0, 57) . '...' : $r->{what};
8183 $r->{diff_url} = ($r->{kind} eq 'file'
8284 ? "/diff.cgi?kind=file&id=$r->{id}"
8385 : "/diff.cgi?kind=schema&id=$r->{id}");
84 $r->{ts} //= '';
86 $r->{ts} //= '';
87 $r->{ts_epoch} //= 0;
8588 $r->{kind_pill} = ($r->{kind} eq 'file') ? 'pill-info' : 'pill-warn';
8689}
8790
8891# ----- 90-day activity timeline (stacked bars) -----------------------
8992my $today_dow = 0;
9093my @file_by_day = $db->db_readwrite_multiple($dbh, q~
9194 SELECT DATE_FORMAT(date_time, '%Y-%m-%d') AS d, COUNT(*) AS n
9295 FROM FILE_CHANGES
9396 WHERE date_time >= DATE_SUB(NOW(), INTERVAL 90 DAY)
9497 GROUP BY d
9598~, __FILE__, __LINE__);
9699my @schema_by_day = $db->db_readwrite_multiple($dbh, q~
97100 SELECT DATE_FORMAT(change_datetime, '%Y-%m-%d') AS d, COUNT(*) AS n
98101 FROM SCHEMA_CHANGE
99102 WHERE change_datetime >= DATE_SUB(NOW(), INTERVAL 90 DAY)
100103 GROUP BY d
101104~, __FILE__, __LINE__);
102105my (%file_map, %schema_map);
103106$file_map{$_->{d}} = $_->{n} for @file_by_day;
104107$schema_map{$_->{d}} = $_->{n} for @schema_by_day;
105108
106109use POSIX ();
107my (@days, @file_series, @schema_series);
110my (@days, @day_keys, @file_series, @schema_series);
108111my $today = time;
109112for (my $i = 89; $i >= 0; $i--) {
110113 my $t = $today - ($i * 86400);
111114 my $key = POSIX::strftime('%Y-%m-%d', localtime($t));
112115 my $lbl = POSIX::strftime('%b %d', localtime($t));
113116 push @days, $lbl;
117 push @day_keys, $key;
114118 push @file_series, ($file_map{$key} || 0);
115119 push @schema_series, ($schema_map{$key} || 0);
116120}
117121my $timeline_svg = MODS::Charts::stacked_bars(
118 days => \@days,
119 series => { file => \@file_series, schema => \@schema_series },
120 width => 1080,
121 height => 180,
122 days => \@days,
123 day_keys => \@day_keys,
124 series => { file => \@file_series, schema => \@schema_series },
125 width => 1080,
126 height => 180,
122127);
123128
124129# ----- Named releases strip (last 5) ---------------------------------
125130my @releases = $db->db_readwrite_multiple($dbh, q~
126131 SELECT named_release_id, name, description,
127132 DATE_FORMAT(released_at, '%b %d, %Y') AS released_h,
133 UNIX_TIMESTAMP(released_at) AS released_epoch,
128134 released_by
129135 FROM NAMED_RELEASES
130136 ORDER BY released_at DESC
131137 LIMIT 5
132138~, __FILE__, __LINE__);
139$_->{released_epoch} //= 0 for @releases;
133140
134141# ----- Server status strip -------------------------------------------
135142my @servers = $db->db_readwrite_multiple($dbh, q~
136143 SELECT server_id, server_name, hostname, role, status,
137 DATE_FORMAT(last_heartbeat_at, '%b %d %H:%i') AS last_heartbeat_h
144 DATE_FORMAT(last_heartbeat_at, '%b %d %H:%i') AS last_heartbeat_h,
145 UNIX_TIMESTAMP(last_heartbeat_at) AS heartbeat_epoch
138146 FROM SERVERS
139147 ORDER BY (status='active') DESC, server_id ASC
140148 LIMIT 8
141149~, __FILE__, __LINE__);
142150foreach my $s (@servers) {
143151 $s->{status_pill} = $s->{status} eq 'active' ? 'pill-ok'
144152 : $s->{status} eq 'offline' ? 'pill-bad'
145153 : 'pill-mute';
146154 $s->{last_heartbeat_h} //= 'never';
155 $s->{heartbeat_epoch} //= 0;
147156}
148157
149158$db->db_disconnect($dbh);
150159
151160# ----- Render body ---------------------------------------------------
152161my $tvars = {
153162 kpi_file_today => ($file_today && $file_today->{n}) || 0,
154163 kpi_schema_7d => ($schema_7d && $schema_7d->{n}) || 0,
155164 kpi_releases => ($releases_ct && $releases_ct->{n}) || 0,
156165 kpi_drifted => ($drifted_ct && $drifted_ct->{n}) || 0,
157166 timeline_svg => $timeline_svg,
158167 has_recent => scalar(@recent) ? 1 : 0,
159168 recent => \@recent,
160169 has_releases => scalar(@releases) ? 1 : 0,
161170 releases => \@releases,
162171 servers => \@servers,
163172};
164173my @body = $tpl->template('dashboard.html', $tvars);
165174
166175$wrap->wrapper(
167176 page_title => 'Dashboard',
168177 page_key => 'dashboard',
169178 body_html => join('', @body),
170179 userinfo => { display_name => 'Operator' }, # single-user install; auth added later
171180);