Files
backPRO/templates/dashboard/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

165 lines
7.3 KiB
PHP

<h2 class="mb-4">Dashboard</h2>
<div class="row g-4 mb-4">
<div class="col-md-3">
<div class="card border-primary">
<div class="card-body text-center">
<div class="fs-2 text-primary"><?= $totalSites ?></div>
<div class="text-muted">Stron ogółem</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card border-success">
<div class="card-body text-center">
<div class="fs-2 text-success"><?= $activeSites ?></div>
<div class="text-muted">Stron aktywnych</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card border-info">
<div class="card-body text-center">
<div class="fs-2 text-info"><?= $articleStats['published'] ?></div>
<div class="text-muted">Opublikowanych</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card border-danger">
<div class="card-body text-center">
<div class="fs-2 text-danger"><?= $articleStats['failed'] ?></div>
<div class="text-muted">Błędnych</div>
</div>
</div>
</div>
</div>
<div class="row g-4">
<div class="col-md-6">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0">Strony WordPress</h5>
<a href="/sites" class="btn btn-sm btn-outline-primary">Zarządzaj</a>
</div>
<div class="card-body p-0">
<table class="table table-hover mb-0">
<thead>
<tr>
<th>Nazwa</th>
<th>Status</th>
<th>Ostatnia publikacja</th>
<th></th>
</tr>
</thead>
<tbody>
<?php if (empty($sites)): ?>
<tr><td colspan="4" class="text-muted text-center py-3">Brak stron. <a href="/sites/create">Dodaj pierwszą</a></td></tr>
<?php else: ?>
<?php foreach ($sites as $site): ?>
<tr>
<td><a href="/sites/<?= $site['id'] ?>/edit"><?= htmlspecialchars($site['name']) ?></a></td>
<td>
<?php if ($site['is_active']): ?>
<span class="badge bg-success">Aktywna</span>
<?php else: ?>
<span class="badge bg-secondary">Nieaktywna</span>
<?php endif; ?>
</td>
<td>
<?= $site['last_published_at'] ? date('d.m.Y H:i', strtotime($site['last_published_at'])) : '<span class="text-muted">-</span>' ?>
</td>
<td>
<?php if ($site['is_active']): ?>
<button class="btn btn-sm btn-outline-success py-0 px-2 btn-force-publish" data-site-id="<?= $site['id'] ?>" data-site-name="<?= htmlspecialchars($site['name']) ?>">
<i class="bi bi-play-fill"></i>
</button>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0">Ostatnie artykuły</h5>
<a href="/articles" class="btn btn-sm btn-outline-primary">Wszystkie</a>
</div>
<div class="card-body p-0">
<table class="table table-hover mb-0">
<thead>
<tr>
<th>Tytuł</th>
<th>Strona</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<?php if (empty($recentArticles)): ?>
<tr><td colspan="3" class="text-muted text-center py-3">Brak artykułów</td></tr>
<?php else: ?>
<?php foreach ($recentArticles as $article): ?>
<tr>
<td><a href="/articles/<?= $article['id'] ?>"><?= htmlspecialchars(mb_strimwidth($article['title'], 0, 40, '...')) ?></a></td>
<td><?= htmlspecialchars($article['site_name']) ?></td>
<td>
<?php if ($article['status'] === 'published'): ?>
<span class="badge bg-success">OK</span>
<?php elseif ($article['status'] === 'failed'): ?>
<span class="badge bg-danger">Błąd</span>
<?php else: ?>
<span class="badge bg-warning">Oczekuje</span>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
<script>
document.querySelectorAll('.btn-force-publish').forEach(function(btn) {
btn.addEventListener('click', function() {
var siteId = this.dataset.siteId;
var siteName = this.dataset.siteName;
if (!confirm('Wymusić publikację artykułu na "' + siteName + '"?\n\nArtykuł zostanie wygenerowany i opublikowany natychmiast.')) return;
btn.disabled = true;
btn.innerHTML = '<span class="spinner-border spinner-border-sm"></span>';
fetch('/publish/site/' + siteId, {
method: 'POST',
headers: { 'X-Requested-With': 'XMLHttpRequest' }
})
.then(function(r) { return r.json(); })
.then(function(data) {
if (data.success) {
btn.classList.remove('btn-outline-success');
btn.classList.add('btn-success');
btn.innerHTML = '<i class="bi bi-check-lg"></i>';
setTimeout(function() { location.reload(); }, 2000);
} else {
alert(data.message || 'Błąd publikacji');
btn.disabled = false;
btn.innerHTML = '<i class="bi bi-play-fill"></i>';
}
})
.catch(function() {
alert('Błąd połączenia');
btn.disabled = false;
btn.innerHTML = '<i class="bi bi-play-fill"></i>';
});
});
});
</script>