From 4408917aec1632fcef586d6a2d3b3de0dd25f3aa Mon Sep 17 00:00:00 2001 From: shuki Date: Thu, 5 Mar 2026 03:13:53 +0200 Subject: [PATCH] Redesign cPanel restore plugin to match WHM workflow Replace the 8-card category grid with a unified restore workflow: - index.live.cgi: now serves as Step 1 (remote + snapshot selection) - restore.live.cgi: Step 2 (Full Account/Selective mode toggle with type checkboxes, exclude paths, file browser), Step 3 (multi-type confirmation), Step 4 (multi-type execution via AdminBin) Also update cPanel plugin icon from gniza.svg source. Co-Authored-By: Claude Opus 4.6 --- cpanel/gniza/assets/gniza-cpanel-icon.png | Bin 2881 -> 1505 bytes cpanel/gniza/assets/gniza-cpanel-icon.svg | 19 +- cpanel/gniza/index.live.cgi | 168 ++-- cpanel/gniza/restore.live.cgi | 980 ++++++++++++++-------- 4 files changed, 735 insertions(+), 432 deletions(-) diff --git a/cpanel/gniza/assets/gniza-cpanel-icon.png b/cpanel/gniza/assets/gniza-cpanel-icon.png index 27b1905030f10fb64f93700cdefbd8d362b3124d..855df06028d6adef0703a4537aaa6154899a1d1a 100644 GIT binary patch literal 1505 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-s3dtTpz6=aiY77hwEes65fIC3&p+^0ZGx%MWUaJ zMZOe?ekv6GQYiXAUExci$f*fApNd4k7Kwc=76oZ36#G;JM4~|QOOZH4JJ1{`0-FX@ z12P#TC<-zbA_!4a2sRufD+)rzVo(GZ0c!*r3qe4afDA!H#Uem=A|YXP1<1NUenE8! z*gZfGg53aeCk&y90J%^puor+_VBo+MgIox67=!^N3q?N`zyu*4gajhC>8D0i~b{U||jx0XZ8I2*9`lI~(G1koUnZ|5z;Y z4d^dm`~pc(VgOkLOd;H@;Z;CtQ%R6tFpx$AOd{gqqRe7@uV1}(?U1tcqubR*#TA#H zYf2xG;pLOq^x^c0Q*WgoYw?w}w^fQV%PA?#vr3$P{`B$PN6#*(KDwM379Mxd(iubc^Ma*KW-1f2XslT)wBDR?Ni76TLXj4CrLWByV>Y zh7ML)41f$YL+?^mS!_#mvgcZ1sHFj|V_Kb3I)gLp+Wr zCrFqz1bRfU$P0)Hr#oFdprfd%%FAlRp};LXdD7%`$BPG6E!e@yHf2g^$Uz5J-&$jz z0O!fPq0M4W&D;9c$w|C;B`vTxar457h9Wa&nS= z3$hZnZ`ri%AoH5sw>LDsy=A`f^%Xo|VdpSR7v~F6-??jNwL{}_ahZz4M`~*RUa)=3 z|2^IzA;3U_rM2advS9p%wpMqSCP$YLlfX`WH8Vc0N5XAEml(2cG>I?Be&FMCYf2CM zk|oO)W_p}Iapn-S(dObAKwIAIto+Gp5WvA+u&`-Sl3oa}D&x}BfPi@+jLcIFHeOnl z^<_&7$F*6vc2yay$j(sFlAARvI_ltprEBM|*m5QD7AI@$-6Ka$lyRdP`(kYX@0Ff!9MFxE9N4KXmZGBCC> zwa_*&ure@sb;cEx0uUN<^HVa@DsgL=J@M}spau=N4JDbmsl_FUxdpiOoN2kE4b&qE u(i5DYR#Ki=l*&+EUaps!mtCBkSdglhUz9%kosASw5re0zpUXO@geCwSD{Ex{ delta 2850 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 z - - - - - - - - - - - - + + + + + + + diff --git a/cpanel/gniza/index.live.cgi b/cpanel/gniza/index.live.cgi index cbfc817..5f80e24 100644 --- a/cpanel/gniza/index.live.cgi +++ b/cpanel/gniza/index.live.cgi @@ -1,5 +1,5 @@ #!/usr/local/cpanel/3rdparty/bin/perl -# gniza cPanel Plugin — Restore Category Grid +# gniza cPanel Plugin — Step 1: Select Remote + Snapshot use strict; use warnings; @@ -27,6 +27,7 @@ my $remotes_raw = eval { Cpanel::AdminBin::Call::call('Gniza', 'Restore', 'LIST_ my @remotes = grep { $_ ne '' } split /\n/, $remotes_raw; print GnizaCPanel::UI::page_header('GNIZA Backups'); +print GnizaCPanel::UI::render_flash(); if (!@remotes) { print qq{
No backup remotes are available for restore. Please contact your server administrator.
\n}; @@ -36,76 +37,111 @@ if (!@remotes) { exit; } -# Category cards -my @categories = ( - { - type => 'account', - label => 'Full Backup', - desc => 'Restore entire account from backup', - icon => '', - }, - { - type => 'files', - label => 'Home Directory', - desc => 'Restore files and folders', - icon => '', - }, - { - type => 'database', - label => 'Databases', - desc => 'Restore MySQL databases', - icon => '', - }, - { - type => 'dbusers', - label => 'Database Users', - desc => 'Restore database users and grants', - icon => '', - }, - { - type => 'cron', - label => 'Cron Jobs', - desc => 'Restore scheduled tasks', - icon => '', - }, - { - type => 'domains', - label => 'Domains', - desc => 'Restore domain and DNS configuration', - icon => '', - }, - { - type => 'ssl', - label => 'SSL Certificates', - desc => 'Restore SSL certificates', - icon => '', - }, - { - type => 'mailbox', - label => 'Email Accounts', - desc => 'Restore mailboxes and email', - icon => '', - }, -); +print qq{
\n
\n}; +print qq{

Select Backup Source

\n}; -print qq{
\n}; +# Remote dropdown +print qq{
\n}; +print qq{ \n}; +print qq{ \n}; +print qq{
\n}; -for my $cat (@categories) { - my $type = GnizaCPanel::UI::esc($cat->{type}); - my $label = GnizaCPanel::UI::esc($cat->{label}); - my $desc = GnizaCPanel::UI::esc($cat->{desc}); - my $icon = $cat->{icon}; # Already safe SVG +# Snapshot dropdown (populated via AJAX) +print qq{
\n}; +print qq{ \n}; +print qq{ \n}; +print qq{
\n}; - print qq{\n}; - print qq{
\n}; - print qq{
$icon
\n}; - print qq{

$label

\n}; - print qq{

$desc

\n}; - print qq{
\n}; - print qq{
\n}; +print qq{
\n
\n}; + +print qq{
\n}; +print qq{ \n}; +print qq{
\n}; + +# JavaScript for snapshot loading and navigation +print <<"END_JS"; + +END_JS print GnizaCPanel::UI::page_footer(); print $cpanel->footer(); diff --git a/cpanel/gniza/restore.live.cgi b/cpanel/gniza/restore.live.cgi index ad9ab9a..9edb1c3 100644 --- a/cpanel/gniza/restore.live.cgi +++ b/cpanel/gniza/restore.live.cgi @@ -23,7 +23,7 @@ my $cpanel = Cpanel::LiveAPI->new(); END { $cpanel->end() if $cpanel } my $form = Cpanel::Form::parseform(); my $method = $ENV{'REQUEST_METHOD'} // 'GET'; -my $step = $form->{'step'} // '1'; +my $step = $form->{'step'} // ''; my %TYPE_LABELS = ( account => 'Full Backup', @@ -36,15 +36,17 @@ my %TYPE_LABELS = ( ssl => 'SSL Certificates', ); -my %SIMPLE_TYPES = map { $_ => 1 } qw(account cron); - # JSON endpoints if ($step eq 'fetch_snapshots') { handle_fetch_snapshots() } elsif ($step eq 'fetch_options') { handle_fetch_options() } elsif ($step eq '2') { handle_step2() } elsif ($step eq '3') { handle_step3() } elsif ($step eq '4') { handle_step4() } -else { handle_step1() } +else { + # Default: redirect to index + print "Status: 302 Found\r\n"; + print "Location: index.live.cgi\r\n\r\n"; +} exit; @@ -75,6 +77,12 @@ sub _adminbin_call { return (1, $result, ''); } +sub _uri_escape { + my $str = shift // ''; + $str =~ s/([^A-Za-z0-9\-._~])/sprintf("%%%02X", ord($1))/ge; + return $str; +} + # ── JSON: fetch snapshots ───────────────────────────────────── sub handle_fetch_snapshots { @@ -161,234 +169,215 @@ sub handle_fetch_options { print qq({"options":[$json_arr]}); } -# ── Step 1: Select Remote + Snapshot ────────────────────────── - -sub handle_step1 { - my $type = $form->{'type'} // 'account'; - unless (exists $TYPE_LABELS{$type}) { - $type = 'account'; - } - - print "Content-Type: text/html\r\n\r\n"; - print $cpanel->header('GNIZA Backups'); - print GnizaCPanel::UI::page_header('Restore: ' . ($TYPE_LABELS{$type} // $type)); - print GnizaCPanel::UI::render_flash(); - - # Get allowed remotes - my $remotes_raw = eval { Cpanel::AdminBin::Call::call('Gniza', 'Restore', 'LIST_ALLOWED_REMOTES') } // ''; - my @remotes = grep { $_ ne '' } split /\n/, $remotes_raw; - - unless (@remotes) { - print qq{
No backup remotes are available. Please contact your server administrator.
\n}; - print qq{Back\n}; - print GnizaCPanel::UI::page_footer(); - print $cpanel->footer(); - return; - } - - my $esc_type = GnizaCPanel::UI::esc($type); - my $type_label = GnizaCPanel::UI::esc($TYPE_LABELS{$type} // $type); - - print qq{
\n
\n}; - print qq{

Step 1: Select Source

\n}; - print qq{

Restore type: $type_label

\n}; - - # Remote dropdown - print qq{
\n}; - print qq{ \n}; - print qq{ \n}; - print qq{
\n}; - - # Snapshot dropdown (populated via AJAX) - print qq{
\n}; - print qq{ \n}; - print qq{ \n}; - print qq{
\n}; - - print qq{
\n
\n}; - - # For simple types, go directly to step 3 (confirm); others go to step 2 - my $next_step = $SIMPLE_TYPES{$type} ? '3' : '2'; - - print qq{
\n}; - print qq{ \n}; - print qq{ Back\n}; - print qq{
\n}; - - # JavaScript for snapshot loading and navigation - _print_step1_js($esc_type, $next_step); - - print GnizaCPanel::UI::page_footer(); - print $cpanel->footer(); -} - -sub _print_step1_js { - my ($esc_type, $next_step) = @_; - print <<"END_JS"; - -END_JS -} - -# ── Step 2: Select specific item ───────────────────────────── +# ── Step 2: Choose Restore Options ─────────────────────────── sub handle_step2 { - my $type = $form->{'type'} // 'account'; my $remote = $form->{'remote'} // ''; my $timestamp = $form->{'timestamp'} // ''; - $type = 'account' unless exists $TYPE_LABELS{$type}; - if ($remote eq '' || $timestamp eq '') { GnizaCPanel::UI::set_flash('error', 'Remote and snapshot are required.'); print "Status: 302 Found\r\n"; - print "Location: restore.live.cgi?type=$type\r\n\r\n"; + print "Location: index.live.cgi\r\n\r\n"; exit; } + # Fetch snapshots server-side via AdminBin + my ($ok, $stdout, $err) = _adminbin_call('LIST_SNAPSHOTS', $remote); + + my @snapshots; + if ($ok) { + for my $line (split /\n/, $stdout) { + if ($line =~ /^\s+(\d{4}-\d{2}-\d{2}T\d{6})/) { + push @snapshots, $1; + } + } + } + print "Content-Type: text/html\r\n\r\n"; print $cpanel->header('GNIZA Backups'); - print GnizaCPanel::UI::page_header('Restore: ' . ($TYPE_LABELS{$type} // $type)); + print GnizaCPanel::UI::page_header('Restore Options'); print GnizaCPanel::UI::render_flash(); - my $esc_type = GnizaCPanel::UI::esc($type); my $esc_remote = GnizaCPanel::UI::esc($remote); my $esc_timestamp = GnizaCPanel::UI::esc($timestamp); + print qq{
\n}; + print qq{\n}; + print qq{\n}; + print qq{
\n
\n}; - print qq{

Step 2: Select Items

\n}; - print qq{

Remote: $esc_remote · Snapshot: $esc_timestamp

\n}; + print qq{

Step 2: Choose Restore Options

\n}; + print qq{

Remote: $esc_remote

\n}; - if ($type eq 'files') { - _render_file_picker(); + # Snapshot dropdown + print qq{
\n}; + print qq{ \n}; + if (@snapshots) { + print qq{ \n}; + } else { + print qq{ No snapshots found\n}; } - elsif ($type eq 'database' || $type eq 'dbusers' || $type eq 'mailbox' || $type eq 'domains' || $type eq 'ssl') { - my $item_label = { - database => 'Databases', - dbusers => 'Database Users', - mailbox => 'Mailboxes', - domains => 'Domains', - ssl => 'SSL Certificates', - }->{$type}; - - print qq{

$item_label

\n}; - print qq{\n}; - print qq{
\n}; - print qq{ Loading...\n}; - print qq{
\n}; - } - elsif ($type eq 'cron') { - print qq{

Cron Jobs Preview

\n}; - print qq{
\n}; - print qq{ Loading...\n}; - print qq{
\n}; - } - - print qq{
\n
\n}; - - print qq{
\n}; - print qq{ \n}; - print qq{ Back\n}; print qq{
\n}; - _print_step2_js($esc_type, $esc_remote, $esc_timestamp); - - print GnizaCPanel::UI::page_footer(); - print $cpanel->footer(); -} - -sub _render_file_picker { + # Restore mode toggle print qq{
\n}; - print qq{ \n}; - print qq{
\n}; - print qq{ \n}; - print qq{ \n}; + print qq{ \n}; + print qq{
\n}; + print qq{ \n}; + print qq{ \n}; print qq{
\n}; print qq{
\n}; - print qq{

Leave empty to restore all files.

\n}; + + # Exclude paths section + print qq{
\n}; + print qq{
\n}; + print qq{

Directories and Files to Exclude

\n}; + print qq{
\n}; + print qq{ \n}; + print qq{ \n}; + print qq{ \n}; + print qq{
\n}; + print qq{

Exclude files and directories from restoration

\n}; + print qq{
\n}; + print qq{ \n}; + print qq{
\n}; + print qq{
\n}; + + # Exclude modal + print qq{\n}; + print qq{\n}; + print qq{\n}; + print qq{\n}; + + # Hidden field for account type in full mode + print qq{\n}; + + # Selective type buttons (hidden by default) + my @selective_types = ( + ['files', 'Files'], + ['database', 'Database'], + ['dbusers', 'Database Users'], + ['mailbox', 'Mailbox'], + ['cron', 'Cron'], + ['domains', 'Domains'], + ['ssl', 'SSL'], + ); + + print qq{\n}; # File browser modal print qq{\n}; @@ -405,47 +394,119 @@ sub _render_file_picker { print qq{ \n}; print qq{
\n}; print qq{
\n}; - print qq{\n}; + print qq{\n}; print qq{\n}; + + print qq{
\n\n}; + + if (@snapshots) { + print qq{
\n}; + print qq{ \n}; + print qq{ Back\n}; + print qq{
\n}; + } else { + print qq{Back\n}; + } + + print qq{\n}; + + # JavaScript for dynamic dropdowns and interactive elements + _print_step2_js($esc_remote); + + print GnizaCPanel::UI::page_footer(); + print $cpanel->footer(); } sub _print_step2_js { - my ($esc_type, $esc_remote, $esc_timestamp) = @_; + my ($esc_remote) = @_; print <<"END_JS"; END_JS } @@ -692,18 +869,34 @@ END_JS # ── Step 3: Confirmation ───────────────────────────────────── sub handle_step3 { - my $type = $form->{'type'} // 'account'; - my $remote = $form->{'remote'} // ''; - my $timestamp = $form->{'timestamp'} // ''; - my $path = $form->{'path'} // ''; - my $items = $form->{'items'} // ''; + my $remote = $form->{'remote'} // ''; + my $timestamp = $form->{'timestamp'} // ''; + my $path = $form->{'path'} // ''; + my $dbnames = $form->{'dbnames'} // ''; + my $dbuser_names = $form->{'dbuser_names'} // ''; + my $emails = $form->{'emails'} // ''; + my $domain_names = $form->{'domain_names'} // ''; + my $ssl_names = $form->{'ssl_names'} // ''; + my $exclude_paths = $form->{'exclude_paths'} // ''; - $type = 'account' unless exists $TYPE_LABELS{$type}; + # Collect selected types from type_* checkboxes + my @all_type_keys = qw(account files database mailbox cron dbusers domains ssl); + my @selected_types; + for my $t (@all_type_keys) { + push @selected_types, $t if ($form->{"type_$t"} // '') eq '1'; + } if ($remote eq '' || $timestamp eq '') { GnizaCPanel::UI::set_flash('error', 'Remote and snapshot are required.'); print "Status: 302 Found\r\n"; - print "Location: restore.live.cgi?type=$type\r\n\r\n"; + print "Location: index.live.cgi\r\n\r\n"; + exit; + } + + unless (@selected_types) { + GnizaCPanel::UI::set_flash('error', 'Please select at least one restore type.'); + print "Status: 302 Found\r\n"; + print "Location: restore.live.cgi?step=2&remote=" . _uri_escape($remote) . "×tamp=" . _uri_escape($timestamp) . "\r\n\r\n"; exit; } @@ -712,27 +905,55 @@ sub handle_step3 { print GnizaCPanel::UI::page_header('Restore: Confirm'); print GnizaCPanel::UI::render_flash(); - my $esc_type = GnizaCPanel::UI::esc($type); my $esc_remote = GnizaCPanel::UI::esc($remote); my $esc_timestamp = GnizaCPanel::UI::esc($timestamp); - my $type_label = GnizaCPanel::UI::esc($TYPE_LABELS{$type} // $type); my $user = GnizaCPanel::UI::esc(GnizaCPanel::UI::get_current_user()); + my $types_display = join(', ', map { GnizaCPanel::UI::esc($TYPE_LABELS{$_} // $_) } @selected_types); + print qq{
\n
\n}; print qq{

Step 3: Confirm Restore

\n}; print qq{
\n}; print qq{\n}; print qq{\n}; print qq{\n}; - print qq{\n}; + print qq{\n}; - if ($type eq 'files') { + # Show sub-field details for applicable types + if (grep { $_ eq 'files' } @selected_types) { my $path_display = $path ne '' ? GnizaCPanel::UI::esc($path) : 'All files'; print qq{\n}; - } elsif ($type ne 'account' && $type ne 'cron' && $items ne '') { - my $items_display = $items eq '__ALL__' ? 'All' : GnizaCPanel::UI::esc($items); - $items_display =~ s/,/, /g; - print qq{\n}; + } + if (grep { $_ eq 'database' } @selected_types) { + my $db_display = ($dbnames eq '' || $dbnames eq '__ALL__') ? 'All databases' : GnizaCPanel::UI::esc($dbnames); + $db_display =~ s/,/, /g; + print qq{\n}; + } + if (grep { $_ eq 'dbusers' } @selected_types) { + my $dbu_display = ($dbuser_names eq '' || $dbuser_names eq '__ALL__') ? 'All database users' : GnizaCPanel::UI::esc($dbuser_names); + $dbu_display =~ s/,/, /g; + print qq{\n}; + } + if (grep { $_ eq 'mailbox' } @selected_types) { + my $mb_display = ($emails eq '' || $emails eq '__ALL__') ? 'All mailboxes' : GnizaCPanel::UI::esc($emails); + $mb_display =~ s/,/, /g; + print qq{\n}; + } + if (grep { $_ eq 'domains' } @selected_types) { + my $dom_display = ($domain_names eq '' || $domain_names eq '__ALL__') ? 'All domains' : GnizaCPanel::UI::esc($domain_names); + $dom_display =~ s/,/, /g; + print qq{\n}; + } + if (grep { $_ eq 'ssl' } @selected_types) { + my $ssl_display = ($ssl_names eq '' || $ssl_names eq '__ALL__') ? 'All certificates' : GnizaCPanel::UI::esc($ssl_names); + $ssl_display =~ s/,/, /g; + print qq{\n}; + } + + if ($exclude_paths ne '') { + my $exclude_display = GnizaCPanel::UI::esc($exclude_paths); + $exclude_display =~ s/,/, /g; + print qq{\n}; } print qq{
Account$user
Remote$esc_remote
Snapshot$esc_timestamp
Restore Type$type_label
Restore Types$types_display
Path$path_display
Items$items_display
Database$db_display
Database Users$dbu_display
Mailbox$mb_display
Domains$dom_display
SSL$ssl_display
Exclude$exclude_display
\n}; @@ -740,11 +961,18 @@ sub handle_step3 { print qq{
\n}; print qq{\n}; - print qq{\n}; print qq{\n}; print qq{\n}; + for my $t (@selected_types) { + print qq{\n}; + } print qq{\n}; - print qq{\n}; + print qq{\n}; + print qq{\n}; + print qq{\n}; + print qq{\n}; + print qq{\n}; + print qq{\n}; print GnizaCPanel::UI::csrf_hidden_field(); print qq{
\n}; @@ -767,68 +995,112 @@ sub handle_step4 { exit; } - my $type = $form->{'type'} // 'account'; - my $remote = $form->{'remote'} // ''; - my $timestamp = $form->{'timestamp'} // ''; - my $path = $form->{'path'} // ''; - my $items = $form->{'items'} // ''; + my $remote = $form->{'remote'} // ''; + my $timestamp = $form->{'timestamp'} // ''; + my $path = $form->{'path'} // ''; + my $dbnames = $form->{'dbnames'} // ''; + my $dbuser_names = $form->{'dbuser_names'} // ''; + my $emails = $form->{'emails'} // ''; + my $domain_names = $form->{'domain_names'} // ''; + my $ssl_names = $form->{'ssl_names'} // ''; + my $exclude_paths = $form->{'exclude_paths'} // ''; - $type = 'account' unless exists $TYPE_LABELS{$type}; + # Collect selected types + my @all_type_keys = qw(account files database mailbox cron dbusers domains ssl); + my @selected_types; + for my $t (@all_type_keys) { + push @selected_types, $t if ($form->{"type_$t"} // '') eq '1'; + } + + unless (@selected_types) { + GnizaCPanel::UI::set_flash('error', 'No restore types selected.'); + print "Status: 302 Found\r\n"; + print "Location: index.live.cgi\r\n\r\n"; + exit; + } print "Content-Type: text/html\r\n\r\n"; print $cpanel->header('GNIZA Backups'); - print GnizaCPanel::UI::page_header('Restore: Results'); - - my $type_label = GnizaCPanel::UI::esc($TYPE_LABELS{$type}); + print GnizaCPanel::UI::page_header('Restore Results'); print qq{
\n
\n}; - print qq{

Restore Results: $type_label

\n}; + print qq{

Restore Results

\n}; my @results; - my %action_map = ( - account => 'RESTORE_ACCOUNT', - files => 'RESTORE_FILES', - database => 'RESTORE_DATABASE', - mailbox => 'RESTORE_MAILBOX', - cron => 'RESTORE_CRON', - dbusers => 'RESTORE_DBUSERS', - domains => 'RESTORE_DOMAINS', - ssl => 'RESTORE_SSL', - ); + for my $type (@selected_types) { + my $type_label = $TYPE_LABELS{$type} // $type; - my $action = $action_map{$type}; - unless ($action) { - push @results, { ok => 0, label => $type, msg => 'Unknown restore type' }; - _render_results(\@results); - print qq{
\n
\n}; - print qq{Back to Categories\n}; - print GnizaCPanel::UI::page_footer(); - print $cpanel->footer(); - return; - } - - if ($type eq 'account') { - my ($ok, $stdout, $err) = _adminbin_call($action, $remote, $timestamp, ''); - push @results, { ok => $ok, label => 'Full Account', msg => $ok ? $stdout : $err }; - } - elsif ($type eq 'cron') { - my ($ok, $stdout, $err) = _adminbin_call($action, $remote, $timestamp); - push @results, { ok => $ok, label => 'Cron Jobs', msg => $ok ? $stdout : $err }; - } - elsif ($type eq 'files') { - my ($ok, $stdout, $err) = _adminbin_call($action, $remote, $timestamp, $path, ''); - push @results, { ok => $ok, label => 'Files', msg => $ok ? $stdout : $err }; - } - elsif ($type eq 'database' || $type eq 'dbusers' || $type eq 'mailbox' || $type eq 'domains' || $type eq 'ssl') { - if ($items eq '' || $items eq '__ALL__') { - my ($ok, $stdout, $err) = _adminbin_call($action, $remote, $timestamp, ''); + if ($type eq 'account') { + my ($ok, $stdout, $err) = _adminbin_call('RESTORE_ACCOUNT', $remote, $timestamp, $exclude_paths); push @results, { ok => $ok, label => $type_label, msg => $ok ? $stdout : $err }; - } else { - for my $item (split /,/, $items) { - next if $item eq ''; - my ($ok, $stdout, $err) = _adminbin_call($action, $remote, $timestamp, $item); - push @results, { ok => $ok, label => $item, msg => $ok ? $stdout : $err }; + } + elsif ($type eq 'files') { + my ($ok, $stdout, $err) = _adminbin_call('RESTORE_FILES', $remote, $timestamp, $path, $exclude_paths); + push @results, { ok => $ok, label => $type_label, msg => $ok ? $stdout : $err }; + } + elsif ($type eq 'cron') { + my ($ok, $stdout, $err) = _adminbin_call('RESTORE_CRON', $remote, $timestamp); + push @results, { ok => $ok, label => $type_label, msg => $ok ? $stdout : $err }; + } + elsif ($type eq 'database') { + if ($dbnames eq '' || $dbnames eq '__ALL__') { + my ($ok, $stdout, $err) = _adminbin_call('RESTORE_DATABASE', $remote, $timestamp, ''); + push @results, { ok => $ok, label => $type_label, msg => $ok ? $stdout : $err }; + } else { + for my $item (split /,/, $dbnames) { + next if $item eq ''; + my ($ok, $stdout, $err) = _adminbin_call('RESTORE_DATABASE', $remote, $timestamp, $item); + push @results, { ok => $ok, label => $item, msg => $ok ? $stdout : $err }; + } + } + } + elsif ($type eq 'dbusers') { + if ($dbuser_names eq '' || $dbuser_names eq '__ALL__') { + my ($ok, $stdout, $err) = _adminbin_call('RESTORE_DBUSERS', $remote, $timestamp, ''); + push @results, { ok => $ok, label => $type_label, msg => $ok ? $stdout : $err }; + } else { + for my $item (split /,/, $dbuser_names) { + next if $item eq ''; + my ($ok, $stdout, $err) = _adminbin_call('RESTORE_DBUSERS', $remote, $timestamp, $item); + push @results, { ok => $ok, label => $item, msg => $ok ? $stdout : $err }; + } + } + } + elsif ($type eq 'mailbox') { + if ($emails eq '' || $emails eq '__ALL__') { + my ($ok, $stdout, $err) = _adminbin_call('RESTORE_MAILBOX', $remote, $timestamp, ''); + push @results, { ok => $ok, label => $type_label, msg => $ok ? $stdout : $err }; + } else { + for my $item (split /,/, $emails) { + next if $item eq ''; + my ($ok, $stdout, $err) = _adminbin_call('RESTORE_MAILBOX', $remote, $timestamp, $item); + push @results, { ok => $ok, label => $item, msg => $ok ? $stdout : $err }; + } + } + } + elsif ($type eq 'domains') { + if ($domain_names eq '' || $domain_names eq '__ALL__') { + my ($ok, $stdout, $err) = _adminbin_call('RESTORE_DOMAINS', $remote, $timestamp, ''); + push @results, { ok => $ok, label => $type_label, msg => $ok ? $stdout : $err }; + } else { + for my $item (split /,/, $domain_names) { + next if $item eq ''; + my ($ok, $stdout, $err) = _adminbin_call('RESTORE_DOMAINS', $remote, $timestamp, $item); + push @results, { ok => $ok, label => $item, msg => $ok ? $stdout : $err }; + } + } + } + elsif ($type eq 'ssl') { + if ($ssl_names eq '' || $ssl_names eq '__ALL__') { + my ($ok, $stdout, $err) = _adminbin_call('RESTORE_SSL', $remote, $timestamp, ''); + push @results, { ok => $ok, label => $type_label, msg => $ok ? $stdout : $err }; + } else { + for my $item (split /,/, $ssl_names) { + next if $item eq ''; + my ($ok, $stdout, $err) = _adminbin_call('RESTORE_SSL', $remote, $timestamp, $item); + push @results, { ok => $ok, label => $item, msg => $ok ? $stdout : $err }; + } } } } @@ -837,7 +1109,7 @@ sub handle_step4 { print qq{
\n
\n}; - print qq{Back to Categories\n}; + print qq{Back to Home\n}; print GnizaCPanel::UI::page_footer(); print $cpanel->footer();