Diff -- /var/www/vhosts/3dshawn.com/site1/file_changes.cgi
Diff
/var/www/vhosts/3dshawn.com/site1/file_changes.cgi
modified on local at 2026-07-11 23:34:33
Added
+2
lines
Removed
-0
lines
Context
127
unchanged
Blobs
from 00ab93cbfdf2
to 5cd91f273405
to 5cd91f273405
Unified diff
Naive LCS-based line diff. Additions in emerald, deletions in rose. Both sides decompressed live from BLOB_STORE.| 1 | 1 | #!/usr/bin/perl |
| 2 | 2 | use strict; |
| 3 | 3 | use warnings; |
| 4 | 4 | use CGI (); |
| 5 | 5 | use MODS::Config; |
| 6 | 6 | use MODS::DBConnect; |
| 7 | 7 | use MODS::Template; |
| 8 | 8 | use MODS::PageWrapper; |
| 9 | 9 | use MODS::Charts; |
| 10 | 10 | use MODS::RangePicker; |
| 11 | 11 | use POSIX (); |
| 12 | 12 | |
| 13 | 13 | my $cgi = CGI->new; |
| 14 | 14 | my $db = MODS::DBConnect->new; |
| 15 | 15 | my $tpl = MODS::Template->new; |
| 16 | 16 | |
| 17 | 17 | $|=1; print "Content-Type: text/html; charset=utf-8\nCache-Control: no-cache\n\n"; |
| 18 | 18 | |
| 19 | 19 | my $dbh = $db->db_connect or die "DB connect failed\n"; |
| 20 | 20 | |
| 21 | 21 | my $r = MODS::RangePicker::parse($cgi); |
| 22 | 22 | my $q = $cgi->param('q') || ''; |
| 23 | 23 | my $server_filter = int($cgi->param('server') || 0); |
| 24 | 24 | |
| 25 | 25 | my $where = $r->{sql_where}->('fc.date_time'); |
| 26 | 26 | $where .= " AND fc.file_name LIKE " . $dbh->quote('%' . $q . '%') if length $q; |
| 27 | 27 | $where .= " AND fc.server_id = $server_filter" if $server_filter; |
| 28 | 28 | |
| 29 | 29 | my @rows = $db->db_readwrite_multiple($dbh, qq~ |
| 30 | 30 | SELECT fc.file_changes_id AS id, fc.file_name, fc.status, |
| 31 | 31 | UNIX_TIMESTAMP(fc.date_time) AS ts_epoch, |
| 32 | 32 | fc.date_time, |
| 33 | 33 | fc.blob_sha, fc.diff_blob_sha, fc.is_ts_only, fc.uid, fc.gid, fc.permissions, |
| 34 | 34 | s.server_name |
| 35 | 35 | FROM FILE_CHANGES fc |
| 36 | 36 | LEFT JOIN SERVERS s ON s.server_id = fc.server_id |
| 37 | 37 | WHERE $where |
| 38 | 38 | ORDER BY fc.date_time DESC |
| 39 | 39 | LIMIT 200 |
| 40 | 40 | ~, __FILE__, __LINE__); |
| 41 | 41 | |
| 42 | 42 | # --- Per-file 30-day sparkline data. One query, then bucket in Perl. --- |
| 43 | 43 | my %spark; |
| 44 | 44 | { |
| 45 | 45 | my @dedupe_files; |
| 46 | 46 | my %seen; |
| 47 | 47 | foreach my $r_row (@rows) { |
| 48 | 48 | next unless defined $r_row->{file_name}; |
| 49 | 49 | next if $seen{$r_row->{file_name}}++; |
| 50 | 50 | push @dedupe_files, $r_row->{file_name}; |
| 51 | 51 | } |
| 52 | 52 | if (@dedupe_files) { |
| 53 | 53 | my @quoted = map { $dbh->quote($_) } @dedupe_files; |
| 54 | 54 | my $in_list = join(',', @quoted); |
| 55 | 55 | my @day_rows = $db->db_readwrite_multiple($dbh, qq~ |
| 56 | 56 | SELECT file_name, |
| 57 | 57 | DATE_FORMAT(date_time, '%Y-%m-%d') AS d, |
| 58 | 58 | COUNT(*) AS n |
| 59 | 59 | FROM FILE_CHANGES |
| 60 | 60 | WHERE file_name IN ($in_list) |
| 61 | 61 | AND date_time >= DATE_SUB(NOW(), INTERVAL 30 DAY) |
| 62 | 62 | GROUP BY file_name, d |
| 63 | 63 | ~, __FILE__, __LINE__); |
| 64 | 64 | my %by_file; |
| 65 | 65 | foreach my $dr (@day_rows) { |
| 66 | 66 | $by_file{$dr->{file_name}}{$dr->{d}} = $dr->{n}; |
| 67 | 67 | } |
| 68 | 68 | my $now = time; |
| 69 | 69 | my @day_keys; |
| 70 | 70 | for (my $i = 29; $i >= 0; $i--) { |
| 71 | 71 | push @day_keys, POSIX::strftime('%Y-%m-%d', localtime($now - $i * 86400)); |
| 72 | 72 | } |
| 73 | 73 | foreach my $fname (@dedupe_files) { |
| 74 | 74 | my @series; |
| 75 | 75 | for my $k (@day_keys) { |
| 76 | 76 | push @series, ($by_file{$fname}{$k} || 0); |
| 77 | 77 | } |
| 78 | 78 | $spark{$fname} = \@series; |
| 79 | 79 | } |
| 80 | 80 | } |
| 81 | 81 | } |
| 82 | 82 | |
| 83 | 83 | foreach my $row (@rows) { |
| 84 | 84 | $row->{status_pill} = $row->{status} && $row->{status} eq 'added' ? 'pill-ok' |
| 85 | 85 | : $row->{status} && $row->{status} eq 'modified' ? 'pill-warn' |
| 86 | 86 | : $row->{status} && $row->{status} eq 'deleted' ? 'pill-bad' |
| 87 | 87 | : 'pill-mute'; |
| 88 | 88 | $row->{status} //= 'unknown'; |
| 89 | 89 | $row->{file_name_h} = $row->{file_name} // ''; |
| 90 | 90 | $row->{file_name_h} = length($row->{file_name_h}) > 90 |
| 91 | 91 | ? '...' . substr($row->{file_name_h}, -87) |
| 92 | 92 | : $row->{file_name_h}; |
| 93 | 93 | $row->{blob_short} = $row->{blob_sha} ? substr($row->{blob_sha}, 0, 8) : '-'; |
| 94 | 94 | $row->{diff_url} = "/diff.cgi?kind=file&id=$row->{id}"; |
| 95 | $row->{restore_url} = "/restore.cgi?id=$row->{id}"; | |
| 96 | $row->{can_restore} = ($row->{blob_sha} && $row->{status} ne 'deleted') ? 1 : 0; | |
| 95 | 97 | $row->{ts_epoch} //= 0; |
| 96 | 98 | my $series = $spark{$row->{file_name} // ''}; |
| 97 | 99 | $row->{sparkline_svg} = $series |
| 98 | 100 | ? MODS::Charts::sparkline(values => $series, width => 88, height => 20, color => '#14b8a6') |
| 99 | 101 | : ''; |
| 100 | 102 | } |
| 101 | 103 | my $total = scalar @rows; |
| 102 | 104 | |
| 103 | 105 | $db->db_disconnect($dbh); |
| 104 | 106 | |
| 105 | 107 | my $picker_html = MODS::RangePicker::picker_html( |
| 106 | 108 | action => '/file_changes.cgi', |
| 107 | 109 | range => $r, |
| 108 | 110 | hidden => { q => $q, ($server_filter ? (server => $server_filter) : ()) }, |
| 109 | 111 | ); |
| 110 | 112 | |
| 111 | 113 | my $tvars = { |
| 112 | 114 | rows => \@rows, |
| 113 | 115 | has_rows => $total ? 1 : 0, |
| 114 | 116 | total => $total, |
| 115 | 117 | q => $q, |
| 116 | 118 | picker_html => $picker_html, |
| 117 | 119 | range_label => $r->{range_label}, |
| 118 | 120 | is_day_view => $r->{is_day} ? 1 : 0, |
| 119 | 121 | day_label => $r->{on}, |
| 120 | 122 | }; |
| 121 | 123 | my @body = $tpl->template('file_changes.html', $tvars); |
| 122 | 124 | |
| 123 | 125 | MODS::PageWrapper->new->wrapper( |
| 124 | 126 | page_title => 'File changes', page_key => 'file_changes', |
| 125 | 127 | body_html => join('', @body), |
| 126 | 128 | userinfo => { display_name => 'Operator' }, |
| 127 | 129 | ); |