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

O Operator
Diff

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

modified on local at 2026-07-12 01:04:49

Added
+352
lines
Removed
-66
lines
Context
139
unchanged
Blobs
from c4e290a1653d
to a339c7d2fcb0
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 -- /site.cgi?mid=N
44#
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.
5# The awesome-detail drilldown for one monitored site. Every stat we
6# have. Server info, storage math, activity breakdown, compile summary,
7# hotspots, releases scoped here, cross-site dedup, recent restores,
8# pin drift, deletion log, and quick actions.
99#======================================================================
1010use strict;
1111use warnings;
1212use CGI ();
1313use POSIX ();
1414use MODS::Config;
1515use MODS::DBConnect;
1616use MODS::Template;
1717use MODS::PageWrapper;
1818use MODS::Charts;
1919
2020my $cgi = CGI->new;
2121my $db = MODS::DBConnect->new;
2222my $tpl = MODS::Template->new;
2323$|=1;
24
25my $mid = int($cgi->param('mid') || 0);
26$mid = int($cgi->param('monitor') || 0) unless $mid; # accept ?monitor= alias
2724
25my $mid = int($cgi->param('mid') || $cgi->param('monitor') || 0);
2826my $dbh = $db->db_connect or die "DB connect failed\n";
27
28# ---- POST: save current state as a Named Release ------------------
29if (($ENV{REQUEST_METHOD} || '') eq 'POST' && $cgi->param('save_snapshot')) {
30 my $name = $cgi->param('name') || ('snapshot-' . time());
31 my $vlbl = $cgi->param('version_label') || $name;
32 my $tier = $cgi->param('tier') || 'draft';
33 $tier = 'draft' unless $tier =~ /^(draft|working|stable)$/;
34 my $desc = "Snapshot of monitor #$mid taken at " . POSIX::strftime('%Y-%m-%d %H:%M:%S', localtime);
35 my $q_name = $dbh->quote($name);
36 my $q_vlbl = $dbh->quote($vlbl);
37 my $q_tier = $dbh->quote($tier);
38 my $q_desc = $dbh->quote($desc);
39 $db->db_readwrite($dbh, qq~
40 INSERT INTO NAMED_RELEASES
41 (name, version_label, tier, description, released_at, released_by,
42 is_locked, created_at, scope_monitor_id)
43 VALUES
44 ($q_name, $q_vlbl, $q_tier, $q_desc, NOW(), 'Operator',
45 1, NOW(), $mid)
46 ~, __FILE__, __LINE__);
47 my $r = $db->db_readwrite($dbh, "SELECT LAST_INSERT_ID() AS id", __FILE__, __LINE__);
48 my $rid = $r && $r->{id};
49 # Pin every current-latest FILE_CHANGES row for this monitor
50 if ($rid) {
51 foreach my $pin ($db->db_readwrite_multiple($dbh, qq~
52 SELECT MAX(file_changes_id) AS id, file_name
53 FROM FILE_CHANGES
54 WHERE file_monitor_list_id = $mid
55 AND blob_sha IS NOT NULL
56 AND status != 'deleted'
57 GROUP BY file_name
58 ~, __FILE__, __LINE__)) {
59 my $q_fname = $dbh->quote($pin->{file_name});
60 my $b = $db->db_readwrite($dbh,
61 "SELECT blob_sha FROM FILE_CHANGES WHERE file_changes_id = $pin->{id}",
62 __FILE__, __LINE__);
63 my $q_sha = $dbh->quote($b ? $b->{blob_sha} : '');
64 $db->db_readwrite($dbh, qq~
65 INSERT INTO NAMED_RELEASE_PINS
66 (named_release_id, file_changes_id, file_name, blob_sha)
67 VALUES ($rid, $pin->{id}, $q_fname, $q_sha)
68 ~, __FILE__, __LINE__);
69 }
70 }
71 $db->db_disconnect($dbh);
72 print "Status: 302 Found\nLocation: /site.cgi?mid=$mid&saved=$rid\n\n"; exit;
73}
2974
3075# ---- Monitor + server ----------------------------------------------
3176my $monitor = $mid ? $db->db_readwrite($dbh, qq~
3277 SELECT m.file_monitor_list_id AS mid,
3378 m.scan_name, m.scan_path, m.ignore_list, m.file_type_filter,
3479 m.max_file_size_bytes, m.retention_days,
80 DATE_FORMAT(m.last_scanned, '%b %d, %Y %H:%i') AS last_scanned_h,
81 UNIX_TIMESTAMP(m.last_scanned) AS last_scanned_epoch,
3582 m.server_id, s.server_name, s.kind AS server_kind,
36 s.ssh_host, s.ssh_user
83 s.ssh_host, s.ssh_user, s.ssh_port,
84 DATE_FORMAT(s.last_heartbeat_at, '%b %d %H:%i') AS heartbeat_h,
85 UNIX_TIMESTAMP(s.last_heartbeat_at) AS heartbeat_epoch,
86 s.status AS server_status
3787 FROM FILE_MONITOR_SETTINGS m
3888 LEFT JOIN SERVERS s ON s.server_id = m.server_id
3989 WHERE m.file_monitor_list_id = $mid LIMIT 1
4090~, __FILE__, __LINE__) : undef;
4191
4292unless ($monitor && $monitor->{mid}) {
4393 print "Content-Type: text/html; charset=utf-8\n\n";
4494 my @body = $tpl->template('site.html', {
4595 has_error => 1,
4696 error_msg => "Monitor #$mid not found.",
4797 });
4898 MODS::PageWrapper->new->wrapper(
4999 page_title => 'Site', page_key => '',
50100 body_html => join('', @body), userinfo => { display_name => 'Operator' },
51101 );
52102 exit;
53103}
54104
55# ---- KPI counts ----------------------------------------------------
56my $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__);
61my $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__);
66my $k_active = $db->db_readwrite($dbh, qq~
67 SELECT COUNT(DISTINCT file_name) AS n FROM FILE_CHANGES
105# ---- KPI counts + status breakdown ---------------------------------
106my $kpi = $db->db_readwrite($dbh, qq~
107 SELECT SUM(CASE WHEN date_time >= DATE_SUB(NOW(), INTERVAL 1 DAY) THEN 1 ELSE 0 END) AS c_24h,
108 SUM(CASE WHEN date_time >= DATE_SUB(NOW(), INTERVAL 7 DAY) THEN 1 ELSE 0 END) AS c_7d,
109 SUM(CASE WHEN date_time >= DATE_SUB(NOW(), INTERVAL 30 DAY) THEN 1 ELSE 0 END) AS c_30d,
110 SUM(CASE WHEN date_time >= DATE_SUB(NOW(), INTERVAL 2 DAY)
111 AND date_time < DATE_SUB(NOW(), INTERVAL 1 DAY) THEN 1 ELSE 0 END) AS c_24h_prev,
112 COUNT(DISTINCT CASE WHEN status != 'deleted' THEN file_name END) AS active_files,
113 COUNT(DISTINCT file_name) AS all_files,
114 COUNT(*) AS c_all,
115 SUM(CASE WHEN status = 'added' THEN 1 ELSE 0 END) AS n_added,
116 SUM(CASE WHEN status = 'modified' THEN 1 ELSE 0 END) AS n_modified,
117 SUM(CASE WHEN status = 'deleted' THEN 1 ELSE 0 END) AS n_deleted,
118 SUM(CASE WHEN is_ts_only = 1 THEN 1 ELSE 0 END) AS n_ts_only,
119 MAX(UNIX_TIMESTAMP(date_time)) AS last_epoch,
120 MIN(UNIX_TIMESTAMP(date_time)) AS first_epoch
121 FROM FILE_CHANGES
68122 WHERE file_monitor_list_id = $mid
69 AND status != 'deleted'
70123~, __FILE__, __LINE__);
71my $k_all = $db->db_readwrite($dbh, qq~
72 SELECT COUNT(*) AS n FROM FILE_CHANGES WHERE file_monitor_list_id = $mid
124
125# Trend arrow
126my ($trend_class, $trend_txt) = ('flat', '&mdash;');
127if ($kpi) {
128 my $cur = $kpi->{c_24h} || 0;
129 my $prev = $kpi->{c_24h_prev} || 0;
130 if ($prev == 0 && $cur > 0) { $trend_class = 'up'; $trend_txt = '&uarr; new'; }
131 elsif ($prev > 0) {
132 my $d = ($cur - $prev) / $prev * 100;
133 $trend_class = $d > 5 ? 'up' : $d < -5 ? 'down' : 'flat';
134 my $arr = $trend_class eq 'up' ? '&uarr;' : $trend_class eq 'down' ? '&darr;' : '&#8226;';
135 $trend_txt = sprintf('%s %+.0f%%', $arr, $d);
136 }
137}
138
139# ---- Storage: blob math for this monitor ---------------------------
140my $storage = $db->db_readwrite($dbh, qq~
141 SELECT COUNT(DISTINCT bs.blob_sha) AS unique_blobs,
142 COALESCE(SUM(DISTINCT bs.size_uncompressed), 0) AS bytes_raw,
143 COALESCE(SUM(DISTINCT bs.size_compressed), 0) AS bytes_gz,
144 COALESCE(SUM(bs.ref_count), 0) AS ref_total
145 FROM FILE_CHANGES fc
146 JOIN BLOB_STORE bs ON bs.blob_sha = fc.blob_sha
147 WHERE fc.file_monitor_list_id = $mid
148 AND fc.blob_sha IS NOT NULL
73149~, __FILE__, __LINE__);
150
151my $unique_blobs = ($storage && $storage->{unique_blobs}) || 0;
152my $bytes_raw = ($storage && $storage->{bytes_raw}) || 0;
153my $bytes_gz = ($storage && $storage->{bytes_gz}) || 0;
154my $dedup_ratio = ($bytes_gz && $bytes_raw)
155 ? sprintf('%.1fx', $bytes_raw / $bytes_gz) : '-';
156my $savings_pct = $bytes_raw
157 ? sprintf('%.0f%%', 100 * (1 - ($bytes_gz / $bytes_raw))) : '-';
74158
75159# ---- 90-day activity chart -----------------------------------------
76160my @day_rows = $db->db_readwrite_multiple($dbh, qq~
77161 SELECT DATE_FORMAT(date_time, '%Y-%m-%d') AS d, COUNT(*) AS n
78162 FROM FILE_CHANGES
79163 WHERE file_monitor_list_id = $mid
80164 AND date_time >= DATE_SUB(NOW(), INTERVAL 90 DAY)
81165 GROUP BY d
82166~, __FILE__, __LINE__);
83167my %by_day; $by_day{$_->{d}} = $_->{n} for @day_rows;
84168my (@days, @day_keys, @file_series, @empty_schema);
85169my $now = time;
86170for (my $i = 89; $i >= 0; $i--) {
87171 my $t = $now - $i * 86400;
88172 my $key = POSIX::strftime('%Y-%m-%d', localtime($t));
89173 my $lbl = POSIX::strftime('%b %d', localtime($t));
90174 push @days, $lbl; push @day_keys, $key;
91175 push @file_series, ($by_day{$key} || 0);
92176 push @empty_schema, 0;
93177}
94178my $timeline_svg = MODS::Charts::stacked_bars(
95179 days => \@days, day_keys => \@day_keys,
96180 series => { file => \@file_series, schema => \@empty_schema },
97181 width => 900, height => 150,
98182 link_file => '/file_changes.cgi',
99183 link_schema => '/schema_changes.cgi',
100184);
101185
102# ---- Recent changes for THIS site (last 40) ------------------------
186# ---- Recent changes (last 25) --------------------------------------
103187my @recent = $db->db_readwrite_multiple($dbh, qq~
104188 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
189 fc.is_ts_only, UNIX_TIMESTAMP(fc.date_time) AS ts_epoch, fc.date_time
108190 FROM FILE_CHANGES fc
109191 WHERE fc.file_monitor_list_id = $mid
110 ORDER BY fc.file_changes_id DESC
111 LIMIT 40
192 ORDER BY fc.file_changes_id DESC LIMIT 25
112193~, __FILE__, __LINE__);
113194foreach my $r (@recent) {
114195 $r->{status_pill} = $r->{status} eq 'added' ? 'pill-ok'
115196 : $r->{status} eq 'modified' ? 'pill-warn'
116197 : $r->{status} eq 'deleted' ? 'pill-bad'
117198 : 'pill-mute';
118199 $r->{file_short} = length($r->{file_name} // '') > 60
119200 ? '...' . substr($r->{file_name}, -57) : $r->{file_name};
120201 $r->{blob_short} = $r->{blob_sha} ? substr($r->{blob_sha}, 0, 8) : '-';
121202 $r->{diff_url} = "/diff.cgi?kind=file&id=$r->{id}";
122203 $r->{restore_url} = "/restore.cgi?id=$r->{id}";
123204 $r->{can_restore} = ($r->{blob_sha} && $r->{status} ne 'deleted') ? 1 : 0;
124205 $r->{ts_epoch} //= 0;
125206}
126207
127# ---- Change-frequency for THIS site (top 8) -------------------------
208# ---- Change-frequency (top 10) -------------------------------------
128209my @hotspots = $db->db_readwrite_multiple($dbh, qq~
129 SELECT file_name,
130 COUNT(*) AS ct,
210 SELECT file_name, COUNT(*) AS ct,
131211 MAX(file_changes_id) AS latest_id,
132212 MAX(UNIX_TIMESTAMP(date_time)) AS latest_epoch
133213 FROM FILE_CHANGES
134214 WHERE file_monitor_list_id = $mid
135215 AND date_time >= DATE_SUB(NOW(), INTERVAL 30 DAY)
136216 GROUP BY file_name
137 ORDER BY ct DESC LIMIT 8
217 ORDER BY ct DESC LIMIT 10
138218~, __FILE__, __LINE__);
139219foreach my $h (@hotspots) {
140220 $h->{file_short} = length($h->{file_name} // '') > 55
141221 ? '...' . substr($h->{file_name}, -52) : $h->{file_name};
142222 $h->{diff_url} = "/diff.cgi?kind=file&id=" . ($h->{latest_id} || 0);
223 $h->{latest_epoch} //= 0;
143224}
144225
145# ---- Named releases scoped to this site ----------------------------
226# ---- Named releases covering this site -----------------------------
146227my @releases = $db->db_readwrite_multiple($dbh, qq~
147228 SELECT nr.named_release_id AS id, nr.name, nr.version_label, nr.tier,
148229 nr.description,
149230 UNIX_TIMESTAMP(nr.released_at) AS released_epoch,
150 DATE_FORMAT(nr.released_at, '%b %d, %Y') AS released_h
231 DATE_FORMAT(nr.released_at, '%b %d, %Y') AS released_h,
232 (SELECT COUNT(*) FROM NAMED_RELEASE_PINS p WHERE p.named_release_id = nr.named_release_id) AS pin_ct
151233 FROM NAMED_RELEASES nr
152234 WHERE nr.scope_monitor_id = $mid
153235 OR nr.scope_server_id = $monitor->{server_id}
154 OR nr.scope_monitor_id IS NULL
236 OR (nr.scope_monitor_id IS NULL AND nr.scope_server_id IS NULL)
155237 ORDER BY (nr.tier = 'stable') DESC, (nr.tier = 'working') DESC, nr.named_release_id DESC
156 LIMIT 10
238 LIMIT 12
157239~, __FILE__, __LINE__);
158240foreach my $r (@releases) {
159241 $r->{tier} //= 'draft';
160242 $r->{tier_pill} = $r->{tier} eq 'stable' ? 'pill-ok'
161243 : $r->{tier} eq 'working' ? 'pill-info'
162244 : 'pill-mute';
163245 $r->{label} = $r->{version_label} || $r->{name};
164246 $r->{restore_url} = "/restore_release.cgi?release_id=$r->{id}";
165247 $r->{released_epoch} //= 0;
248 $r->{pin_ct} //= 0;
249}
250
251# ---- Deletion log --------------------------------------------------
252my @deletions = $db->db_readwrite_multiple($dbh, qq~
253 SELECT fc.file_changes_id AS id, fc.file_name,
254 UNIX_TIMESTAMP(fc.date_time) AS ts_epoch,
255 fc.date_time,
256 (SELECT MAX(fc2.file_changes_id) FROM FILE_CHANGES fc2
257 WHERE fc2.file_monitor_list_id = $mid
258 AND fc2.file_name = fc.file_name
259 AND fc2.blob_sha IS NOT NULL
260 AND fc2.file_changes_id < fc.file_changes_id) AS last_content_id
261 FROM FILE_CHANGES fc
262 WHERE fc.file_monitor_list_id = $mid
263 AND fc.status = 'deleted'
264 ORDER BY fc.file_changes_id DESC LIMIT 10
265~, __FILE__, __LINE__);
266foreach my $d (@deletions) {
267 $d->{file_short} = length($d->{file_name} // '') > 55
268 ? '...' . substr($d->{file_name}, -52) : $d->{file_name};
269 $d->{ts_epoch} //= 0;
270 $d->{restore_url} = $d->{last_content_id}
271 ? "/restore.cgi?id=$d->{last_content_id}"
272 : '';
273 $d->{diff_url} = $d->{last_content_id}
274 ? "/diff.cgi?kind=file&id=$d->{last_content_id}"
275 : '';
276 $d->{can_restore} = $d->{last_content_id} ? 1 : 0;
277}
278
279# ---- Recent restores affecting this site ---------------------------
280my @restores = $db->db_readwrite_multiple($dbh, qq~
281 SELECT rl.restore_id, rl.target_file, rl.status,
282 LEFT(rl.source_blob_sha, 12) AS sha_short,
283 rl.restored_by,
284 UNIX_TIMESTAMP(rl.restored_at) AS restored_epoch,
285 DATE_FORMAT(rl.restored_at, '%b %d %H:%i') AS restored_h
286 FROM RESTORE_LOG rl
287 WHERE rl.server_id = $monitor->{server_id}
288 AND rl.target_file LIKE @{[ $dbh->quote($monitor->{scan_path} . '%') ]}
289 ORDER BY rl.restore_id DESC LIMIT 8
290~, __FILE__, __LINE__);
291foreach my $r (@restores) {
292 $r->{status_pill} = $r->{status} eq 'success' ? 'pill-ok' :
293 $r->{status} eq 'failed' ? 'pill-bad' : 'pill-mute';
294 $r->{file_short} = length($r->{target_file} // '') > 55
295 ? '...' . substr($r->{target_file}, -52) : $r->{target_file};
296 $r->{restored_epoch} //= 0;
297}
298
299# ---- Compile-check summary -----------------------------------------
300my $compile_summary = $db->db_readwrite($dbh, qq~
301 SELECT SUM(CASE WHEN bs.compile_status = 'ok' THEN 1 ELSE 0 END) AS n_ok,
302 SUM(CASE WHEN bs.compile_status = 'error' THEN 1 ELSE 0 END) AS n_err,
303 COUNT(DISTINCT bs.blob_sha) AS n_checked
304 FROM FILE_CHANGES fc
305 JOIN BLOB_STORE bs ON bs.blob_sha = fc.blob_sha
306 WHERE fc.file_monitor_list_id = $mid
307 AND bs.compile_status IN ('ok', 'error')
308~, __FILE__, __LINE__);
309my @compile_errors = $db->db_readwrite_multiple($dbh, qq~
310 SELECT fc.file_name, fc.file_changes_id AS id,
311 LEFT(bs.compile_message, 200) AS msg
312 FROM FILE_CHANGES fc
313 JOIN BLOB_STORE bs ON bs.blob_sha = fc.blob_sha
314 WHERE fc.file_monitor_list_id = $mid
315 AND bs.compile_status = 'error'
316 GROUP BY fc.file_name, fc.file_changes_id, bs.compile_message
317 ORDER BY fc.file_changes_id DESC LIMIT 8
318~, __FILE__, __LINE__);
319foreach my $e (@compile_errors) {
320 $e->{file_short} = length($e->{file_name} // '') > 55
321 ? '...' . substr($e->{file_name}, -52) : $e->{file_name};
322 $e->{diff_url} = "/diff.cgi?kind=file&id=$e->{id}";
323}
324
325# ---- Cross-site dedup: blobs this site shares elsewhere ------------
326my @shared = $db->db_readwrite_multiple($dbh, qq~
327 SELECT fc.blob_sha,
328 COUNT(DISTINCT fc2.file_monitor_list_id) AS site_ct,
329 GROUP_CONCAT(DISTINCT m.scan_name SEPARATOR ' | ') AS scans,
330 MAX(fc.file_changes_id) AS example_id,
331 bs.size_uncompressed
332 FROM FILE_CHANGES fc
333 JOIN FILE_CHANGES fc2 ON fc2.blob_sha = fc.blob_sha
334 AND fc2.file_monitor_list_id != $mid
335 LEFT JOIN FILE_MONITOR_SETTINGS m ON m.file_monitor_list_id = fc2.file_monitor_list_id
336 JOIN BLOB_STORE bs ON bs.blob_sha = fc.blob_sha
337 WHERE fc.file_monitor_list_id = $mid
338 AND fc.blob_sha IS NOT NULL
339 GROUP BY fc.blob_sha, bs.size_uncompressed
340 ORDER BY site_ct DESC LIMIT 6
341~, __FILE__, __LINE__);
342foreach my $b (@shared) {
343 $b->{sha_short} = substr($b->{blob_sha} // '', 0, 12);
344 $b->{size_h} = _fmt_bytes($b->{size_uncompressed} || 0);
345 $b->{diff_url} = "/diff.cgi?kind=file&id=" . ($b->{example_id} || 0);
346 $b->{scans_h} = length($b->{scans} // '') > 90
347 ? substr($b->{scans}, 0, 87) . '...' : $b->{scans};
348}
349
350# ---- Pin drift affecting this site ---------------------------------
351my @drift = $db->db_readwrite_multiple($dbh, qq~
352 SELECT d.file_name, d.pinned_sha, d.current_sha,
353 r.name AS release_name, r.named_release_id AS rid,
354 UNIX_TIMESTAMP(d.detected_at) AS detected_epoch,
355 DATE_FORMAT(d.detected_at, '%b %d %H:%i') AS detected_h
356 FROM PIN_DRIFT_LOG d
357 LEFT JOIN NAMED_RELEASES r ON r.named_release_id = d.named_release_id
358 LEFT JOIN FILE_CHANGES fc ON fc.file_changes_id = d.file_changes_id
359 WHERE fc.file_monitor_list_id = $mid
360 ORDER BY d.drift_id DESC LIMIT 6
361~, __FILE__, __LINE__);
362foreach my $d (@drift) {
363 $d->{file_short} = length($d->{file_name} // '') > 50
364 ? '...' . substr($d->{file_name}, -47) : $d->{file_name};
365 $d->{pinned_short} = substr($d->{pinned_sha} // '', 0, 12);
366 $d->{current_short}= substr($d->{current_sha} // '', 0, 12);
367 $d->{restore_url} = "/restore_release.cgi?release_id=" . ($d->{rid} || 0);
368 $d->{detected_epoch} //= 0;
369}
370
371# ---- Longest-active files -----------------------------------------
372my @longevity = $db->db_readwrite_multiple($dbh, qq~
373 SELECT file_name,
374 MIN(UNIX_TIMESTAMP(date_time)) AS first_epoch,
375 MAX(UNIX_TIMESTAMP(date_time)) AS last_epoch,
376 COUNT(*) AS ct,
377 MAX(file_changes_id) AS latest_id
378 FROM FILE_CHANGES
379 WHERE file_monitor_list_id = $mid
380 AND status != 'deleted'
381 GROUP BY file_name
382 ORDER BY first_epoch ASC LIMIT 8
383~, __FILE__, __LINE__);
384foreach my $L (@longevity) {
385 $L->{file_short} = length($L->{file_name} // '') > 55
386 ? '...' . substr($L->{file_name}, -52) : $L->{file_name};
387 $L->{diff_url} = "/diff.cgi?kind=file&id=" . ($L->{latest_id} || 0);
388 my $days = int((time - ($L->{first_epoch} || time)) / 86400);
389 $L->{days_tracked} = $days;
390 $L->{first_epoch} //= 0;
166391}
167392
168393$db->db_disconnect($dbh);
169394
170395my $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}",
396 mid => $mid,
397 scan_name => $monitor->{scan_name},
398 scan_path => $monitor->{scan_path},
399 server_name => $monitor->{server_name} // 'local',
400 server_kind => $monitor->{server_kind} // 'local',
401 server_status => $monitor->{server_status} // 'active',
402 ssh_host => $monitor->{ssh_host} // '',
403 ssh_user => $monitor->{ssh_user} // '',
404 ssh_port => $monitor->{ssh_port} // 22,
405 is_agent => (($monitor->{server_kind}//'') eq 'ssh_agent') ? 1 : 0,
406 retention_h => defined($monitor->{retention_days}) && $monitor->{retention_days}
407 ? "$monitor->{retention_days} days"
408 : 'global default',
409 ignore_list => $monitor->{ignore_list} // '(none)',
410 file_type_filter => $monitor->{file_type_filter} // '(all types)',
411 max_file_size_h => $monitor->{max_file_size_bytes}
412 ? _fmt_bytes($monitor->{max_file_size_bytes}) : 'global default',
413 last_scanned_h => $monitor->{last_scanned_h} // 'never',
414 last_scanned_epoch => $monitor->{last_scanned_epoch} || 0,
415 heartbeat_h => $monitor->{heartbeat_h} // 'never',
416 heartbeat_epoch => $monitor->{heartbeat_epoch} || 0,
417
418 kpi_24h => ($kpi && $kpi->{c_24h}) || 0,
419 kpi_24h_prev => ($kpi && $kpi->{c_24h_prev}) || 0,
420 kpi_7d => ($kpi && $kpi->{c_7d}) || 0,
421 kpi_30d => ($kpi && $kpi->{c_30d}) || 0,
422 kpi_active => ($kpi && $kpi->{active_files})|| 0,
423 kpi_all_files => ($kpi && $kpi->{all_files}) || 0,
424 kpi_lifetime => ($kpi && $kpi->{c_all}) || 0,
425 n_added => ($kpi && $kpi->{n_added}) || 0,
426 n_modified => ($kpi && $kpi->{n_modified}) || 0,
427 n_deleted => ($kpi && $kpi->{n_deleted}) || 0,
428 n_ts_only => ($kpi && $kpi->{n_ts_only}) || 0,
429 first_captured => $kpi && $kpi->{first_epoch}
430 ? POSIX::strftime('%b %d, %Y', localtime($kpi->{first_epoch}))
431 : '(none)',
432 last_captured => $kpi && $kpi->{last_epoch}
433 ? POSIX::strftime('%b %d %H:%i', localtime($kpi->{last_epoch}))
434 : '(none)',
435 last_captured_epoch => ($kpi && $kpi->{last_epoch}) || 0,
436 trend_class => $trend_class,
437 trend_txt => $trend_txt,
438
439 unique_blobs => $unique_blobs,
440 bytes_raw_h => _fmt_bytes($bytes_raw),
441 bytes_gz_h => _fmt_bytes($bytes_gz),
442 dedup_ratio => $dedup_ratio,
443 savings_pct => $savings_pct,
444 ref_total => $storage ? $storage->{ref_total} : 0,
445
446 timeline_svg => $timeline_svg,
447 recent => \@recent,
448 has_recent => scalar(@recent) ? 1 : 0,
449 hotspots => \@hotspots,
450 has_hotspots => scalar(@hotspots) ? 1 : 0,
451 releases => \@releases,
452 has_releases => scalar(@releases) ? 1 : 0,
453 deletions => \@deletions,
454 has_deletions => scalar(@deletions)? 1 : 0,
455 restores => \@restores,
456 has_restores => scalar(@restores) ? 1 : 0,
457 cc_ok => ($compile_summary && $compile_summary->{n_ok}) || 0,
458 cc_err => ($compile_summary && $compile_summary->{n_err}) || 0,
459 cc_checked => ($compile_summary && $compile_summary->{n_checked})|| 0,
460 has_compile_data => ($compile_summary && $compile_summary->{n_checked}) ? 1 : 0,
461 compile_errors => \@compile_errors,
462 has_compile_errors => scalar(@compile_errors) ? 1 : 0,
463 shared_blobs => \@shared,
464 has_shared => scalar(@shared) ? 1 : 0,
465 drift => \@drift,
466 has_drift => scalar(@drift) ? 1 : 0,
467 longevity => \@longevity,
468 has_longevity => scalar(@longevity) ? 1 : 0,
469
470 changes_url => "/file_changes.cgi?monitor=$mid",
471 pin_url => "/pin_hotspots.cgi?monitor=$mid",
472 export_url => "/export.cgi?server_id=$monitor->{server_id}",
473 saved_id => int($cgi->param('saved') || 0),
195474};
196475
197476print "Content-Type: text/html; charset=utf-8\nCache-Control: no-cache\n\n";
198477my @body = $tpl->template('site.html', $tvars);
199478MODS::PageWrapper->new->wrapper(
200479 page_title => "Site: " . ($monitor->{scan_name} // '?'),
201 page_key => '',
202 active_mid => $mid,
203 body_html => join('', @body),
204 userinfo => { display_name => 'Operator' },
480 page_key => 'sites',
481 body_html => join('', @body),
482 userinfo => { display_name => 'Operator' },
205483);
484
485sub _fmt_bytes {
486 my $n = shift; $n //= 0;
487 return "$n B" if $n < 1024;
488 return sprintf('%.1f KB', $n/1024) if $n < 1024*1024;
489 return sprintf('%.1f MB', $n/1024/1024) if $n < 1024*1024*1024;
490 return sprintf('%.1f GB', $n/1024/1024/1024);
491}