- 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.
165 lines
7.3 KiB
PHP
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>
|