BastionGuard/webui/wizard.php
specialworld83 f0f913a209 Release 2.0
2026-07-15 10:52:22 +02:00

318 lines
23 KiB
PHP

<?php
include 'auth.php';
bg_require_admin();
require_once __DIR__ . '/includes/config_manager.php';
$page_title = bg_t('wizard_page_title');
$active_page = 'wizard.php';
$web = bg_web_options();
$target = trim((string)($web['desktop_user'] ?? bg_detect_desktop_user()));
$httpPort = (int)($web['http_port'] ?? 81);
$httpsPort = (int)($web['https_port'] ?? 444);
$labels = bg_wizard_service_labels();
$defaultServices = bg_wizard_services_default();
$status = bg_wizard_config_status($target ?: null);
$scan = bg_config_scan_dir($target ?: null);
$steps = bg_wizard_steps();
$stepDefaults = bg_wizard_step_defaults_src();
$desktopMode = bg_desktop_mode_status($target ?: null);
$wizardLock = bg_wizard_lock_status($target ?: null, $scan, $status, $desktopMode);
$log = '';
$messages = [];
$errors = [];
if (!empty($_GET['from_setup'])) {
$messages[] = bg_t('wizard_started_after_setup');
}
$systemStepOrder = ['resolv','dnsmasq','firewall','nftables','certs','webconf','native','useragent','banks'];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
csrf_validate();
$action = $_POST['action'] ?? '';
$target = trim((string)($_POST['desktop_user'] ?? $target));
$httpPort = (int)($_POST['http_port'] ?? $httpPort);
$httpsPort = (int)($_POST['https_port'] ?? $httpsPort);
if ($httpPort < 1 || $httpPort > 65535) $httpPort = 81;
if ($httpsPort < 1 || $httpsPort > 65535) $httpsPort = 444;
$web['desktop_user'] = $target;
$web['http_port'] = $httpPort;
$web['https_port'] = $httpsPort;
unset($web['language'], $web['theme']);
bg_write_json('web_options.json', $web);
$status = bg_wizard_config_status($target ?: null);
$scan = bg_config_scan_dir($target ?: null);
$desktopMode = bg_desktop_mode_status($target ?: null);
$wizardLock = bg_wizard_lock_status($target ?: null, $scan, $status, $desktopMode);
if ($action === 'run_initial_wizard') {
if (!empty($wizardLock['locked'])) {
$errors[] = bg_t('wizard_locked_no_post');
$log .= "Wizard locked: existing desktop configuration detected. No changes made.\n";
} elseif ($target === '') {
$errors[] = bg_t('wizard_missing_desktop_user');
} else {
$selectedServices = bg_wizard_selected_services_from_post($_POST, $defaultServices);
$selectedSteps = bg_wizard_selected_steps_from_post($_POST);
$log .= "### " . bg_t('wizard_config_file_title') . "\n";
$cfgRes = bg_write_first_run_config($target, $selectedServices);
$log .= trim((string)($cfgRes['output'] ?? '')) . "\n";
if (($cfgRes['code'] ?? 1) !== 0) {
$errors[] = bg_t('wizard_config_error');
} else {
$messages[] = bg_t('wizard_config_created');
}
if (empty($errors)) {
$log .= "\n### " . bg_t('wizard_services_log_title') . "\n";
$svcRes = bg_apply_services_from_config($selectedServices);
$log .= trim((string)($svcRes['output'] ?? '')) . "\n";
}
if (empty($errors)) {
$opts = ['desktop_user'=>$target, 'http_port'=>$httpPort, 'https_port'=>$httpsPort];
foreach ($selectedSteps as $k => $v) $opts[$k] = $v;
if (count(array_filter($selectedSteps)) > 0) {
$sysRes = bg_wizard_apply($opts);
$log .= "\n### " . bg_t('wizard_system_log_title') . "\n" . trim((string)($sysRes['output'] ?? '')) . "\n";
if (($sysRes['code'] ?? 1) === 0) $messages[] = bg_t('wizard_system_completed');
else $errors[] = bg_t('wizard_system_error');
} else {
$log .= "\n### " . bg_t('wizard_system_log_title') . "\n" . bg_t('wizard_no_system_steps_selected') . "\n";
$messages[] = bg_t('wizard_system_skipped');
}
}
}
$status = bg_wizard_config_status($target ?: null);
$scan = bg_config_scan_dir($target ?: null);
$desktopMode = bg_desktop_mode_status($target ?: null);
$wizardLock = bg_wizard_lock_status($target ?: null, $scan, $status, $desktopMode);
}
}
$currentServices = is_array($status['data'] ?? null) && is_array($status['data']['services'] ?? null) ? $status['data']['services'] : $defaultServices;
$lang = bg_lang_attr();
$theme = bg_theme();
$bsTheme = $theme === 'dark' ? 'dark' : 'light';
$wizardLocked = !empty($wizardLock['locked']);
$stepTitles = [
bg_t('wizard_step_welcome_ui'),
bg_t('wizard_step_services_ui'),
bg_t('wizard_step_resolver'),
bg_t('wizard_step_dnsmasq'),
bg_t('wizard_step_firewall'),
bg_t('wizard_step_nftables_short'),
bg_t('wizard_step_certs'),
bg_t('wizard_step_webserver'),
bg_t('wizard_step_native'),
bg_t('wizard_step_useragent'),
bg_t('wizard_step_banks'),
bg_t('wizard_step_review_ui'),
];
$canRun = !$wizardLocked && empty($status['exists']);
?>
<!DOCTYPE html>
<html lang="<?= bg_e($lang) ?>" data-theme="<?= bg_e($theme) ?>" data-bs-theme="<?= bg_e($bsTheme) ?>">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title><?= bg_e(bg_t('wizard_page_title')) ?></title>
<link href="assets/vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet">
<link href="assets/css/style.css?v=20260507-gtk-step-wizard" rel="stylesheet">
</head>
<body class="auth-page wizard-auth-page">
<div class="auth-preferences" aria-label="<?= bg_e(bg_t('auth_language_switch')) ?>">
<a class="<?= bg_language()==='en'?'active':'' ?>" href="?bg_lang=en">EN</a>
<a class="<?= bg_language()==='it'?'active':'' ?>" href="?bg_lang=it">IT</a>
<span></span>
<a class="<?= bg_theme()==='system'?'active':'' ?>" href="?bg_theme=system"><?= bg_e(bg_t('system_theme')) ?></a>
<a class="<?= bg_theme()==='light'?'active':'' ?>" href="?bg_theme=light"><?= bg_e(bg_t('light')) ?></a>
<a class="<?= bg_theme()==='dark'?'active':'' ?>" href="?bg_theme=dark"><?= bg_e(bg_t('dark')) ?></a>
</div>
<main class="auth-shell wizard-shell">
<section class="auth-brand-panel" aria-label="BastionGuard WebPanel">
<div class="auth-brand-content">
<p class="auth-kicker"><?= bg_e(bg_t('auth_welcome_to')) ?></p>
<h1><?= str_replace(' ', '<br>', bg_e(bg_t('auth_webpanel_bastionguard'))) ?></h1>
<div class="auth-brand-lockup">
<?= bg_logo_html('auth-brand-logo') ?>
<div><strong>BastionGuard</strong><span><?= bg_e(bg_t('auth_tagline_total')) ?></span></div>
</div>
</div>
<div class="auth-shape auth-shape-one"></div>
<div class="auth-shape auth-shape-two"></div>
<div class="auth-dot-grid" aria-hidden="true"></div>
</section>
<section class="auth-form-panel wizard-form-panel">
<div class="auth-card-wrap wizard-card-wrap">
<div class="auth-floating-logo"><?= bg_logo_html('auth-floating-logo-mark') ?></div>
<div class="auth-card setup-card wizard-card">
<div class="auth-card-header">
<h2><?= bg_e(bg_t('wizard_title')) ?></h2>
<p><?= bg_e(bg_t('wizard_subtitle')) ?></p>
</div>
<?php foreach($messages as $m): ?><div class="alert alert-success"><?= bg_e($m) ?></div><?php endforeach; ?>
<?php foreach($errors as $e): ?><div class="alert alert-danger"><?= bg_e($e) ?></div><?php endforeach; ?>
<div class="wizard-status-grid mb-3">
<div class="wizard-status-item"><span><?= bg_e(bg_t('wizard_desktop_user')) ?></span><strong><?= bg_e($target ?: '—') ?></strong></div>
<div class="wizard-status-item"><span><?= bg_e(bg_t('wizard_config_folder')) ?></span><strong><?= $scan['exists'] ? bg_e(bg_t('present')) : bg_e(bg_t('missing')) ?></strong></div>
<div class="wizard-status-item"><span>config.json</span><strong><?php if(!$status['exists']): ?><?= bg_e(bg_t('missing')) ?><?php elseif(!$status['valid']): ?><?= bg_e(bg_t('invalid')) ?><?php elseif(!empty($status['completed'])): ?><?= bg_e(bg_t('wizard_completed')) ?><?php else: ?><?= bg_e(bg_t('present')) ?><?php endif; ?></strong></div>
<div class="wizard-status-item"><span><?= bg_e(bg_t('desktop_arbitration')) ?></span><strong><?= bg_e($desktopMode['mode'] ?? 'unknown') ?></strong></div>
</div>
<?php if($wizardLocked): ?>
<div class="alert alert-warning">
<h3 class="h5 mb-2"><?= bg_e(bg_t('wizard_locked_title')) ?></h3>
<p class="mb-2"><?= bg_e(bg_t('wizard_locked_desc')) ?></p>
<?php if(!empty($wizardLock['reasons'])): ?>
<ul class="mb-2"><?php foreach($wizardLock['reasons'] as $reason): ?><li><?= bg_e($reason) ?></li><?php endforeach; ?></ul>
<?php endif; ?>
<code><?= bg_e($wizardLock['config_dir'] ?? '') ?></code>
<?php if(!empty($wizardLock['desktop_summary'])): ?><br><code><?= bg_e($wizardLock['desktop_summary']) ?></code><?php endif; ?>
</div>
<div class="d-grid gap-2">
<a class="btn btn-primary auth-submit" href="dashboard.php"><?= bg_e(bg_t('wizard_go_dashboard')) ?></a>
<a class="btn btn-outline-secondary" href="settings.php"><?= bg_e(bg_t('wizard_locked_open_settings')) ?></a>
<a class="btn btn-outline-secondary" href="config.php"><?= bg_e(bg_t('wizard_open_configurations')) ?></a>
</div>
<?php if($status['raw'] !== ''): ?>
<details class="wizard-details mt-3"><summary><?= bg_e(bg_t('wizard_show_config_json')) ?></summary><?php bg_render_log($status['raw']); ?></details>
<?php endif; ?>
<?php elseif(!$canRun): ?>
<div class="auth-info-note mb-3"><?= bg_e(bg_t('wizard_already_completed_desc')) ?></div>
<div class="d-grid gap-2"><a class="btn btn-primary auth-submit" href="dashboard.php"><?= bg_e(bg_t('wizard_go_dashboard')) ?></a><a class="btn btn-outline-secondary" href="config.php"><?= bg_e(bg_t('wizard_open_configurations')) ?></a></div>
<?php if($status['raw'] !== ''): ?><details class="wizard-details mt-3"><summary><?= bg_e(bg_t('wizard_show_config_json')) ?></summary><?php bg_render_log($status['raw']); ?></details><?php endif; ?>
<?php else: ?>
<form method="post" autocomplete="off" data-step-wizard>
<?php csrf_field(); ?><input type="hidden" name="action" value="run_initial_wizard">
<div class="wizard-stepper" aria-label="<?= bg_e(bg_t('wizard_page_title')) ?>">
<?php foreach($stepTitles as $idx => $title): ?>
<button type="button" class="wizard-stepper-item <?= $idx === 0 ? 'active' : '' ?>" data-step-jump="<?= (int)$idx ?>"><span><?= (int)($idx + 1) ?></span><?= bg_e($title) ?></button>
<?php endforeach; ?>
</div>
<section class="wizard-step-pane active" data-step-pane="0" data-step-name="welcome">
<h3><?= bg_e(bg_t('wizard_step_welcome_ui')) ?></h3>
<p class="form-hint"><?= bg_e(bg_t('wizard_welcome_desc')) ?></p>
<div class="row g-3">
<div class="col-md-6"><label class="form-label" for="desktop_user"><?= bg_e(bg_t('wizard_desktop_user')) ?></label><input class="form-control auth-input" id="desktop_user" name="desktop_user" value="<?= bg_e($target) ?>" placeholder="$USER"><div class="form-hint"><?= bg_e(bg_t('wizard_desktop_user_hint')) ?></div></div>
<div class="col-md-3"><label class="form-label" for="http_port"><?= bg_e(bg_t('wizard_http_port')) ?></label><input class="form-control auth-input" id="http_port" name="http_port" type="number" min="1" max="65535" value="<?= (int)$httpPort ?>"></div>
<div class="col-md-3"><label class="form-label" for="https_port"><?= bg_e(bg_t('wizard_https_port')) ?></label><input class="form-control auth-input" id="https_port" name="https_port" type="number" min="1" max="65535" value="<?= (int)$httpsPort ?>"></div>
</div>
<div class="auth-info-note mt-3"><strong><?= bg_e(bg_t('desktop_arbitration')) ?>:</strong> <?= bg_e(($desktopMode['mode'] ?? 'unknown') === 'gui' ? bg_t('desktop_gui_active_webui_no_proxy') : bg_t('desktop_headless_webui_setup')) ?><?php if(!empty($desktopMode['summary'])): ?><br><code><?= bg_e($desktopMode['summary']) ?></code><?php endif; ?></div>
<div class="wizard-actions mt-4"><a class="btn btn-outline-secondary" href="logout.php"><?= bg_e(bg_t('logout')) ?></a><button class="btn btn-primary" type="button" data-step-next><?= bg_e(bg_t('wizard_next')) ?></button></div>
</section>
<section class="wizard-step-pane" data-step-pane="1" data-step-name="services">
<div class="wizard-section-title mt-1"><h3><?= bg_e(bg_t('wizard_services_title')) ?></h3><div class="d-flex gap-2 flex-wrap"><button class="btn btn-sm btn-warning" type="button" data-select-minimal><?= bg_e(bg_t('wizard_minimal')) ?></button><button class="btn btn-sm btn-primary" type="button" data-select-all><?= bg_e(bg_t('wizard_enable_all')) ?></button></div></div>
<p class="form-hint"><?= bg_e(bg_t('wizard_services_desc_gtk')) ?></p>
<div class="wizard-service-grid">
<?php foreach($defaultServices as $unit=>$enabled): $meta=$labels[$unit]??['label'=>$unit,'description'=>'']; $webuiDisabled = bg_webui_disabled_unit($unit); $checked = $webuiDisabled ? false : (array_key_exists($unit, $currentServices) ? !empty($currentServices[$unit]) : !empty($enabled)); ?>
<label class="wizard-choice-card <?= $webuiDisabled ? 'opacity-75' : '' ?>"><input class="form-check-input wizard-service-check" type="checkbox" name="svc[<?= bg_e($unit) ?>]" value="1" <?= $checked ? 'checked' : '' ?> <?= $webuiDisabled ? 'disabled' : '' ?> data-unit="<?= bg_e($unit) ?>" data-webui-disabled="<?= $webuiDisabled ? '1' : '0' ?>"><span><strong><?= bg_e($meta['label']) ?></strong><small><?= bg_e($meta['description']) ?></small><code><?= bg_e($unit) ?></code><?php if($webuiDisabled): ?><small class="text-warning d-block"><?= bg_e(bg_t('cef_pac_excluded_webui')) ?></small><?php endif; ?></span></label>
<?php endforeach; ?>
</div>
<div class="auth-info-note mt-3"><?= bg_e(bg_t('wizard_services_apply_final')) ?></div>
<div class="wizard-actions mt-4"><button class="btn btn-outline-secondary" type="button" data-step-back><?= bg_e(bg_t('wizard_back')) ?></button><button class="btn btn-primary" type="button" data-step-next><?= bg_e(bg_t('wizard_next')) ?></button></div>
</section>
<?php $paneIndex = 2; foreach($systemStepOrder as $key): $step = $steps[$key]; ?>
<section class="wizard-step-pane" data-step-pane="<?= (int)$paneIndex ?>" data-step-name="<?= bg_e($key) ?>">
<h3><?= bg_e($step['label']) ?></h3>
<p class="form-hint"><?= bg_e($step['description']) ?></p>
<label class="wizard-choice-card wizard-step-card"><input class="form-check-input wizard-step-check" type="checkbox" name="step[<?= bg_e($key) ?>]" value="1" <?= !empty($stepDefaults[$key]) ? 'checked' : '' ?> data-step="<?= bg_e($key) ?>"><span><strong><?= bg_e(bg_t('wizard_enable_this_step')) ?></strong><small><?= bg_e($step['description']) ?></small></span></label>
<?php if($key === 'webconf'): ?>
<div class="row g-3 mt-2"><div class="col-md-6"><label class="form-label" for="web_http_port_inline"><?= bg_e(bg_t('wizard_http_port')) ?></label><input class="form-control auth-input" id="web_http_port_inline" type="number" min="1" max="65535" value="<?= (int)$httpPort ?>" data-mirror-http></div><div class="col-md-6"><label class="form-label" for="web_https_port_inline"><?= bg_e(bg_t('wizard_https_port')) ?></label><input class="form-control auth-input" id="web_https_port_inline" type="number" min="1" max="65535" value="<?= (int)$httpsPort ?>" data-mirror-https></div></div>
<?php endif; ?>
<div class="wizard-actions mt-4"><button class="btn btn-outline-secondary" type="button" data-step-back><?= bg_e(bg_t('wizard_back')) ?></button><button class="btn btn-primary" type="button" data-step-next><?= bg_e(bg_t('wizard_next')) ?></button></div>
</section>
<?php $paneIndex++; endforeach; ?>
<section class="wizard-step-pane" data-step-pane="<?= (int)$paneIndex ?>" data-step-name="review">
<h3><?= bg_e(bg_t('wizard_review_title')) ?></h3>
<p class="form-hint"><?= bg_e(bg_t('wizard_review_desc_gtk')) ?></p>
<div class="wizard-status-grid mb-3"><div class="wizard-status-item"><span><?= bg_e(bg_t('wizard_desktop_user')) ?></span><strong data-review-user><?= bg_e($target ?: '—') ?></strong></div><div class="wizard-status-item"><span><?= bg_e(bg_t('wizard_http_port')) ?></span><strong data-review-http><?= (int)$httpPort ?></strong></div><div class="wizard-status-item"><span><?= bg_e(bg_t('wizard_https_port')) ?></span><strong data-review-https><?= (int)$httpsPort ?></strong></div><div class="wizard-status-item"><span><?= bg_e(bg_t('desktop_arbitration')) ?></span><strong><?= bg_e($desktopMode['mode'] ?? 'unknown') ?></strong></div></div>
<div class="auth-info-note"><strong><?= bg_e(bg_t('wizard_final_execution_note')) ?></strong></div>
<div class="wizard-review-list mt-3"><h4><?= bg_e(bg_t('wizard_review_selected_services')) ?></h4><ul data-review-services></ul><h4><?= bg_e(bg_t('wizard_review_selected_steps')) ?></h4><ul data-review-steps></ul></div>
<div class="wizard-actions mt-4"><button class="btn btn-outline-secondary" type="button" data-step-back><?= bg_e(bg_t('wizard_back')) ?></button><button class="btn btn-primary auth-submit" type="submit"><?= bg_e(bg_t('wizard_start_setup')) ?></button></div>
</section>
</form>
<?php endif; ?>
<?php if($log !== ''): ?><details class="wizard-details mt-3" open><summary><?= bg_e(bg_t('wizard_log')) ?></summary><?php bg_render_log($log); ?></details><?php endif; ?>
<div class="auth-version">v1.0</div>
</div>
</div>
</section>
</main>
<script>
(function(){
var form = document.querySelector('[data-step-wizard]');
var allBtn = document.querySelector('[data-select-all]');
var minBtn = document.querySelector('[data-select-minimal]');
var minimal = {'BastionGuard-ransomware-realtime-alert.service': true};
var stepLabels = <?= json_encode(array_map(fn($k) => $steps[$k]['label'] ?? $k, $systemStepOrder), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
var stepKeys = <?= json_encode($systemStepOrder, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
if (allBtn) allBtn.addEventListener('click', function(){ document.querySelectorAll('.wizard-service-check').forEach(function(c){ c.checked = c.getAttribute('data-webui-disabled') !== '1'; }); updateReview(); });
if (minBtn) minBtn.addEventListener('click', function(){ document.querySelectorAll('.wizard-service-check').forEach(function(c){ c.checked = !!minimal[c.getAttribute('data-unit')]; }); updateReview(); });
if (!form) return;
var current = 0;
var panes = Array.prototype.slice.call(document.querySelectorAll('[data-step-pane]'));
var jumps = Array.prototype.slice.call(document.querySelectorAll('[data-step-jump]'));
var http = document.getElementById('http_port');
var https = document.getElementById('https_port');
var inlineHttp = document.querySelector('[data-mirror-http]');
var inlineHttps = document.querySelector('[data-mirror-https]');
function syncPorts(fromInline) {
if (fromInline) { if (inlineHttp && http) http.value = inlineHttp.value; if (inlineHttps && https) https.value = inlineHttps.value; }
else { if (inlineHttp && http) inlineHttp.value = http.value; if (inlineHttps && https) inlineHttps.value = https.value; }
}
function updateReview() {
syncPorts(false);
var user = document.getElementById('desktop_user');
var ru = document.querySelector('[data-review-user]');
var rh = document.querySelector('[data-review-http]');
var rs = document.querySelector('[data-review-https]');
if (ru && user) ru.textContent = user.value || '—';
if (rh && http) rh.textContent = http.value || '81';
if (rs && https) rs.textContent = https.value || '444';
var svcList = document.querySelector('[data-review-services]');
var stepList = document.querySelector('[data-review-steps]');
if (svcList) {
svcList.innerHTML = '';
document.querySelectorAll('.wizard-service-check').forEach(function(c){
if (!c.checked) return;
var card = c.closest('.wizard-choice-card');
var label = card ? (card.querySelector('strong') ? card.querySelector('strong').textContent : c.getAttribute('data-unit')) : c.getAttribute('data-unit');
var li = document.createElement('li'); li.textContent = label + ' — ' + c.getAttribute('data-unit'); svcList.appendChild(li);
});
if (!svcList.children.length) { var li = document.createElement('li'); li.textContent = '—'; svcList.appendChild(li); }
}
if (stepList) {
stepList.innerHTML = '';
stepKeys.forEach(function(k, i){ var c = document.querySelector('.wizard-step-check[data-step="'+k+'"]'); if (c && c.checked) { var li = document.createElement('li'); li.textContent = stepLabels[i] || k; stepList.appendChild(li); } });
if (!stepList.children.length) { var li = document.createElement('li'); li.textContent = '—'; stepList.appendChild(li); }
}
}
function showStep(idx) {
current = Math.max(0, Math.min(idx, panes.length - 1));
panes.forEach(function(p, i){ p.classList.toggle('active', i === current); });
jumps.forEach(function(b, i){ b.classList.toggle('active', i === current); b.classList.toggle('done', i < current); });
updateReview();
}
document.querySelectorAll('[data-step-next]').forEach(function(btn){ btn.addEventListener('click', function(){ showStep(current + 1); }); });
document.querySelectorAll('[data-step-back]').forEach(function(btn){ btn.addEventListener('click', function(){ showStep(current - 1); }); });
jumps.forEach(function(btn){ btn.addEventListener('click', function(){ showStep(parseInt(btn.getAttribute('data-step-jump') || '0', 10)); }); });
document.querySelectorAll('.wizard-service-check,.wizard-step-check,#desktop_user,#http_port,#https_port').forEach(function(el){ el.addEventListener('change', updateReview); el.addEventListener('input', updateReview); });
if (inlineHttp) inlineHttp.addEventListener('input', function(){ syncPorts(true); updateReview(); });
if (inlineHttps) inlineHttps.addEventListener('input', function(){ syncPorts(true); updateReview(); });
showStep(0);
})();
</script>
</body>
</html>