Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions
Production moved from on-prem VM 249 (10.22.68.249) to OVH VPS (57.128.200.27, inpi-vps-waw01). Updated ALL documentation, slash commands, memory files, architecture docs, and deploy procedures. Added |local_time Jinja filter (UTC→Europe/Warsaw) and converted 155 .strftime() calls across 71 templates so timestamps display in Polish timezone regardless of server timezone. Also includes: created_by_id tracking, abort import fix, ICS calendar fix for missing end times, Pros Poland data cleanup. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
189 lines
6.7 KiB
HTML
189 lines
6.7 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Audyt SEO #{{ audit.id }} - Norda Biznes Partner{% endblock %}
|
|
|
|
{% block extra_css %}
|
|
<style>
|
|
.detail-header { margin-bottom: var(--spacing-xl); }
|
|
.detail-section {
|
|
background: white;
|
|
border-radius: var(--radius-lg);
|
|
padding: var(--spacing-lg);
|
|
margin-bottom: var(--spacing-lg);
|
|
box-shadow: var(--shadow-sm);
|
|
border: 1px solid var(--border);
|
|
}
|
|
.detail-section h3 {
|
|
margin-bottom: var(--spacing-md);
|
|
padding-bottom: var(--spacing-sm);
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
.detail-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
gap: var(--spacing-md);
|
|
}
|
|
.detail-item {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
padding: var(--spacing-xs) 0;
|
|
font-size: var(--font-size-sm);
|
|
}
|
|
.detail-item-label { color: var(--text-secondary); }
|
|
.detail-item-value { font-weight: 600; }
|
|
.compare-better { color: var(--success); }
|
|
.compare-worse { color: var(--error); }
|
|
.issues-list { list-style: none; padding: 0; }
|
|
.issues-list li {
|
|
padding: var(--spacing-sm) var(--spacing-md);
|
|
border-left: 3px solid var(--warning);
|
|
margin-bottom: var(--spacing-xs);
|
|
font-size: var(--font-size-sm);
|
|
background: #fffbeb;
|
|
border-radius: 0 var(--radius) var(--radius) 0;
|
|
}
|
|
.issues-list li.critical { border-left-color: var(--error); background: #fef2f2; }
|
|
pre.json-dump {
|
|
background: #1e293b;
|
|
color: #e2e8f0;
|
|
padding: var(--spacing-lg);
|
|
border-radius: var(--radius);
|
|
overflow-x: auto;
|
|
font-size: 12px;
|
|
max-height: 500px;
|
|
overflow-y: auto;
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="detail-header">
|
|
<a href="{{ url_for('admin.admin_portal_seo') }}" style="color: var(--primary); text-decoration: none; font-size: var(--font-size-sm);">
|
|
← Wróć do listy audytów
|
|
</a>
|
|
<h1 style="margin-top: var(--spacing-sm);">Audyt SEO #{{ audit.id }}</h1>
|
|
<p style="color: var(--text-secondary);">
|
|
{{ audit.url }} — {{ audit.audited_at|local_time('%d.%m.%Y %H:%M') }}
|
|
{% if audit.notes %} — {{ audit.notes }}{% endif %}
|
|
</p>
|
|
</div>
|
|
|
|
{% if audit.full_results %}
|
|
{% set r = audit.full_results %}
|
|
|
|
<!-- Issues -->
|
|
{% if r.get('issues') %}
|
|
<div class="detail-section">
|
|
<h3>Znalezione problemy ({{ r.issues|length }})</h3>
|
|
<ul class="issues-list">
|
|
{% for issue in r.issues %}
|
|
<li class="{{ 'critical' if issue.severity == 'critical' or issue.severity == 'high' else '' }}">
|
|
<strong>[{{ issue.severity|upper }}]</strong> {{ issue.message }}
|
|
</li>
|
|
{% endfor %}
|
|
</ul>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- PageSpeed -->
|
|
{% if r.get('pagespeed') %}
|
|
<div class="detail-section">
|
|
<h3>PageSpeed Insights</h3>
|
|
<div class="detail-grid">
|
|
{% set ps = r.pagespeed %}
|
|
{% set scores = ps.get('scores', {}) %}
|
|
{% for key in ['performance', 'seo', 'accessibility', 'best_practices'] %}
|
|
<div class="detail-item">
|
|
<span class="detail-item-label">{{ key|replace('_', ' ')|title }}</span>
|
|
<span class="detail-item-value">{{ scores.get(key, '—') }}</span>
|
|
</div>
|
|
{% endfor %}
|
|
{% set cwv = ps.get('core_web_vitals', {}) %}
|
|
{% for key in ['lcp_ms', 'fcp_ms', 'cls', 'tbt_ms', 'ttfb_ms'] %}
|
|
{% if cwv.get(key) is not none %}
|
|
<div class="detail-item">
|
|
<span class="detail-item-label">{{ key|upper|replace('_MS','')|replace('_','') }}</span>
|
|
<span class="detail-item-value">{{ cwv[key] }}{{ 'ms' if 'ms' in key else '' }}</span>
|
|
</div>
|
|
{% endif %}
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- On-Page -->
|
|
{% if r.get('on_page') %}
|
|
<div class="detail-section">
|
|
<h3>On-Page SEO</h3>
|
|
<div class="detail-grid">
|
|
{% set op = r.on_page %}
|
|
{% for key, label in [('meta_title', 'Meta Title'), ('meta_description', 'Meta Description'), ('h1_text', 'H1'), ('h2_count', 'H2 Count'), ('total_images', 'Obrazy'), ('images_without_alt', 'Bez alt'), ('word_count', 'Słowa'), ('has_structured_data', 'Structured Data'), ('has_og_tags', 'Open Graph')] %}
|
|
{% if op.get(key) is not none %}
|
|
<div class="detail-item">
|
|
<span class="detail-item-label">{{ label }}</span>
|
|
<span class="detail-item-value">
|
|
{% if op[key] is sameas true %}✓{% elif op[key] is sameas false %}✗{% else %}{{ op[key] }}{% endif %}
|
|
</span>
|
|
</div>
|
|
{% endif %}
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Technical -->
|
|
{% if r.get('technical') %}
|
|
<div class="detail-section">
|
|
<h3>Technical SEO</h3>
|
|
<div class="detail-grid">
|
|
{% set t = r.technical %}
|
|
{% for key, label in [('has_robots_txt', 'robots.txt'), ('has_sitemap', 'sitemap.xml'), ('has_canonical', 'Canonical'), ('has_ssl', 'SSL'), ('is_mobile_friendly', 'Mobile Friendly'), ('is_indexable', 'Indexable'), ('viewport_configured', 'Viewport')] %}
|
|
{% if t.get(key) is not none %}
|
|
<div class="detail-item">
|
|
<span class="detail-item-label">{{ label }}</span>
|
|
<span class="detail-item-value" style="color: {{ 'var(--success)' if t[key] else 'var(--error)' }}">
|
|
{% if t[key] %}✓{% else %}✗{% endif %}
|
|
</span>
|
|
</div>
|
|
{% endif %}
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Local SEO -->
|
|
{% if r.get('local_seo') %}
|
|
<div class="detail-section">
|
|
<h3>Local SEO</h3>
|
|
<div class="detail-grid">
|
|
{% set ls = r.local_seo %}
|
|
{% for key, label in [('score', 'Wynik'), ('has_local_business_schema', 'LocalBusiness Schema'), ('nap_on_website', 'NAP na stronie'), ('has_google_maps_embed', 'Google Maps'), ('has_local_keywords', 'Lokalne słowa kluczowe')] %}
|
|
{% if ls.get(key) is not none %}
|
|
<div class="detail-item">
|
|
<span class="detail-item-label">{{ label }}</span>
|
|
<span class="detail-item-value">
|
|
{% if ls[key] is sameas true %}✓{% elif ls[key] is sameas false %}✗{% else %}{{ ls[key] }}{% endif %}
|
|
</span>
|
|
</div>
|
|
{% endif %}
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Raw JSON (collapsible) -->
|
|
<div class="detail-section">
|
|
<details>
|
|
<summary style="cursor: pointer; font-weight: 600;">Pełne dane audytu (JSON)</summary>
|
|
<pre class="json-dump">{{ r|tojson(indent=2) }}</pre>
|
|
</details>
|
|
</div>
|
|
|
|
{% else %}
|
|
<div class="detail-section">
|
|
<p style="color: var(--text-secondary);">Brak szczegółowych danych dla tego audytu.</p>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% endblock %}
|