Diff -- /var/www/vhosts/3dshawn.com/site1/site.cgi
Diff
/var/www/vhosts/3dshawn.com/site1/site.cgi
added on local at 2026-07-12 00:48:43
Added
+205
lines
Removed
-0
lines
Context
0
unchanged
Blobs
from
to c4e290a1653d
to c4e290a1653d
Unified diff
Naive LCS-based line diff. Additions in emerald, deletions in rose. Both sides decompressed live from BLOB_STORE.| 1 | #!/usr/bin/perl | |
| 2 | #====================================================================== | |
| 3 | # DriftSense -- /site.cgi?mid=N | |
| 4 | # | |
| 5 | # Focused per-site drilldown. Aimed at the "what's happening in THIS | |
| 6 | # codebase specifically?" question. Programmer view: recent activity, | |
| 7 | # what changed, quick restore. Sysadmin view: activity chart, drift | |
| 8 | # score. Owner view: KPI totals + stable versions. | |
| 9 | #====================================================================== | |
| 10 | use strict; | |
| 11 | use warnings; | |
| 12 | use CGI (); | |
| 13 | use POSIX (); | |
| 14 | use MODS::Config; | |
| 15 | use MODS::DBConnect; | |
| 16 | use MODS::Template; | |
| 17 | use MODS::PageWrapper; | |
| 18 | use MODS::Charts; | |
| 19 | ||
| 20 | my $cgi = CGI->new; | |
| 21 | my $db = MODS::DBConnect->new; | |
| 22 | my $tpl = MODS::Template->new; | |
| 23 | $|=1; | |
| 24 | ||
| 25 | my $mid = int($cgi->param('mid') || 0); | |
| 26 | $mid = int($cgi->param('monitor') || 0) unless $mid; # accept ?monitor= alias | |
| 27 | ||
| 28 | my $dbh = $db->db_connect or die "DB connect failed\n"; | |
| 29 | ||
| 30 | # ---- Monitor + server ---------------------------------------------- | |
| 31 | my $monitor = $mid ? $db->db_readwrite($dbh, qq~ | |
| 32 | SELECT m.file_monitor_list_id AS mid, | |
| 33 | m.scan_name, m.scan_path, m.ignore_list, m.file_type_filter, | |
| 34 | m.max_file_size_bytes, m.retention_days, | |
| 35 | m.server_id, s.server_name, s.kind AS server_kind, | |
| 36 | s.ssh_host, s.ssh_user | |
| 37 | FROM FILE_MONITOR_SETTINGS m | |
| 38 | LEFT JOIN SERVERS s ON s.server_id = m.server_id | |
| 39 | WHERE m.file_monitor_list_id = $mid LIMIT 1 | |
| 40 | ~, __FILE__, __LINE__) : undef; | |
| 41 | ||
| 42 | unless ($monitor && $monitor->{mid}) { | |
| 43 | print "Content-Type: text/html; charset=utf-8\n\n"; | |
| 44 | my @body = $tpl->template('site.html', { | |
| 45 | has_error => 1, | |
| 46 | error_msg => "Monitor #$mid not found.", | |
| 47 | }); | |
| 48 | MODS::PageWrapper->new->wrapper( | |
| 49 | page_title => 'Site', page_key => '', | |
| 50 | body_html => join('', @body), userinfo => { display_name => 'Operator' }, | |
| 51 | ); | |
| 52 | exit; | |
| 53 | } | |
| 54 | ||
| 55 | # ---- KPI counts ---------------------------------------------------- | |
| 56 | my $k_24h = $db->db_readwrite($dbh, qq~ | |
| 57 | SELECT COUNT(*) AS n FROM FILE_CHANGES | |
| 58 | WHERE file_monitor_list_id = $mid | |
| 59 | AND date_time >= DATE_SUB(NOW(), INTERVAL 24 HOUR) | |
| 60 | ~, __FILE__, __LINE__); | |
| 61 | my $k_7d = $db->db_readwrite($dbh, qq~ | |
| 62 | SELECT COUNT(*) AS n FROM FILE_CHANGES | |
| 63 | WHERE file_monitor_list_id = $mid | |
| 64 | AND date_time >= DATE_SUB(NOW(), INTERVAL 7 DAY) | |
| 65 | ~, __FILE__, __LINE__); | |
| 66 | my $k_active = $db->db_readwrite($dbh, qq~ | |
| 67 | SELECT COUNT(DISTINCT file_name) AS n FROM FILE_CHANGES | |
| 68 | WHERE file_monitor_list_id = $mid | |
| 69 | AND status != 'deleted' | |
| 70 | ~, __FILE__, __LINE__); | |
| 71 | my $k_all = $db->db_readwrite($dbh, qq~ | |
| 72 | SELECT COUNT(*) AS n FROM FILE_CHANGES WHERE file_monitor_list_id = $mid | |
| 73 | ~, __FILE__, __LINE__); | |
| 74 | ||
| 75 | # ---- 90-day activity chart ----------------------------------------- | |
| 76 | my @day_rows = $db->db_readwrite_multiple($dbh, qq~ | |
| 77 | SELECT DATE_FORMAT(date_time, '%Y-%m-%d') AS d, COUNT(*) AS n | |
| 78 | FROM FILE_CHANGES | |
| 79 | WHERE file_monitor_list_id = $mid | |
| 80 | AND date_time >= DATE_SUB(NOW(), INTERVAL 90 DAY) | |
| 81 | GROUP BY d | |
| 82 | ~, __FILE__, __LINE__); | |
| 83 | my %by_day; $by_day{$_->{d}} = $_->{n} for @day_rows; | |
| 84 | my (@days, @day_keys, @file_series, @empty_schema); | |
| 85 | my $now = time; | |
| 86 | for (my $i = 89; $i >= 0; $i--) { | |
| 87 | my $t = $now - $i * 86400; | |
| 88 | my $key = POSIX::strftime('%Y-%m-%d', localtime($t)); | |
| 89 | my $lbl = POSIX::strftime('%b %d', localtime($t)); | |
| 90 | push @days, $lbl; push @day_keys, $key; | |
| 91 | push @file_series, ($by_day{$key} || 0); | |
| 92 | push @empty_schema, 0; | |
| 93 | } | |
| 94 | my $timeline_svg = MODS::Charts::stacked_bars( | |
| 95 | days => \@days, day_keys => \@day_keys, | |
| 96 | series => { file => \@file_series, schema => \@empty_schema }, | |
| 97 | width => 900, height => 150, | |
| 98 | link_file => '/file_changes.cgi', | |
| 99 | link_schema => '/schema_changes.cgi', | |
| 100 | ); | |
| 101 | ||
| 102 | # ---- Recent changes for THIS site (last 40) ------------------------ | |
| 103 | my @recent = $db->db_readwrite_multiple($dbh, qq~ | |
| 104 | SELECT fc.file_changes_id AS id, fc.file_name, fc.status, fc.blob_sha, | |
| 105 | fc.is_ts_only, | |
| 106 | UNIX_TIMESTAMP(fc.date_time) AS ts_epoch, | |
| 107 | fc.date_time | |
| 108 | FROM FILE_CHANGES fc | |
| 109 | WHERE fc.file_monitor_list_id = $mid | |
| 110 | ORDER BY fc.file_changes_id DESC | |
| 111 | LIMIT 40 | |
| 112 | ~, __FILE__, __LINE__); | |
| 113 | foreach my $r (@recent) { | |
| 114 | $r->{status_pill} = $r->{status} eq 'added' ? 'pill-ok' | |
| 115 | : $r->{status} eq 'modified' ? 'pill-warn' | |
| 116 | : $r->{status} eq 'deleted' ? 'pill-bad' | |
| 117 | : 'pill-mute'; | |
| 118 | $r->{file_short} = length($r->{file_name} // '') > 60 | |
| 119 | ? '...' . substr($r->{file_name}, -57) : $r->{file_name}; | |
| 120 | $r->{blob_short} = $r->{blob_sha} ? substr($r->{blob_sha}, 0, 8) : '-'; | |
| 121 | $r->{diff_url} = "/diff.cgi?kind=file&id=$r->{id}"; | |
| 122 | $r->{restore_url} = "/restore.cgi?id=$r->{id}"; | |
| 123 | $r->{can_restore} = ($r->{blob_sha} && $r->{status} ne 'deleted') ? 1 : 0; | |
| 124 | $r->{ts_epoch} //= 0; | |
| 125 | } | |
| 126 | ||
| 127 | # ---- Change-frequency for THIS site (top 8) ------------------------- | |
| 128 | my @hotspots = $db->db_readwrite_multiple($dbh, qq~ | |
| 129 | SELECT file_name, | |
| 130 | COUNT(*) AS ct, | |
| 131 | MAX(file_changes_id) AS latest_id, | |
| 132 | MAX(UNIX_TIMESTAMP(date_time)) AS latest_epoch | |
| 133 | FROM FILE_CHANGES | |
| 134 | WHERE file_monitor_list_id = $mid | |
| 135 | AND date_time >= DATE_SUB(NOW(), INTERVAL 30 DAY) | |
| 136 | GROUP BY file_name | |
| 137 | ORDER BY ct DESC LIMIT 8 | |
| 138 | ~, __FILE__, __LINE__); | |
| 139 | foreach my $h (@hotspots) { | |
| 140 | $h->{file_short} = length($h->{file_name} // '') > 55 | |
| 141 | ? '...' . substr($h->{file_name}, -52) : $h->{file_name}; | |
| 142 | $h->{diff_url} = "/diff.cgi?kind=file&id=" . ($h->{latest_id} || 0); | |
| 143 | } | |
| 144 | ||
| 145 | # ---- Named releases scoped to this site ---------------------------- | |
| 146 | my @releases = $db->db_readwrite_multiple($dbh, qq~ | |
| 147 | SELECT nr.named_release_id AS id, nr.name, nr.version_label, nr.tier, | |
| 148 | nr.description, | |
| 149 | UNIX_TIMESTAMP(nr.released_at) AS released_epoch, | |
| 150 | DATE_FORMAT(nr.released_at, '%b %d, %Y') AS released_h | |
| 151 | FROM NAMED_RELEASES nr | |
| 152 | WHERE nr.scope_monitor_id = $mid | |
| 153 | OR nr.scope_server_id = $monitor->{server_id} | |
| 154 | OR nr.scope_monitor_id IS NULL | |
| 155 | ORDER BY (nr.tier = 'stable') DESC, (nr.tier = 'working') DESC, nr.named_release_id DESC | |
| 156 | LIMIT 10 | |
| 157 | ~, __FILE__, __LINE__); | |
| 158 | foreach my $r (@releases) { | |
| 159 | $r->{tier} //= 'draft'; | |
| 160 | $r->{tier_pill} = $r->{tier} eq 'stable' ? 'pill-ok' | |
| 161 | : $r->{tier} eq 'working' ? 'pill-info' | |
| 162 | : 'pill-mute'; | |
| 163 | $r->{label} = $r->{version_label} || $r->{name}; | |
| 164 | $r->{restore_url} = "/restore_release.cgi?release_id=$r->{id}"; | |
| 165 | $r->{released_epoch} //= 0; | |
| 166 | } | |
| 167 | ||
| 168 | $db->db_disconnect($dbh); | |
| 169 | ||
| 170 | my $tvars = { | |
| 171 | mid => $mid, | |
| 172 | scan_name => $monitor->{scan_name}, | |
| 173 | scan_path => $monitor->{scan_path}, | |
| 174 | server_name => $monitor->{server_name} // 'local', | |
| 175 | server_kind => $monitor->{server_kind} // 'local', | |
| 176 | ssh_host => $monitor->{ssh_host} // '', | |
| 177 | ssh_user => $monitor->{ssh_user} // '', | |
| 178 | retention_h => defined($monitor->{retention_days}) && $monitor->{retention_days} | |
| 179 | ? "$monitor->{retention_days} days" | |
| 180 | : '(global default)', | |
| 181 | kpi_24h => ($k_24h && $k_24h->{n}) || 0, | |
| 182 | kpi_7d => ($k_7d && $k_7d->{n}) || 0, | |
| 183 | kpi_active => ($k_active && $k_active->{n}) || 0, | |
| 184 | kpi_all => ($k_all && $k_all->{n}) || 0, | |
| 185 | timeline_svg => $timeline_svg, | |
| 186 | recent => \@recent, | |
| 187 | has_recent => scalar(@recent) ? 1 : 0, | |
| 188 | hotspots => \@hotspots, | |
| 189 | has_hotspots => scalar(@hotspots) ? 1 : 0, | |
| 190 | releases => \@releases, | |
| 191 | has_releases => scalar(@releases) ? 1 : 0, | |
| 192 | changes_url => "/file_changes.cgi?monitor=$mid", | |
| 193 | pin_url => "/pin_hotspots.cgi?monitor=$mid", | |
| 194 | export_url => "/export.cgi?server_id=$monitor->{server_id}", | |
| 195 | }; | |
| 196 | ||
| 197 | print "Content-Type: text/html; charset=utf-8\nCache-Control: no-cache\n\n"; | |
| 198 | my @body = $tpl->template('site.html', $tvars); | |
| 199 | MODS::PageWrapper->new->wrapper( | |
| 200 | page_title => "Site: " . ($monitor->{scan_name} // '?'), | |
| 201 | page_key => '', | |
| 202 | active_mid => $mid, | |
| 203 | body_html => join('', @body), | |
| 204 | userinfo => { display_name => 'Operator' }, | |
| 205 | ); |