Refactor setup wizard to reuse remotes.cgi and schedules.cgi
Setup wizard step 1 (SSH key) stays as-is, then redirects to remotes.cgi?action=add&wizard=1 and schedules.cgi?action=add&wizard=1 instead of reimplementing its own forms. - Remove ~200 lines of duplicated form/handler code from setup.cgi - Wizard gets full feature parity (S3/GDrive, password auth, etc.) - key_path pre-fills REMOTE_KEY, remote_name pre-fills REMOTES - wizard=1 flag chains the flow: remote add → schedule add → index Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -267,6 +267,12 @@ sub handle_add {
|
||||
}
|
||||
my ($ok, $err) = GnizaWHM::Config::write($dest, \%data, \@GnizaWHM::Config::REMOTE_KEYS);
|
||||
if ($ok) {
|
||||
if ($form->{'wizard'}) {
|
||||
GnizaWHM::UI::set_flash('success', "Remote '$name' created. Now set up a schedule.");
|
||||
print "Status: 302 Found\r\n";
|
||||
print "Location: schedules.cgi?action=add&wizard=1&remote_name=" . _uri_escape($name) . "\r\n\r\n";
|
||||
exit;
|
||||
}
|
||||
GnizaWHM::UI::set_flash('success', "Remote '$name' created successfully.");
|
||||
print "Status: 302 Found\r\n";
|
||||
print "Location: remotes.cgi\r\n\r\n";
|
||||
@@ -289,16 +295,18 @@ sub handle_add {
|
||||
print GnizaWHM::UI::render_errors(\@errors);
|
||||
}
|
||||
|
||||
# Pre-populate from POST if validation failed, else empty
|
||||
# Pre-populate from POST if validation failed, else defaults
|
||||
my $conf = {};
|
||||
if ($method eq 'POST') {
|
||||
for my $key (@GnizaWHM::Config::REMOTE_KEYS) {
|
||||
$conf->{$key} = $form->{$key} // '';
|
||||
}
|
||||
} elsif ($form->{'key_path'}) {
|
||||
$conf->{REMOTE_KEY} = $form->{'key_path'};
|
||||
}
|
||||
my $name_val = GnizaWHM::UI::esc($form->{'remote_name'} // '');
|
||||
|
||||
render_remote_form($conf, $name_val, 0);
|
||||
render_remote_form($conf, $name_val, 0, $form->{'wizard'} ? 1 : 0);
|
||||
|
||||
print GnizaWHM::UI::page_footer();
|
||||
Whostmgr::HTMLInterface::footer();
|
||||
@@ -453,13 +461,16 @@ sub handle_delete {
|
||||
# ── Shared Form Renderer ────────────────────────────────────
|
||||
|
||||
sub render_remote_form {
|
||||
my ($conf, $name_val, $is_edit) = @_;
|
||||
my ($conf, $name_val, $is_edit, $wizard) = @_;
|
||||
|
||||
my $action_val = $is_edit ? 'edit' : 'add';
|
||||
my $remote_type = $conf->{REMOTE_TYPE} // 'ssh';
|
||||
|
||||
print qq{<form method="POST" action="remotes.cgi">\n};
|
||||
print qq{<input type="hidden" name="action" value="$action_val">\n};
|
||||
if ($wizard) {
|
||||
print qq{<input type="hidden" name="wizard" value="1">\n};
|
||||
}
|
||||
print GnizaWHM::UI::csrf_hidden_field();
|
||||
|
||||
if ($is_edit) {
|
||||
@@ -750,3 +761,9 @@ sub _password_field {
|
||||
print qq{ $hint_html\n} if $hint;
|
||||
print qq{</div>\n};
|
||||
}
|
||||
|
||||
sub _uri_escape {
|
||||
my ($str) = @_;
|
||||
$str =~ s/([^A-Za-z0-9._~-])/sprintf("%%%02X", ord($1))/ge;
|
||||
return $str;
|
||||
}
|
||||
|
||||
@@ -154,6 +154,12 @@ sub handle_add {
|
||||
my ($ok, $err) = GnizaWHM::Config::write($dest, \%data, \@GnizaWHM::Config::SCHEDULE_KEYS);
|
||||
if ($ok) {
|
||||
my ($cron_ok, $cron_err) = GnizaWHM::Cron::install_schedule($name);
|
||||
if ($form->{'wizard'}) {
|
||||
GnizaWHM::UI::set_flash('success', 'Setup complete! Your remote and schedule are configured.');
|
||||
print "Status: 302 Found\r\n";
|
||||
print "Location: index.cgi\r\n\r\n";
|
||||
exit;
|
||||
}
|
||||
if ($cron_ok) {
|
||||
GnizaWHM::UI::set_flash('success', "Schedule '$name' created and activated.");
|
||||
} else {
|
||||
@@ -185,10 +191,12 @@ sub handle_add {
|
||||
for my $key (@GnizaWHM::Config::SCHEDULE_KEYS) {
|
||||
$conf->{$key} = $form->{$key} // '';
|
||||
}
|
||||
} elsif ($form->{'remote_name'}) {
|
||||
$conf->{REMOTES} = $form->{'remote_name'};
|
||||
}
|
||||
my $name_val = GnizaWHM::UI::esc($form->{'schedule_name'} // '');
|
||||
|
||||
render_schedule_form($conf, $name_val, 0);
|
||||
render_schedule_form($conf, $name_val, 0, $form->{'wizard'} ? 1 : 0);
|
||||
|
||||
print GnizaWHM::UI::page_footer();
|
||||
Whostmgr::HTMLInterface::footer();
|
||||
@@ -371,11 +379,14 @@ sub _json_response {
|
||||
# ── Shared Form Renderer ────────────────────────────────────
|
||||
|
||||
sub render_schedule_form {
|
||||
my ($conf, $name_val, $is_edit) = @_;
|
||||
my ($conf, $name_val, $is_edit, $wizard) = @_;
|
||||
|
||||
my $action_val = $is_edit ? 'edit' : 'add';
|
||||
print qq{<form method="POST" action="schedules.cgi">\n};
|
||||
print qq{<input type="hidden" name="action" value="$action_val">\n};
|
||||
if ($wizard) {
|
||||
print qq{<input type="hidden" name="wizard" value="1">\n};
|
||||
}
|
||||
print GnizaWHM::UI::csrf_hidden_field();
|
||||
|
||||
if ($is_edit) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/local/cpanel/3rdparty/bin/perl
|
||||
# gniza WHM Plugin — Setup Wizard
|
||||
# 3-step wizard: SSH Key → Remote Destination → Schedule
|
||||
# Step 1: SSH Key selection, then redirects to remotes.cgi and schedules.cgi
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
@@ -8,54 +8,13 @@ use lib '/usr/local/cpanel/whostmgr/docroot/cgi/gniza-whm/lib';
|
||||
|
||||
use Whostmgr::HTMLInterface ();
|
||||
use Cpanel::Form ();
|
||||
use File::Copy ();
|
||||
use GnizaWHM::Config;
|
||||
use GnizaWHM::Validator;
|
||||
use GnizaWHM::Cron;
|
||||
use GnizaWHM::UI;
|
||||
|
||||
my $form = Cpanel::Form::parseform();
|
||||
my $method = $ENV{'REQUEST_METHOD'} // 'GET';
|
||||
my $step = $form->{'step'} // '1';
|
||||
|
||||
if ($step eq 'test') { handle_test_connection() }
|
||||
elsif ($step eq '2') { handle_step2() }
|
||||
elsif ($step eq '3') { handle_step3() }
|
||||
else { handle_step1() }
|
||||
|
||||
handle_step1();
|
||||
exit;
|
||||
|
||||
# ── Test Connection (JSON) ────────────────────────────────────
|
||||
|
||||
sub handle_test_connection {
|
||||
print "Content-Type: application/json\r\n\r\n";
|
||||
|
||||
my $host = $form->{'host'} // '';
|
||||
my $port = $form->{'port'} || '22';
|
||||
my $user = $form->{'user'} || 'root';
|
||||
my $key = $form->{'key'} // '';
|
||||
|
||||
if ($host eq '' || $key eq '') {
|
||||
print qq({"success":false,"message":"Host and SSH key path are required."});
|
||||
exit;
|
||||
}
|
||||
|
||||
my ($ok, $err) = GnizaWHM::UI::test_ssh_connection($host, $port, $user, $key);
|
||||
if ($ok) {
|
||||
print qq({"success":true,"message":"SSH connection successful."});
|
||||
} else {
|
||||
$err //= 'Unknown error';
|
||||
$err =~ s/\\/\\\\/g;
|
||||
$err =~ s/"/\\"/g;
|
||||
$err =~ s/\n/\\n/g;
|
||||
$err =~ s/\r/\\r/g;
|
||||
$err =~ s/\t/\\t/g;
|
||||
$err =~ s/[\x00-\x1f]//g;
|
||||
print qq({"success":false,"message":"SSH connection failed: $err"});
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
# ── Step 1: SSH Key ──────────────────────────────────────────
|
||||
|
||||
sub handle_step1 {
|
||||
@@ -63,13 +22,12 @@ sub handle_step1 {
|
||||
Whostmgr::HTMLInterface::defheader('gniza Setup Wizard', '', '/cgi/gniza-whm/setup.cgi');
|
||||
|
||||
print GnizaWHM::UI::page_header('gniza Setup Wizard');
|
||||
render_steps_indicator(1);
|
||||
|
||||
my $keys = GnizaWHM::UI::detect_ssh_keys();
|
||||
|
||||
print qq{<div class="card bg-base-100 shadow-sm border border-base-300 mb-6">\n<div class="card-body">\n};
|
||||
print qq{<h2 class="card-title text-sm">Step 1: SSH Key</h2>\n};
|
||||
print qq{<p>gniza uses SSH keys to connect to remote backup destinations. An SSH key must be set up before adding a remote.</p>\n};
|
||||
print qq{<p>gniza uses SSH keys to connect to remote backup destinations. Select an existing key or create one first.</p>\n};
|
||||
|
||||
if (@$keys) {
|
||||
print qq{<div class="my-4">\n};
|
||||
@@ -100,8 +58,9 @@ sub handle_step1 {
|
||||
print qq{</div>\n};
|
||||
print qq{</div>\n};
|
||||
|
||||
print qq{<form id="step1form" method="GET" action="setup.cgi">\n};
|
||||
print qq{<input type="hidden" name="step" value="2">\n};
|
||||
print qq{<form id="step1form" method="GET" action="remotes.cgi">\n};
|
||||
print qq{<input type="hidden" name="action" value="add">\n};
|
||||
print qq{<input type="hidden" name="wizard" value="1">\n};
|
||||
print qq{<div class="flex gap-2 mt-4">\n};
|
||||
print qq{ <button type="submit" class="btn btn-primary btn-sm" onclick="return gnizaPrepStep2()">Next: Configure Remote</button>\n};
|
||||
print qq{ <a href="index.cgi" class="btn btn-ghost btn-sm">Cancel</a>\n};
|
||||
@@ -121,8 +80,9 @@ sub handle_step1 {
|
||||
print qq{</div>\n};
|
||||
|
||||
unless (@$keys) {
|
||||
print qq{<form method="GET" action="setup.cgi" class="mt-4">\n};
|
||||
print qq{<input type="hidden" name="step" value="2">\n};
|
||||
print qq{<form method="GET" action="remotes.cgi" class="mt-4">\n};
|
||||
print qq{<input type="hidden" name="action" value="add">\n};
|
||||
print qq{<input type="hidden" name="wizard" value="1">\n};
|
||||
print qq{<div class="flex items-center gap-3 mb-2.5">\n};
|
||||
print qq{ <label class="w-44 font-medium text-sm" for="key_path_manual">Key path:</label>\n};
|
||||
print qq{ <input type="text" class="input input-bordered input-sm w-full max-w-xs" id="key_path_manual" name="key_path" value="/root/.ssh/id_ed25519">\n};
|
||||
@@ -161,372 +121,3 @@ JS
|
||||
print GnizaWHM::UI::page_footer();
|
||||
Whostmgr::HTMLInterface::footer();
|
||||
}
|
||||
|
||||
# ── Step 2: Remote Destination ───────────────────────────────
|
||||
|
||||
sub handle_step2 {
|
||||
my @errors;
|
||||
my $key_path = $form->{'key_path'} // '/root/.ssh/id_ed25519';
|
||||
|
||||
if ($method eq 'POST') {
|
||||
unless (GnizaWHM::UI::verify_csrf_token($form->{'gniza_csrf'})) {
|
||||
push @errors, 'Invalid or expired form token. Please try again.';
|
||||
}
|
||||
|
||||
my $name = $form->{'remote_name'} // '';
|
||||
my $name_err = GnizaWHM::Validator::validate_remote_name($name);
|
||||
push @errors, $name_err if $name_err;
|
||||
|
||||
if (!@errors && -f GnizaWHM::UI::remote_conf_path($name)) {
|
||||
push @errors, "A remote named '$name' already exists.";
|
||||
}
|
||||
|
||||
my %data;
|
||||
for my $key (@GnizaWHM::Config::REMOTE_KEYS) {
|
||||
$data{$key} = $form->{$key} // '';
|
||||
}
|
||||
|
||||
if (!@errors) {
|
||||
my $validation_errors = GnizaWHM::Validator::validate_remote_config(\%data);
|
||||
push @errors, @$validation_errors;
|
||||
}
|
||||
|
||||
if (!@errors) {
|
||||
my ($ssh_ok, $ssh_err) = GnizaWHM::UI::test_ssh_connection(
|
||||
$data{REMOTE_HOST},
|
||||
$data{REMOTE_PORT} || '22',
|
||||
$data{REMOTE_USER} || 'root',
|
||||
$data{REMOTE_KEY},
|
||||
);
|
||||
push @errors, "SSH connection test failed: $ssh_err" unless $ssh_ok;
|
||||
}
|
||||
|
||||
if (!@errors) {
|
||||
my $dest = GnizaWHM::UI::remote_conf_path($name);
|
||||
my $example = GnizaWHM::UI::remote_example_path();
|
||||
if (-f $example) {
|
||||
File::Copy::copy($example, $dest)
|
||||
or do { push @errors, "Failed to create remote file: $!"; goto RENDER_STEP2; };
|
||||
}
|
||||
my ($ok, $err) = GnizaWHM::Config::write($dest, \%data, \@GnizaWHM::Config::REMOTE_KEYS);
|
||||
if ($ok) {
|
||||
print "Status: 302 Found\r\n";
|
||||
print "Location: setup.cgi?step=3&remote_name=" . _uri_escape($name) . "\r\n\r\n";
|
||||
exit;
|
||||
} else {
|
||||
push @errors, "Failed to save remote config: $err";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RENDER_STEP2:
|
||||
|
||||
print "Content-Type: text/html\r\n\r\n";
|
||||
Whostmgr::HTMLInterface::defheader('gniza Setup Wizard', '', '/cgi/gniza-whm/setup.cgi');
|
||||
|
||||
print GnizaWHM::UI::page_header('gniza Setup Wizard');
|
||||
render_steps_indicator(2);
|
||||
|
||||
if (@errors) {
|
||||
print GnizaWHM::UI::render_errors(\@errors);
|
||||
}
|
||||
|
||||
my $conf = {};
|
||||
my $name_val = '';
|
||||
if ($method eq 'POST') {
|
||||
for my $key (@GnizaWHM::Config::REMOTE_KEYS) {
|
||||
$conf->{$key} = $form->{$key} // '';
|
||||
}
|
||||
$name_val = GnizaWHM::UI::esc($form->{'remote_name'} // '');
|
||||
} else {
|
||||
$conf->{REMOTE_KEY} = $key_path;
|
||||
$conf->{REMOTE_PORT} = '22';
|
||||
$conf->{REMOTE_USER} = 'root';
|
||||
$conf->{REMOTE_BASE} = '/backups';
|
||||
$conf->{RETENTION_COUNT} = '30';
|
||||
$conf->{BWLIMIT} = '0';
|
||||
}
|
||||
|
||||
print qq{<form method="POST" action="setup.cgi">\n};
|
||||
print qq{<input type="hidden" name="step" value="2">\n};
|
||||
print qq{<input type="hidden" name="key_path" value="} . GnizaWHM::UI::esc($key_path) . qq{">\n};
|
||||
print qq{<input type="hidden" name="REMOTE_TYPE" value="ssh">\n};
|
||||
print GnizaWHM::UI::csrf_hidden_field();
|
||||
|
||||
print qq{<div class="card bg-base-100 shadow-sm border border-base-300 mb-6">\n<div class="card-body">\n};
|
||||
print qq{<h2 class="card-title text-sm">Step 2: Remote Destination</h2>\n};
|
||||
print qq{<p>Configure the remote server where backups will be stored.</p>\n};
|
||||
|
||||
_wiz_field('remote_name', $name_val, 'Remote Name', 'Letters, digits, hyphens, underscores');
|
||||
_wiz_field_conf($conf, 'REMOTE_HOST', 'Hostname / IP', 'Required');
|
||||
_wiz_field_conf($conf, 'REMOTE_PORT', 'SSH Port', 'Default: 22');
|
||||
_wiz_field_conf($conf, 'REMOTE_USER', 'SSH User', 'Default: root');
|
||||
_wiz_field_conf($conf, 'REMOTE_KEY', 'SSH Private Key', 'Absolute path, required');
|
||||
_wiz_field_conf($conf, 'REMOTE_BASE', 'Remote Base Dir', 'Default: /backups');
|
||||
_wiz_field_conf($conf, 'BWLIMIT', 'Bandwidth Limit', 'KB/s, 0 = unlimited');
|
||||
_wiz_field_conf($conf, 'RETENTION_COUNT', 'Snapshots to Keep', 'Default: 30');
|
||||
_wiz_field_conf($conf, 'RSYNC_EXTRA_OPTS', 'Extra rsync Options', 'Optional');
|
||||
print qq{</div>\n</div>\n};
|
||||
|
||||
print qq{<div class="flex gap-2 mt-4">\n};
|
||||
print qq{ <button type="submit" class="btn btn-primary btn-sm">Next: Set Schedule</button>\n};
|
||||
print qq{ <button type="button" class="btn btn-secondary btn-sm" id="test-conn-btn" onclick="gnizaTestConnection()">Test Connection</button>\n};
|
||||
print qq{ <a href="setup.cgi" class="btn btn-ghost btn-sm">Back</a>\n};
|
||||
print qq{</div>\n};
|
||||
|
||||
print qq{<div id="gniza-alert-area" class="mt-4"></div>\n};
|
||||
|
||||
print qq{</form>\n};
|
||||
|
||||
print <<'JS';
|
||||
<script>
|
||||
function gnizaTestConnection() {
|
||||
var host = document.getElementById('REMOTE_HOST').value;
|
||||
var port = document.getElementById('REMOTE_PORT').value;
|
||||
var user = document.getElementById('REMOTE_USER').value;
|
||||
var key = document.getElementById('REMOTE_KEY').value;
|
||||
var btn = document.getElementById('test-conn-btn');
|
||||
|
||||
if (!host || !key) {
|
||||
gnizaToast('error', 'Host and SSH key path are required.');
|
||||
return;
|
||||
}
|
||||
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<span class="loading loading-spinner loading-xs"></span> Testing\u2026';
|
||||
|
||||
var fd = new FormData();
|
||||
fd.append('step', 'test');
|
||||
fd.append('host', host);
|
||||
fd.append('port', port);
|
||||
fd.append('user', user);
|
||||
fd.append('key', key);
|
||||
|
||||
fetch('setup.cgi', { method: 'POST', body: fd })
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(data) {
|
||||
gnizaToast(data.success ? 'success' : 'error', data.message);
|
||||
})
|
||||
.catch(function(err) {
|
||||
gnizaToast('error', 'Request failed: ' + err.toString());
|
||||
})
|
||||
.finally(function() {
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = 'Test Connection';
|
||||
});
|
||||
}
|
||||
|
||||
function gnizaToast(type, msg) {
|
||||
var area = document.getElementById('gniza-alert-area');
|
||||
if (!area) return;
|
||||
area.innerHTML = '';
|
||||
var el = document.createElement('div');
|
||||
el.className = 'alert alert-' + type;
|
||||
el.style.cssText = 'padding:12px 20px;border-radius:8px;font-size:14px';
|
||||
el.textContent = msg;
|
||||
area.appendChild(el);
|
||||
setTimeout(function() { el.style.opacity = '0'; }, type === 'error' ? 6000 : 3000);
|
||||
setTimeout(function() { area.innerHTML = ''; }, type === 'error' ? 6500 : 3500);
|
||||
}
|
||||
</script>
|
||||
JS
|
||||
|
||||
print GnizaWHM::UI::page_footer();
|
||||
Whostmgr::HTMLInterface::footer();
|
||||
}
|
||||
|
||||
# ── Step 3: Schedule (writes to schedules.d/) ────────────────
|
||||
|
||||
sub handle_step3 {
|
||||
my $remote_name = $form->{'remote_name'} // '';
|
||||
my @errors;
|
||||
|
||||
my $name_err = GnizaWHM::Validator::validate_remote_name($remote_name);
|
||||
if ($name_err) {
|
||||
GnizaWHM::UI::set_flash('error', 'Invalid remote name. Please start the wizard again.');
|
||||
print "Status: 302 Found\r\n";
|
||||
print "Location: setup.cgi\r\n\r\n";
|
||||
exit;
|
||||
}
|
||||
|
||||
my $remote_conf_path = GnizaWHM::UI::remote_conf_path($remote_name);
|
||||
unless (-f $remote_conf_path) {
|
||||
GnizaWHM::UI::set_flash('error', "Remote '$remote_name' not found. Please start the wizard again.");
|
||||
print "Status: 302 Found\r\n";
|
||||
print "Location: setup.cgi\r\n\r\n";
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($method eq 'POST') {
|
||||
unless (GnizaWHM::UI::verify_csrf_token($form->{'gniza_csrf'})) {
|
||||
push @errors, 'Invalid or expired form token. Please try again.';
|
||||
}
|
||||
|
||||
my $schedule = $form->{SCHEDULE} // '';
|
||||
|
||||
if ($schedule ne '' && !@errors) {
|
||||
# Create a schedule config in schedules.d/ targeting this remote
|
||||
my $sched_name = $remote_name;
|
||||
my %data = (
|
||||
SCHEDULE => $schedule,
|
||||
SCHEDULE_TIME => $form->{SCHEDULE_TIME} // '02:00',
|
||||
SCHEDULE_DAY => $form->{SCHEDULE_DAY} // '',
|
||||
SCHEDULE_CRON => $form->{SCHEDULE_CRON} // '',
|
||||
REMOTES => $remote_name,
|
||||
);
|
||||
|
||||
my $validation_errors = GnizaWHM::Validator::validate_schedule_config(\%data);
|
||||
push @errors, @$validation_errors;
|
||||
|
||||
if (!@errors) {
|
||||
my $sched_path = GnizaWHM::UI::schedule_conf_path($sched_name);
|
||||
my $example = GnizaWHM::UI::schedule_example_path();
|
||||
if (-f $example) {
|
||||
File::Copy::copy($example, $sched_path)
|
||||
or do { push @errors, "Failed to create schedule file: $!"; };
|
||||
}
|
||||
if (!@errors) {
|
||||
my ($ok, $err) = GnizaWHM::Config::write($sched_path, \%data, \@GnizaWHM::Config::SCHEDULE_KEYS);
|
||||
push @errors, "Failed to save schedule: $err" unless $ok;
|
||||
}
|
||||
}
|
||||
|
||||
if (!@errors) {
|
||||
my ($ok, $stdout, $stderr) = GnizaWHM::Cron::install_schedules();
|
||||
if (!$ok) {
|
||||
push @errors, "Schedule saved but cron install failed: $stderr";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!@errors) {
|
||||
GnizaWHM::UI::set_flash('success', 'Setup complete! Your first remote destination is configured.');
|
||||
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 Setup Wizard', '', '/cgi/gniza-whm/setup.cgi');
|
||||
|
||||
print GnizaWHM::UI::page_header('gniza Setup Wizard');
|
||||
render_steps_indicator(3);
|
||||
|
||||
if (@errors) {
|
||||
print GnizaWHM::UI::render_errors(\@errors);
|
||||
}
|
||||
|
||||
my $esc_name = GnizaWHM::UI::esc($remote_name);
|
||||
|
||||
# Load existing schedule data from schedules.d/ if it exists, else from POST
|
||||
my $conf = {};
|
||||
if (@errors && $method eq 'POST') {
|
||||
for my $key (@GnizaWHM::Config::SCHEDULE_KEYS) {
|
||||
$conf->{$key} = $form->{$key} // '';
|
||||
}
|
||||
} elsif (-f GnizaWHM::UI::schedule_conf_path($remote_name)) {
|
||||
$conf = GnizaWHM::Config::parse(GnizaWHM::UI::schedule_conf_path($remote_name), 'schedule');
|
||||
}
|
||||
|
||||
print qq{<form method="POST" action="setup.cgi">\n};
|
||||
print qq{<input type="hidden" name="step" value="3">\n};
|
||||
print qq{<input type="hidden" name="remote_name" value="$esc_name">\n};
|
||||
print GnizaWHM::UI::csrf_hidden_field();
|
||||
|
||||
print qq{<div class="card bg-base-100 shadow-sm border border-base-300 mb-6">\n<div class="card-body">\n};
|
||||
print qq{<h2 class="card-title text-sm">Step 3: Backup Schedule</h2>\n};
|
||||
print qq{<p>Set up an automatic backup schedule for remote <strong>$esc_name</strong>. You can skip this and configure it later.</p>\n};
|
||||
|
||||
my $sched = $conf->{SCHEDULE} // '';
|
||||
print qq{<div class="flex items-center gap-3 mb-2.5">\n};
|
||||
print qq{ <label class="w-44 font-medium text-sm" for="SCHEDULE">Schedule Type</label>\n};
|
||||
print qq{ <select class="select select-bordered select-sm w-full max-w-xs" id="SCHEDULE" name="SCHEDULE" onchange="gnizaScheduleChange()">\n};
|
||||
for my $opt ('', 'hourly', 'daily', 'weekly', 'monthly', 'custom') {
|
||||
my $sel = ($sched eq $opt) ? ' selected' : '';
|
||||
my $display = $opt eq '' ? '(none)' : $opt;
|
||||
print qq{ <option value="} . GnizaWHM::UI::esc($opt) . qq{"$sel>$display</option>\n};
|
||||
}
|
||||
print qq{ </select>\n};
|
||||
print qq{</div>\n};
|
||||
|
||||
_wiz_field_conf($conf, 'SCHEDULE_TIME', 'Time (HH:MM)', 'Default: 02:00');
|
||||
|
||||
print qq{<div id="gniza-schedule-day">\n};
|
||||
_wiz_field_conf($conf, 'SCHEDULE_DAY', 'Day', 'Day-of-week 0-6 (weekly) or day-of-month 1-28 (monthly)');
|
||||
print qq{</div>\n};
|
||||
|
||||
print qq{<div id="gniza-schedule-cron">\n};
|
||||
_wiz_field_conf($conf, 'SCHEDULE_CRON', 'Cron Expression', '5-field cron (for custom only)');
|
||||
print qq{</div>\n};
|
||||
|
||||
print qq{</div>\n</div>\n};
|
||||
|
||||
print qq{<div class="flex gap-2 mt-4">\n};
|
||||
print qq{ <button type="submit" class="btn btn-primary btn-sm">Finish Setup</button>\n};
|
||||
print qq{ <a href="index.cgi" class="btn btn-ghost btn-sm">Skip</a>\n};
|
||||
print qq{</div>\n};
|
||||
|
||||
print qq{</form>\n};
|
||||
|
||||
print <<'JS';
|
||||
<script>
|
||||
function gnizaScheduleChange() {
|
||||
var sel = document.getElementById('SCHEDULE').value;
|
||||
var dayDiv = document.getElementById('gniza-schedule-day');
|
||||
var cronDiv = document.getElementById('gniza-schedule-cron');
|
||||
dayDiv.style.display = (sel === 'hourly' || sel === 'weekly' || sel === 'monthly') ? '' : 'none';
|
||||
cronDiv.style.display = (sel === 'custom') ? '' : 'none';
|
||||
var dayLabel = dayDiv.querySelector('label');
|
||||
var dayHint = dayDiv.querySelector('.text-xs');
|
||||
if (sel === 'hourly') {
|
||||
if (dayLabel) dayLabel.textContent = 'Every N hours';
|
||||
if (dayHint) dayHint.textContent = '1-23 (default: 1 = every hour)';
|
||||
} else {
|
||||
if (dayLabel) dayLabel.textContent = 'Day';
|
||||
if (dayHint) dayHint.textContent = 'Day-of-week 0-6 (weekly) or day-of-month 1-28 (monthly)';
|
||||
}
|
||||
}
|
||||
gnizaScheduleChange();
|
||||
</script>
|
||||
JS
|
||||
|
||||
print GnizaWHM::UI::page_footer();
|
||||
Whostmgr::HTMLInterface::footer();
|
||||
}
|
||||
|
||||
# ── Shared Helpers ───────────────────────────────────────────
|
||||
|
||||
sub render_steps_indicator {
|
||||
my ($current) = @_;
|
||||
my @labels = ('SSH Key', 'Remote', 'Schedule');
|
||||
print qq{<ul class="steps mb-6 w-full">\n};
|
||||
for my $i (1..3) {
|
||||
my $class = 'step';
|
||||
$class .= ' step-primary' if $i <= $current;
|
||||
print qq{ <li class="$class">$labels[$i-1]</li>\n};
|
||||
}
|
||||
print qq{</ul>\n};
|
||||
}
|
||||
|
||||
sub _wiz_field {
|
||||
my ($name, $val, $label, $hint) = @_;
|
||||
$val = GnizaWHM::UI::esc($val // '');
|
||||
my $hint_html = $hint ? qq{ <span class="text-xs text-base-content/60 ml-2">$hint</span>} : '';
|
||||
print qq{<div class="flex items-center gap-3 mb-2.5">\n};
|
||||
print qq{ <label class="w-44 font-medium text-sm" for="$name">$label</label>\n};
|
||||
print qq{ <input type="text" class="input input-bordered input-sm w-full max-w-xs" id="$name" name="$name" value="$val">\n};
|
||||
print qq{ $hint_html\n} if $hint;
|
||||
print qq{</div>\n};
|
||||
}
|
||||
|
||||
sub _wiz_field_conf {
|
||||
my ($conf, $key, $label, $hint) = @_;
|
||||
_wiz_field($key, $conf->{$key}, $label, $hint);
|
||||
}
|
||||
|
||||
sub _uri_escape {
|
||||
my ($str) = @_;
|
||||
$str =~ s/([^A-Za-z0-9._~-])/sprintf("%%%02X", ord($1))/ge;
|
||||
return $str;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user