#!/usr/local/cpanel/3rdparty/bin/perl # gniza WHM Plugin — Dashboard use strict; use warnings; use lib '/usr/local/cpanel/whostmgr/docroot/cgi/gniza-whm/lib'; use Whostmgr::HTMLInterface (); use Cpanel::Form (); use GnizaWHM::Config; use GnizaWHM::Cron; use GnizaWHM::Runner; use GnizaWHM::UI; my $form = Cpanel::Form::parseform(); # Redirect to setup wizard if gniza is not configured unless (GnizaWHM::UI::is_configured()) { if (($form->{'action'} // '') eq 'status') { print "Content-Type: application/json\r\n\r\n"; print '{"running":false,"pid":"","log_file":"","lines":[]}'; exit; } print "Status: 302 Found\r\n"; print "Location: setup.cgi\r\n\r\n"; exit; } # JSON status endpoint for AJAX polling if (($form->{'action'} // '') eq 'status') { handle_status_json(); exit; } # Refresh stats endpoint if (($form->{'action'} // '') eq 'refresh_stats' && ($ENV{'REQUEST_METHOD'} // '') eq 'POST') { unless (GnizaWHM::UI::verify_csrf_token($form->{'gniza_csrf'} // '')) { GnizaWHM::UI::set_flash('error', 'Invalid or expired form token.'); print "Status: 302 Found\r\n"; print "Location: index.cgi\r\n\r\n"; exit; } my ($ok, $stdout, $stderr) = GnizaWHM::Runner::run('stats', undef, [], {}); if ($ok) { GnizaWHM::UI::set_flash('success', 'Statistics refreshed successfully.'); } else { GnizaWHM::UI::set_flash('error', 'Failed to refresh statistics: ' . GnizaWHM::UI::esc($stderr)); } print "Status: 302 Found\r\n"; print "Location: index.cgi\r\n\r\n"; exit; } print "Content-Type: text/html\r\n\r\n"; Whostmgr::HTMLInterface::defheader('GNIZA Backup Manager — Dashboard', '', '/cgi/gniza-whm/index.cgi'); print GnizaWHM::UI::page_header('GNIZA Backup Manager'); print GnizaWHM::UI::render_nav('index.cgi'); print GnizaWHM::UI::render_flash(); # Stats overview cards { my $log_dir = '/var/log/gniza'; my $main_conf_file = '/etc/gniza/gniza.conf'; if (-f $main_conf_file) { my $cfg = GnizaWHM::Config::parse($main_conf_file, 'main'); $log_dir = $cfg->{LOG_DIR} if $cfg->{LOG_DIR} && $cfg->{LOG_DIR} ne ''; } my @cpanel_accounts = GnizaWHM::UI::get_cpanel_accounts(); my @remotes = GnizaWHM::UI::list_remotes(); my $schedules = GnizaWHM::Cron::get_current_schedules(); my $sched_count = scalar keys %$schedules; # Read stats.json cache my $stats_file = "$log_dir/stats.json"; my ($backed_up, $total_snaps, $last_status, $last_log, $updated) = (0, 0, '', '', ''); my $has_stats = 0; if (-f $stats_file) { if (open my $fh, '<', $stats_file) { my $json = do { local $/; <$fh> }; close $fh; $has_stats = 1; ($backed_up) = $json =~ /"backed_up_accounts"\s*:\s*(\d+)/; ($total_snaps) = $json =~ /"snapshots"\s*:\s*(\d+)/; ($last_status) = $json =~ /"status"\s*:\s*"([^"]*)"/; ($last_log) = $json =~ /"log"\s*:\s*"([^"]*)"/; ($updated) = $json =~ /"updated"\s*:\s*"([^"]*)"/; $backed_up //= 0; $total_snaps //= 0; $last_status //= ''; $last_log //= ''; $updated //= ''; } } # Updated timestamp + refresh button print qq{
\n}; if ($has_stats && $updated) { my $esc_updated = GnizaWHM::UI::esc($updated); print qq{ Stats updated: $esc_updated\n}; } else { print qq{ No stats collected yet\n}; } print qq{
\n}; print qq{ \n}; print qq{ } . GnizaWHM::UI::csrf_hidden_field() . qq{\n}; print qq{ \n}; print qq{
\n}; print qq{
\n}; # 6 stat cards in responsive grid print qq{
\n}; # Card 1: cPanel Accounts my $acct_count = scalar @cpanel_accounts; print qq{
\n}; print qq{
cPanel Accounts
\n}; print qq{
$acct_count
\n}; print qq{
on this server
\n}; print qq{
\n}; # Card 2: Backed Up Accounts print qq{
\n}; print qq{
Backed Up Accounts
\n}; print qq{
$backed_up
\n}; my $remote_count_desc = scalar @remotes; print qq{
across $remote_count_desc remote(s)
\n}; print qq{
\n}; # Card 3: Total Snapshots print qq{
\n}; print qq{
Total Snapshots
\n}; print qq{
$total_snaps
\n}; print qq{
across all remotes
\n}; print qq{
\n}; # Card 4: Remotes my $rem_count = scalar @remotes; print qq{
\n}; print qq{
Remotes
\n}; print qq{
$rem_count
\n}; print qq{
configured destinations
\n}; print qq{
\n}; # Card 5: Schedules print qq{
\n}; print qq{
Schedules
\n}; print qq{
$sched_count
\n}; print qq{
active cron jobs
\n}; print qq{
\n}; # Card 6: Last Backup my ($badge_class, $badge_text); if ($last_status eq 'SUCCESS') { $badge_class = 'badge-success'; $badge_text = 'SUCCESS'; } elsif ($last_status eq 'FAILURE') { $badge_class = 'badge-error'; $badge_text = 'FAILURE'; } else { $badge_class = 'badge-neutral'; $badge_text = $has_stats ? 'UNKNOWN' : 'N/A'; } print qq{
\n}; print qq{
Last Backup
\n}; print qq{
$badge_text
\n}; if ($last_log) { my $esc_log = GnizaWHM::UI::esc($last_log); print qq{
$esc_log
\n}; } else { print qq{
click Refresh to collect
\n}; } print qq{
\n}; print qq{
\n}; } # Live Status card print qq{
\n
\n}; print qq{

Live Status

\n}; print qq{
\n}; print qq{
\n}; print qq{ \n}; print qq{ Checking...\n}; print qq{
\n}; print qq{
\n}; print qq{
\n
\n}; # Quick links print qq{
\n}; print qq{ \n}; print qq{
\n}; # Remote destinations my @remotes = GnizaWHM::UI::list_remotes(); print qq{
\n
\n}; print qq{

Configured Remotes

\n}; if (@remotes) { print qq{
\n}; print qq{\n}; print qq{\n}; for my $name (@remotes) { my $conf = GnizaWHM::Config::parse(GnizaWHM::UI::remote_conf_path($name), 'remote'); my $host = GnizaWHM::UI::esc($conf->{REMOTE_HOST} // ''); my $port = GnizaWHM::UI::esc($conf->{REMOTE_PORT} // '22'); my $retention = GnizaWHM::UI::esc($conf->{RETENTION_COUNT} // '30'); my $esc_name = GnizaWHM::UI::esc($name); print qq{\n}; } print qq{\n
NameHostPortRetention
$esc_name$host$port$retention
\n}; } else { print qq{

No remotes configured. Run the setup wizard to add one.

\n}; } print qq{
\n
\n}; # Active schedules my $schedules = GnizaWHM::Cron::get_current_schedules(); print qq{
\n
\n}; print qq{

Active Cron Schedules

\n}; if (keys %$schedules) { print qq{
\n}; print qq{\n}; print qq{\n}; for my $name (sort keys %$schedules) { my $esc_name = GnizaWHM::UI::esc($name); my ($timing, $remotes) = GnizaWHM::Cron::cron_to_human($schedules->{$name}); my $esc_timing = GnizaWHM::UI::esc($timing); my $esc_remotes = GnizaWHM::UI::esc($remotes); print qq{\n}; } print qq{\n
ScheduleTimingRemote Destination(s)
$esc_name$esc_timing$esc_remotes
\n}; } else { print qq{

No active gniza cron entries.

\n}; } print qq{
\n
\n}; # Overview my $version = GnizaWHM::UI::get_gniza_version(); print qq{
\n
\n}; print qq{

Overview

\n}; print qq{
\n}; print qq{\n}; print qq{
gniza version} . GnizaWHM::UI::esc($version) . qq{
\n}; print qq{
\n
\n}; # Inline JavaScript for live status polling print qq{ }; print GnizaWHM::UI::page_footer(); Whostmgr::HTMLInterface::footer(); # --- JSON status endpoint --- sub handle_status_json { print "Content-Type: application/json\r\n\r\n"; my $lock_file = '/var/run/gniza.lock'; my $running = 0; my $pid = ''; # Check lock file for running PID if (-f $lock_file) { if (open my $fh, '<', $lock_file) { $pid = <$fh> // ''; chomp $pid; close $fh; if ($pid =~ /^\d+$/ && kill(0, $pid)) { $running = 1; } else { $pid = ''; } } } # Find LOG_DIR from main config my $log_dir = '/var/log/gniza'; my $main_conf_file = '/etc/gniza/gniza.conf'; if (-f $main_conf_file) { my $cfg = GnizaWHM::Config::parse($main_conf_file, 'main'); $log_dir = $cfg->{LOG_DIR} if $cfg->{LOG_DIR} && $cfg->{LOG_DIR} ne ''; } # Find most recent gniza-*.log by mtime my $log_file = ''; my @lines; if (opendir my $dh, $log_dir) { my @logs = sort { (stat("$log_dir/$b"))[9] <=> (stat("$log_dir/$a"))[9] } grep { /^gniza-\d{8}-\d{6}\.log$/ } readdir($dh); closedir $dh; $log_file = $logs[0] // ''; } # Read last 15 lines if running and log exists if ($log_file && $running) { my $path = "$log_dir/$log_file"; if (open my $fh, '<', $path) { my @all = <$fh>; close $fh; my $start = @all > 15 ? @all - 15 : 0; @lines = @all[$start .. $#all]; chomp @lines; } } # Build JSON manually (no JSON module dependency) my $json = '{"running":' . ($running ? 'true' : 'false'); $json .= ',"pid":"' . _json_esc($pid) . '"'; $json .= ',"log_file":"' . _json_esc($log_file) . '"'; $json .= ',"lines":['; my @json_lines; for my $line (@lines) { my $level = 'INFO'; if ($line =~ /\]\s+\[(\w+)\]/) { $level = uc($1); } push @json_lines, '{"level":"' . _json_esc($level) . '","text":"' . _json_esc($line) . '"}'; } $json .= join(',', @json_lines); $json .= ']}'; print $json; } sub _json_esc { my ($s) = @_; $s //= ''; $s =~ s/\\/\\\\/g; $s =~ s/"/\\"/g; $s =~ s/\n/\\n/g; $s =~ s/\r/\\r/g; $s =~ s/\t/\\t/g; # Escape control characters $s =~ s/([\x00-\x1f])/sprintf("\\u%04x", ord($1))/ge; return $s; }