- Updated CRON documentation to include DataForSEO metrics synchronization. - Enhanced SettingsController to manage DataForSEO API credentials and settings. - Modified SiteController to handle DataForSEO domain input. - Updated Site model to accommodate DataForSEO data handling. - Added methods in SiteSeoMetric model for DataForSEO data retrieval and validation. - Implemented SiteSeoSyncService to synchronize SEO metrics from both SEMSTORM and DataForSEO. - Enhanced dashboard templates to display indexed pages data. - Updated settings and site creation/edit templates to include DataForSEO fields. - Created migration for adding DataForSEO related columns in the database. - Developed DataForSeoService to fetch indexed pages count from DataForSEO API.
201 lines
8.5 KiB
PHP
201 lines
8.5 KiB
PHP
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<h2>SEO Panel: <?= htmlspecialchars((string) ($site['url'] ?? '')) ?></h2>
|
|
<div class="d-flex gap-2">
|
|
<a href="/sites/<?= (int) $site['id'] ?>/dashboard" class="btn btn-outline-dark">
|
|
<i class="bi bi-sliders me-1"></i>WP Dashboard
|
|
</a>
|
|
<a href="/sites/<?= (int) $site['id'] ?>/edit" class="btn btn-outline-secondary">
|
|
<i class="bi bi-pencil me-1"></i>Edytuj strone
|
|
</a>
|
|
<a href="/sites" class="btn btn-outline-secondary">
|
|
<i class="bi bi-arrow-left me-1"></i>Lista stron
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row g-4">
|
|
<div class="col-lg-8">
|
|
<div class="card">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<h5 class="mb-0">Widocznosc SEO (SEMSTORM)</h5>
|
|
<form method="post" action="/sites/<?= (int) $site['id'] ?>/seo/sync" data-confirm="Pobrac i nadpisac dane SEO dla biezacego miesiaca?">
|
|
<button type="submit" class="btn btn-sm btn-outline-primary">
|
|
<i class="bi bi-arrow-repeat me-1"></i>Synchronizuj teraz
|
|
</button>
|
|
</form>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="position-relative" style="height: 340px;">
|
|
<canvas id="seoVisibilityChart"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card mt-4 border-secondary-subtle">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<h6 class="mb-0">Zaindeksowane strony (DataForSEO)</h6>
|
|
</div>
|
|
<div class="card-body">
|
|
<?php if (!empty($seoLatest)): ?>
|
|
<div class="display-6 fw-semibold mb-1"><?= (int) ($seoLatest['indexed_pages'] ?? 0) ?></div>
|
|
<p class="small text-muted mb-0">
|
|
Ostatnia aktualizacja miesieczna:
|
|
<?= htmlspecialchars(date('m.Y', strtotime((string) $seoLatest['metric_month']))) ?>
|
|
</p>
|
|
<?php else: ?>
|
|
<p class="text-muted small mb-0">Brak danych o indeksacji. Uzyj przycisku "Synchronizuj teraz".</p>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-lg-4">
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h5 class="mb-0">Aktualne metryki</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<?php if (!empty($seoLatest)): ?>
|
|
<div class="row g-2 mb-3">
|
|
<div class="col-6">
|
|
<div class="border rounded p-2">
|
|
<div class="small text-muted">TOP3</div>
|
|
<div class="fw-semibold"><?= (int) $seoLatest['top3'] ?></div>
|
|
</div>
|
|
</div>
|
|
<div class="col-6">
|
|
<div class="border rounded p-2">
|
|
<div class="small text-muted">TOP10</div>
|
|
<div class="fw-semibold"><?= (int) $seoLatest['top10'] ?></div>
|
|
</div>
|
|
</div>
|
|
<div class="col-6">
|
|
<div class="border rounded p-2">
|
|
<div class="small text-muted">TOP20</div>
|
|
<div class="fw-semibold"><?= (int) $seoLatest['top20'] ?></div>
|
|
</div>
|
|
</div>
|
|
<div class="col-6">
|
|
<div class="border rounded p-2">
|
|
<div class="small text-muted">TOP50</div>
|
|
<div class="fw-semibold"><?= (int) $seoLatest['top50'] ?></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<p class="small mb-2"><strong>Ruch:</strong> <?= (int) $seoLatest['traffic'] ?></p>
|
|
<p class="small mb-2"><strong>Zaindeksowane strony:</strong> <?= (int) ($seoLatest['indexed_pages'] ?? 0) ?></p>
|
|
<p class="small text-muted mb-0">
|
|
Ostatni zapis: <?= htmlspecialchars(date('d.m.Y H:i', strtotime((string) $seoLatest['updated_at']))) ?><br>
|
|
Miesiac: <?= htmlspecialchars(date('m.Y', strtotime((string) $seoLatest['metric_month']))) ?>
|
|
</p>
|
|
<?php else: ?>
|
|
<p class="text-muted small mb-0">Brak zapisanych danych SEO. Uzyj przycisku "Synchronizuj teraz".</p>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card border-info">
|
|
<div class="card-header bg-info-subtle">
|
|
<h5 class="mb-0">Kolejne zrodla (plan)</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<p class="small text-muted mb-0">Ten panel jest przygotowany pod dodatkowe integracje SEO (np. GSC, Ahrefs, Senuto) bez mieszania z ustawieniami WordPress.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.4/dist/chart.umd.min.js"></script>
|
|
<script>
|
|
(function () {
|
|
var seoMetrics = <?= json_encode($seoMetrics ?? [], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
|
|
var seoCanvas = document.getElementById('seoVisibilityChart');
|
|
|
|
if (!seoCanvas || !window.Chart) {
|
|
return;
|
|
}
|
|
|
|
if (!Array.isArray(seoMetrics) || seoMetrics.length === 0) {
|
|
seoCanvas.style.display = 'none';
|
|
return;
|
|
}
|
|
|
|
var labels = seoMetrics.map(function (item) {
|
|
var date = new Date(item.metric_month);
|
|
if (isNaN(date.getTime())) return item.metric_month;
|
|
var month = String(date.getMonth() + 1).padStart(2, '0');
|
|
return month + '.' + date.getFullYear();
|
|
});
|
|
|
|
new Chart(seoCanvas, {
|
|
type: 'line',
|
|
data: {
|
|
labels: labels,
|
|
datasets: [
|
|
{
|
|
label: 'TOP3',
|
|
data: seoMetrics.map(function (item) { return parseInt(item.top3 || 0, 10); }),
|
|
borderColor: '#0d6efd',
|
|
backgroundColor: 'rgba(13,110,253,0.15)',
|
|
tension: 0.25,
|
|
yAxisID: 'y'
|
|
},
|
|
{
|
|
label: 'TOP10',
|
|
data: seoMetrics.map(function (item) { return parseInt(item.top10 || 0, 10); }),
|
|
borderColor: '#20c997',
|
|
backgroundColor: 'rgba(32,201,151,0.15)',
|
|
tension: 0.25,
|
|
yAxisID: 'y'
|
|
},
|
|
{
|
|
label: 'TOP20',
|
|
data: seoMetrics.map(function (item) { return parseInt(item.top20 || 0, 10); }),
|
|
borderColor: '#fd7e14',
|
|
backgroundColor: 'rgba(253,126,20,0.15)',
|
|
tension: 0.25,
|
|
yAxisID: 'y'
|
|
},
|
|
{
|
|
label: 'TOP50',
|
|
data: seoMetrics.map(function (item) { return parseInt(item.top50 || 0, 10); }),
|
|
borderColor: '#dc3545',
|
|
backgroundColor: 'rgba(220,53,69,0.15)',
|
|
tension: 0.25,
|
|
yAxisID: 'y'
|
|
},
|
|
{
|
|
label: 'Ruch',
|
|
data: seoMetrics.map(function (item) { return parseInt(item.traffic || 0, 10); }),
|
|
borderColor: '#6f42c1',
|
|
backgroundColor: 'rgba(111,66,193,0.15)',
|
|
tension: 0.25,
|
|
yAxisID: 'yTraffic'
|
|
}
|
|
]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
interaction: {
|
|
mode: 'index',
|
|
intersect: false
|
|
},
|
|
scales: {
|
|
y: {
|
|
beginAtZero: true,
|
|
position: 'left',
|
|
title: { display: true, text: 'TOP' }
|
|
},
|
|
yTraffic: {
|
|
beginAtZero: true,
|
|
position: 'right',
|
|
grid: { drawOnChartArea: false },
|
|
title: { display: true, text: 'Ruch' }
|
|
}
|
|
}
|
|
}
|
|
});
|
|
})();
|
|
</script>
|