Files
backPRO/templates/installer/index.php
Jacek Pyziak b653cea252 Add installer functionality for WordPress with FTP and database configuration
- Create SQL migration for prompt templates used in article and image generation.
- Add migration to change publish interval from days to hours in the sites table.
- Implement InstallerController to handle installation requests and validation.
- Develop FtpService for FTP connections and file uploads.
- Create InstallerService to manage the WordPress installation process, including downloading, extracting, and configuring WordPress.
- Add index view for the installer with form inputs for FTP, database, and WordPress admin settings.
- Implement progress tracking for the installation process with AJAX polling.
2026-02-16 21:55:24 +01:00

373 lines
18 KiB
PHP

<h2 class="mb-4"><i class="bi bi-cloud-upload me-2"></i>Instalator WordPress</h2>
<div class="row g-4">
<div class="col-lg-7">
<form method="post" action="/installer" id="installerForm">
<!-- FTP -->
<div class="card mb-4">
<div class="card-header"><h5 class="mb-0"><i class="bi bi-hdd-network me-2"></i>Dane FTP</h5></div>
<div class="card-body">
<div class="row mb-3">
<div class="col-md-8">
<label for="ftp_host" class="form-label">Host FTP</label>
<input type="text" class="form-control" id="ftp_host" name="ftp_host" required placeholder="ftp.example.com">
</div>
<div class="col-md-4">
<label for="ftp_port" class="form-label">Port</label>
<input type="number" class="form-control" id="ftp_port" name="ftp_port" value="21">
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<label for="ftp_user" class="form-label">Użytkownik FTP</label>
<input type="text" class="form-control" id="ftp_user" name="ftp_user" required>
</div>
<div class="col-md-6">
<label for="ftp_pass" class="form-label">Hasło FTP</label>
<input type="password" class="form-control" id="ftp_pass" name="ftp_pass" required>
</div>
</div>
<div class="mb-3">
<label for="ftp_path" class="form-label">Ścieżka docelowa</label>
<input type="text" class="form-control" id="ftp_path" name="ftp_path" required placeholder="/public_html" value="/public_html">
<div class="form-text">Ścieżka na serwerze, gdzie mają być wgrane pliki WordPress</div>
</div>
<div class="form-check">
<input type="checkbox" class="form-check-input" id="ftp_ssl" name="ftp_ssl" value="1">
<label class="form-check-label" for="ftp_ssl">Użyj FTPS (SSL)</label>
</div>
</div>
</div>
<!-- Database -->
<div class="card mb-4">
<div class="card-header"><h5 class="mb-0"><i class="bi bi-database me-2"></i>Baza danych</h5></div>
<div class="card-body">
<div class="row mb-3">
<div class="col-md-6">
<label for="db_host" class="form-label">Host bazy danych</label>
<input type="text" class="form-control" id="db_host" name="db_host" required value="localhost">
</div>
<div class="col-md-6">
<label for="db_name" class="form-label">Nazwa bazy danych</label>
<input type="text" class="form-control" id="db_name" name="db_name" required>
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<label for="db_user" class="form-label">Użytkownik bazy</label>
<input type="text" class="form-control" id="db_user" name="db_user" required>
</div>
<div class="col-md-6">
<label for="db_pass" class="form-label">Hasło bazy</label>
<input type="password" class="form-control" id="db_pass" name="db_pass" required>
</div>
</div>
<div class="mb-0">
<label for="db_prefix" class="form-label">Prefix tabel</label>
<input type="text" class="form-control" id="db_prefix" name="db_prefix" value="wp_" style="max-width: 150px;">
<div class="form-text">Zmień domyślny "wp_" dla lepszego bezpieczeństwa</div>
</div>
</div>
</div>
<!-- WordPress Admin -->
<div class="card mb-4">
<div class="card-header"><h5 class="mb-0"><i class="bi bi-wordpress me-2"></i>Administrator WordPress</h5></div>
<div class="card-body">
<div class="mb-3">
<label for="site_url" class="form-label">URL strony</label>
<input type="url" class="form-control" id="site_url" name="site_url" required placeholder="https://example.com">
<div class="form-text">Pełny URL strony (bez końcowego /)</div>
</div>
<div class="mb-3">
<label for="site_title" class="form-label">Tytuł strony</label>
<input type="text" class="form-control" id="site_title" name="site_title" required placeholder="np. Blog o Ogrodnictwie">
</div>
<div class="row mb-3">
<div class="col-md-6">
<label for="admin_user" class="form-label">Login administratora</label>
<input type="text" class="form-control" id="admin_user" name="admin_user" required value="admin">
</div>
<div class="col-md-6">
<label for="admin_pass" class="form-label">Hasło administratora</label>
<input type="password" class="form-control" id="admin_pass" name="admin_pass" required>
<div class="form-text">Min. 8 znaków, użyj silnego hasła</div>
</div>
</div>
<div class="mb-3">
<label for="admin_email" class="form-label">E-mail administratora</label>
<input type="email" class="form-control" id="admin_email" name="admin_email" required placeholder="admin@example.com">
</div>
<div class="mb-0">
<label for="language" class="form-label">Wersja językowa</label>
<select class="form-select" id="language" name="language" style="max-width: 250px;">
<option value="pl_PL">Polski (pl_PL)</option>
<option value="en_US">English (en_US)</option>
</select>
</div>
</div>
</div>
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary btn-lg" id="installBtn">
<i class="bi bi-cloud-upload me-1"></i>Zainstaluj WordPress
</button>
<a href="/" class="btn btn-outline-secondary btn-lg" id="cancelBtn">Anuluj</a>
</div>
</form>
<!-- Progress panel (hidden by default) -->
<div id="progressPanel" class="mt-4" style="display: none;">
<div class="card">
<div class="card-header">
<h5 class="mb-0"><i class="bi bi-hourglass-split me-2"></i>Postęp instalacji</h5>
</div>
<div class="card-body">
<div class="progress mb-3" style="height: 25px;">
<div id="progressBar" class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" style="width: 0%;" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">
0%
</div>
</div>
<div id="progressMessage" class="text-muted">
<span class="spinner-border spinner-border-sm me-1"></span>
Oczekiwanie na start...
</div>
<div id="progressSteps" class="mt-3 small">
<div class="d-flex align-items-center mb-1" data-step="download">
<i class="bi bi-circle me-2 text-muted" id="stepIcon1"></i>
<span>Pobieranie WordPress</span>
</div>
<div class="d-flex align-items-center mb-1" data-step="extract">
<i class="bi bi-circle me-2 text-muted" id="stepIcon2"></i>
<span>Rozpakowywanie archiwum</span>
</div>
<div class="d-flex align-items-center mb-1" data-step="config">
<i class="bi bi-circle me-2 text-muted" id="stepIcon3"></i>
<span>Generowanie wp-config.php</span>
</div>
<div class="d-flex align-items-center mb-1" data-step="ftp">
<i class="bi bi-circle me-2 text-muted" id="stepIcon4"></i>
<span>Wgrywanie plików FTP</span>
</div>
<div class="d-flex align-items-center mb-1" data-step="install">
<i class="bi bi-circle me-2 text-muted" id="stepIcon5"></i>
<span>Instalacja WordPress</span>
</div>
<div class="d-flex align-items-center mb-1" data-step="apppass">
<i class="bi bi-circle me-2 text-muted" id="stepIcon6"></i>
<span>Tworzenie Application Password</span>
</div>
<div class="d-flex align-items-center mb-1" data-step="register">
<i class="bi bi-circle me-2 text-muted" id="stepIcon7"></i>
<span>Rejestracja w BackPRO</span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Right column -->
<div class="col-lg-5">
<div class="card mb-4">
<div class="card-header"><h5 class="mb-0">Jak to działa?</h5></div>
<div class="card-body">
<ol class="mb-0">
<li class="mb-2">System pobiera najnowszą wersję WordPress</li>
<li class="mb-2">Generuje <code>wp-config.php</code> z danymi bazy</li>
<li class="mb-2">Wgrywa pliki na serwer przez FTP</li>
<li class="mb-2">Uruchamia instalację WordPress</li>
<li class="mb-2">Tworzy Application Password do API</li>
<li class="mb-0">Rejestruje stronę w BackPRO</li>
</ol>
</div>
</div>
<div class="card mb-4">
<div class="card-header"><h5 class="mb-0">Wymagania</h5></div>
<div class="card-body">
<ul class="mb-0 small">
<li class="mb-1">Serwer z PHP 7.4+ i MySQL 5.7+</li>
<li class="mb-1">Konto FTP z uprawnieniami zapisu</li>
<li class="mb-1">Pusta baza danych (lub z unikalnym prefixem tabel)</li>
<li class="mb-1">Domena skierowana na serwer docelowy</li>
<li class="mb-0">Proces trwa 2-5 minut</li>
</ul>
</div>
</div>
<div class="alert alert-warning small mb-0">
<i class="bi bi-exclamation-triangle me-1"></i>
<strong>Uwaga:</strong> Instalacja może potrwać kilka minut ze względu na przesyłanie ~1500 plików przez FTP.
Nie zamykaj przeglądarki do momentu zakończenia procesu.
</div>
</div>
</div>
<script>
(function() {
var form = document.getElementById('installerForm');
var btn = document.getElementById('installBtn');
var cancelBtn = document.getElementById('cancelBtn');
var progressPanel = document.getElementById('progressPanel');
var progressBar = document.getElementById('progressBar');
var progressMessage = document.getElementById('progressMessage');
var pollInterval = null;
var progressId = null;
// Step thresholds: [minPercent, iconId]
var steps = [
[5, 'stepIcon1'], // download
[20, 'stepIcon2'], // extract
[28, 'stepIcon3'], // config
[33, 'stepIcon4'], // ftp
[86, 'stepIcon5'], // install
[93, 'stepIcon6'], // apppass
[97, 'stepIcon7'], // register
];
function generateId() {
var chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
var id = '';
for (var i = 0; i < 20; i++) {
id += chars.charAt(Math.floor(Math.random() * chars.length));
}
return id;
}
function updateStepIcons(percent) {
for (var i = 0; i < steps.length; i++) {
var icon = document.getElementById(steps[i][1]);
if (!icon) continue;
var threshold = steps[i][0];
var nextThreshold = (i + 1 < steps.length) ? steps[i + 1][0] : 101;
if (percent >= nextThreshold) {
// Completed
icon.className = 'bi bi-check-circle-fill me-2 text-success';
} else if (percent >= threshold) {
// In progress
icon.className = 'bi bi-arrow-right-circle-fill me-2 text-primary';
} else {
// Pending
icon.className = 'bi bi-circle me-2 text-muted';
}
}
}
function setProgress(percent, message) {
progressBar.style.width = percent + '%';
progressBar.setAttribute('aria-valuenow', percent);
progressBar.textContent = percent + '%';
progressMessage.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span> ' + message;
updateStepIcons(percent);
}
function pollStatus() {
fetch('/installer/status/' + progressId)
.then(function(r) { return r.json(); })
.then(function(data) {
if (data.status === 'waiting') return;
setProgress(data.percent, data.message);
if (data.status === 'completed' || data.status === 'failed') {
clearInterval(pollInterval);
progressBar.classList.remove('progress-bar-animated');
if (data.status === 'failed') {
progressBar.classList.remove('bg-primary');
progressBar.classList.add('bg-danger');
progressMessage.innerHTML = '<i class="bi bi-x-circle me-1 text-danger"></i> ' + data.message;
}
}
})
.catch(function() { /* ignore polling errors */ });
}
form.addEventListener('submit', function(e) {
e.preventDefault();
// Generate progress ID and collect form data BEFORE disabling inputs
progressId = generateId();
var formData = new FormData(form);
formData.append('progress_id', progressId);
// Show progress panel, disable form
progressPanel.style.display = 'block';
btn.disabled = true;
btn.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span>Instalowanie...';
cancelBtn.style.display = 'none';
// Disable all form inputs
var inputs = form.querySelectorAll('input, select, button');
for (var i = 0; i < inputs.length; i++) {
inputs[i].disabled = true;
}
// Scroll to progress panel
progressPanel.scrollIntoView({ behavior: 'smooth', block: 'center' });
// Start polling before the AJAX request
pollInterval = setInterval(pollStatus, 2000);
// Send AJAX install request
fetch('/installer', {
method: 'POST',
body: formData
})
.then(function(r) { return r.json(); })
.then(function(result) {
clearInterval(pollInterval);
progressBar.classList.remove('progress-bar-animated');
if (result.success) {
setProgress(100, result.message);
progressBar.classList.remove('bg-primary');
progressBar.classList.add('bg-success');
progressMessage.innerHTML = '<i class="bi bi-check-circle me-1 text-success"></i> ' + result.message;
// Mark all steps as completed
updateStepIcons(100);
// Redirect after 2 seconds
setTimeout(function() {
window.location.href = '/sites/' + result.site_id + '/edit';
}, 2000);
} else {
progressBar.classList.remove('bg-primary');
progressBar.classList.add('bg-danger');
progressMessage.innerHTML = '<i class="bi bi-x-circle me-1 text-danger"></i> ' + result.message;
// Re-enable form for retry
var inputs = form.querySelectorAll('input, select');
for (var i = 0; i < inputs.length; i++) {
inputs[i].disabled = false;
}
btn.disabled = false;
btn.innerHTML = '<i class="bi bi-arrow-clockwise me-1"></i>Sprobuj ponownie';
cancelBtn.style.display = '';
}
})
.catch(function(err) {
clearInterval(pollInterval);
progressBar.classList.remove('progress-bar-animated', 'bg-primary');
progressBar.classList.add('bg-danger');
progressMessage.innerHTML = '<i class="bi bi-x-circle me-1 text-danger"></i> Blad polaczenia z serwerem.';
var inputs = form.querySelectorAll('input, select');
for (var i = 0; i < inputs.length; i++) {
inputs[i].disabled = false;
}
btn.disabled = false;
btn.innerHTML = '<i class="bi bi-arrow-clockwise me-1"></i>Sprobuj ponownie';
cancelBtn.style.display = '';
});
});
})();
</script>