- 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.
373 lines
18 KiB
PHP
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>
|