#!/usr/local/cpanel/3rdparty/bin/perl # gniza cPanel Plugin — Activity Logs # Shows user-initiated restore actions and their results use strict; use warnings; BEGIN { my $base; if ($0 =~ m{^(.*)/}) { $base = $1; } else { $base = '.'; } unshift @INC, "$base/lib"; } use Cpanel::LiveAPI (); use Cpanel::AdminBin::Call (); use Cpanel::Form (); use GnizaCPanel::UI; my $cpanel = Cpanel::LiveAPI->new(); END { $cpanel->end() if $cpanel } my $form = Cpanel::Form::parseform(); my $entry = $form->{'entry'} // ''; if ($entry ne '') { show_entry($entry); } else { show_list(); } exit; # ── List View ──────────────────────────────────────────────── sub show_list { print "Content-Type: text/html\r\n\r\n"; print $cpanel->header(''); print GnizaCPanel::UI::page_header('GNIZA Activity Log'); print GnizaCPanel::UI::render_nav('logs.live.cgi'); print GnizaCPanel::UI::render_flash(); my $raw = eval { Cpanel::AdminBin::Call::call('Gniza', 'Restore', 'LIST_LOGS') } // ''; if ($raw =~ /^ERROR: (.*)/) { print qq{
} . GnizaCPanel::UI::esc($1) . qq{
\n}; print GnizaCPanel::UI::page_footer(); print $cpanel->footer(); return; } my @entries; for my $line (split /\n/, $raw) { next if $line eq ''; my ($idx, $date, $action, $details, $status) = split /\t/, $line; push @entries, { idx => $idx // '', date => $date // '', action => $action // '', details => $details // '', status => $status // '', }; } if (!@entries) { print qq{
No restore activity yet. Actions you perform in the Restore section will appear here.
\n}; print GnizaCPanel::UI::page_footer(); print $cpanel->footer(); return; } # Pagination my $per_page = 25; my $total = scalar @entries; my $page = int($form->{'page'} // 1); $page = 1 if $page < 1; my $total_pages = int(($total + $per_page - 1) / $per_page); $page = $total_pages if $page > $total_pages; my $start = ($page - 1) * $per_page; my $end = $start + $per_page - 1; $end = $#entries if $end > $#entries; my @page_entries = @entries[$start .. $end]; print qq{
\n}; print qq{\n}; print qq{\n}; print qq{\n}; for my $e (@page_entries) { my $esc_date = GnizaCPanel::UI::esc($e->{date}); my $esc_action = GnizaCPanel::UI::esc($e->{action}); my $esc_details = GnizaCPanel::UI::esc($e->{details}); my $esc_status = GnizaCPanel::UI::esc($e->{status}); my $esc_idx = GnizaCPanel::UI::esc($e->{idx}); my $status_badge = $e->{status} eq 'Error' ? 'badge-error' : 'badge-success'; my $href = 'logs.live.cgi?entry=' . _uri_escape($e->{idx}); print qq{\n}; print qq{ \n}; print qq{ \n}; print qq{ \n}; print qq{ \n}; print qq{ \n}; print qq{\n}; } print qq{\n
Date (UTC)ActionDetailsStatus
$esc_date$esc_action$esc_details$esc_status
\n
\n}; # Pagination controls if ($total_pages > 1) { print qq{
\n}; if ($page > 1) { my $prev = $page - 1; print qq{ \n}; } print qq{ Page $page of $total_pages ($total entries)\n}; if ($page < $total_pages) { my $next = $page + 1; print qq{ \n}; } print qq{
\n}; } print GnizaCPanel::UI::page_footer(); print $cpanel->footer(); } # ── Entry Detail View ──────────────────────────────────────── sub show_entry { my ($entry_idx) = @_; print "Content-Type: text/html\r\n\r\n"; print $cpanel->header(''); print GnizaCPanel::UI::page_header('GNIZA Activity Detail'); print GnizaCPanel::UI::render_nav('logs.live.cgi'); # Validate entry index (numeric only) unless ($entry_idx =~ /^[0-9]+$/) { print qq{
Invalid entry.
\n}; print qq{

← Back to activity log

\n}; print GnizaCPanel::UI::page_footer(); print $cpanel->footer(); return; } my $content = eval { Cpanel::AdminBin::Call::call('Gniza', 'Restore', 'GET_LOG', $entry_idx) } // ''; if ($content =~ /^ERROR: (.*)/) { print qq{
} . GnizaCPanel::UI::esc($1) . qq{
\n}; print qq{

← Back to activity log

\n}; print GnizaCPanel::UI::page_footer(); print $cpanel->footer(); return; } # Parse header fields and output from entry content my ($date, $action, $details, $status, $output) = ('', '', '', '', ''); my @lines = split /\n/, $content; my @output_lines; my $in_output = 0; for my $line (@lines) { if (!$in_output) { if ($line =~ /^Date:\s+(.+)$/) { $date = $1; } elsif ($line =~ /^Action:\s+(.+)$/) { $action = $1; } elsif ($line =~ /^Details:\s+(.+)$/) { $details = $1; } elsif ($line =~ /^Status:\s+(.+)$/) { $status = $1; $in_output = 1; } } else { push @output_lines, $line; } } # Back link print qq{

← Back to activity log

\n}; # Entry info card my $status_badge = $status eq 'Error' ? 'badge-error' : 'badge-success'; print qq{
\n}; print qq{
\n}; print qq{
\n}; print qq{ Date: } . GnizaCPanel::UI::esc($date) . qq{\n}; print qq{ Action: } . GnizaCPanel::UI::esc($action) . qq{\n}; print qq{ Status: } . GnizaCPanel::UI::esc($status) . qq{\n}; print qq{
\n}; print qq{
Details: } . GnizaCPanel::UI::esc($details) . qq{
\n}; print qq{
\n
\n}; # Output section if (@output_lines) { print qq{

Command Output

\n}; print qq{
};
        for my $line (@output_lines) {
            my $esc = GnizaCPanel::UI::esc($line);
            if ($line =~ /\[ERROR\]/) {
                print qq{$esc\n};
            } elsif ($line =~ /\[WARN\]/) {
                print qq{$esc\n};
            } elsif ($line =~ /\[DEBUG\]/) {
                print qq{$esc\n};
            } else {
                print "$esc\n";
            }
        }
        print qq{
\n}; } else { print qq{
No output recorded for this action.
\n}; } print GnizaCPanel::UI::page_footer(); print $cpanel->footer(); } # ── Helpers ─────────────────────────────────────────────────── sub _uri_escape { my $str = shift // ''; $str =~ s/([^A-Za-z0-9\-._~])/sprintf("%%%02X", ord($1))/ge; return $str; } 1;