{% extends "base.html" %} {% block title %}SEO Portalu - Norda Biznes Partner{% endblock %} {% block container_class %}container-full{% endblock %} {% block extra_css %} {% endblock %} {% block content %}

SEO Portalu

Audyt {{ portal_url }} — pełna historia zmian

Audyt w toku...

{% if audits %} {% set latest = audits[0] %} {% set prev = audits[1] if audits|length > 1 else None %} {% set fr = latest.full_results or {} %} {% set fr_prev = prev.full_results or {} if prev else {} %} {# Helper macros #} {% macro score_card(label, value, prev_value, unit='', threshold_good=90, threshold_ok=50, lower_better=false) %}
{% if value is not none %}{{ value }}{{ unit }}{% else %}—{% endif %}
{{ label }}
{% if prev_value is not none and value is not none %} {% set delta = value - prev_value %}
{{ '+' if delta > 0 else '' }}{{ delta }}{{ unit }}
{% endif %}
{% endmacro %} {% macro check(label, value) %}
{% if value %}{% else %}{% endif %} {{ label }}
{% endmacro %}

Ostatni audyt: {{ latest.audited_at|local_time('%d.%m.%Y %H:%M') }} {% if latest.notes %} — {{ latest.notes }}{% endif %}

PageSpeed Insights

{{ score_card('Performance', latest.pagespeed_performance, prev.pagespeed_performance if prev else None) }} {{ score_card('SEO', latest.pagespeed_seo, prev.pagespeed_seo if prev else None) }} {{ score_card('Accessibility', latest.pagespeed_accessibility, prev.pagespeed_accessibility if prev else None) }} {{ score_card('Best Practices', latest.pagespeed_best_practices, prev.pagespeed_best_practices if prev else None) }}

Core Web Vitals

{% set ps = fr.get('pagespeed', {}) %} {% set cwv = ps.get('core_web_vitals', {}) %} {% set ps_prev = fr_prev.get('pagespeed', {}) %} {% set cwv_prev = ps_prev.get('core_web_vitals', {}) %} {{ score_card('LCP', cwv.get('lcp_ms'), cwv_prev.get('lcp_ms'), 'ms', 2500, 4000, true) }} {{ score_card('FCP', cwv.get('fcp_ms'), cwv_prev.get('fcp_ms'), 'ms', 1800, 3000, true) }} {{ score_card('CLS', cwv.get('cls'), cwv_prev.get('cls'), '', 0.1, 0.25, true) }} {{ score_card('TBT', cwv.get('tbt_ms'), cwv_prev.get('tbt_ms'), 'ms', 200, 600, true) }} {{ score_card('TTFB', cwv.get('ttfb_ms'), cwv_prev.get('ttfb_ms'), 'ms', 800, 1800, true) }} {{ score_card('INP', cwv.get('inp_ms'), cwv_prev.get('inp_ms'), 'ms', 200, 500, true) }} {{ score_card('SI', cwv.get('speed_index_ms') or cwv.get('speed_index'), cwv_prev.get('speed_index_ms') or cwv_prev.get('speed_index'), 'ms', 3400, 5800, true) }} {{ score_card('Load', fr.get('load_time_ms'), fr_prev.get('load_time_ms'), 'ms', 1000, 3000, true) }}

Elementy SEO i bezpieczeństwo

{{ check('Meta Title', latest.has_meta_title) }} {{ check('Meta Description', latest.has_meta_description) }} {{ check('Canonical URL', latest.has_canonical) }} {{ check('robots.txt', latest.has_robots_txt) }} {{ check('sitemap.xml', latest.has_sitemap) }} {{ check('Structured Data', latest.has_structured_data) }} {{ check('Open Graph', latest.has_og_tags) }} {{ check('SSL/HTTPS', latest.has_ssl) }} {{ check('Indexable', fr.get('technical', {}).get('indexability', {}).get('is_indexable', None)) }} {{ check('Viewport', fr.get('onpage', {}).get('meta_tags', {}).get('viewport') is not none) }} {{ check('Lang Attribute', fr.get('onpage', {}).get('has_lang_attribute', false)) }} {{ check('DOCTYPE', fr.get('onpage', {}).get('has_doctype', false)) }} {{ check('Single H1', fr.get('onpage', {}).get('headings', {}).get('has_single_h1', false)) }} {{ check('HSTS', latest.has_hsts) }} {{ check('CSP', latest.has_csp) }} {{ check('X-Frame-Options', latest.has_x_frame) }} {{ check('X-Content-Type', latest.has_x_content_type) }}

Metryki treści i linków

{% set onpage = fr.get('onpage', {}) %} {% set imgs = onpage.get('images', {}) %} {% set links = onpage.get('links', {}) %} {% set headings = onpage.get('headings', {}) %}
{{ onpage.get('word_count', '—') }} Słowa
{{ headings.get('h1_count', '—') }} H1
{{ headings.get('h2_count', '—') }} H2
{{ imgs.get('total_images', '—') }} Obrazy
{{ imgs.get('images_without_alt', '—') }} Bez alt
{{ links.get('total_links', '—') }} Linki
{{ links.get('internal_links', '—') }} Wewnętrzne
{{ links.get('external_links', '—') }} Zewnętrzne

Local SEO, aktualność i cytowania

{% set local = fr.get('local_seo', {}) %} {% set fresh = fr.get('freshness', {}) %} {% set cit = fr.get('citations', []) %} {% set cit_found = cit|selectattr('status', 'equalto', 'found')|list|length if cit else 0 %}
{{ score_card('Local SEO', local.get('local_seo_score'), None, '/100', 70, 40) }} {{ score_card('Aktualność', fresh.get('content_freshness_score'), None, '/100', 80, 40) }} {{ score_card('Cytowania', cit_found, None, '/' ~ (cit|length), cit|length, (cit|length)//2) }} {{ score_card('Overall', fr.get('scores', {}).get('overall_seo'), None, '', 80, 50) }}
{% endif %}

Historia audytów

{% if audits %}
{% for a in audits %} {% set f = a.full_results or {} %} {% set f_ps = f.get('pagespeed', {}) %} {% set f_cwv = f_ps.get('core_web_vitals', {}) %} {% set f_op = f.get('onpage', {}) %} {% set f_imgs = f_op.get('images', {}) %} {% set f_links = f_op.get('links', {}) %} {% set f_heads = f_op.get('headings', {}) %} {% set f_local = f.get('local_seo', {}) %} {% set f_fresh = f.get('freshness', {}) %} {% set f_cit = f.get('citations', []) %} {% set f_cit_found = f_cit|selectattr('status', 'equalto', 'found')|list|length if f_cit else 0 %} {# PageSpeed scores #} {% macro std(val, good=90, ok=50) %}{% endmacro %} {{ std(a.pagespeed_performance) }} {{ std(a.pagespeed_seo) }} {{ std(a.pagespeed_accessibility) }} {{ std(a.pagespeed_best_practices) }} {# CWV - lower is better #} {% macro cwv_td(val, unit='ms', good=2500, ok=4000) %}{% endmacro %} {{ cwv_td(f_cwv.get('lcp_ms'), 'ms', 2500, 4000) }} {{ cwv_td(f_cwv.get('fcp_ms'), 'ms', 1800, 3000) }} {{ cwv_td(f_cwv.get('cls'), '', 0.1, 0.25) }} {{ cwv_td(f_cwv.get('tbt_ms'), 'ms', 200, 600) }} {{ cwv_td(f_cwv.get('ttfb_ms'), 'ms', 800, 1800) }} {{ cwv_td(f_cwv.get('inp_ms'), 'ms', 200, 500) }} {{ cwv_td(f.get('load_time_ms'), 'ms', 1000, 3000) }} {# On-Page booleans #} {% macro bool_td(val) %}{% endmacro %} {{ bool_td(a.has_meta_title) }} {{ bool_td(a.has_meta_description) }} {{ bool_td(a.has_canonical) }} {{ bool_td(a.has_robots_txt) }} {{ bool_td(a.has_sitemap) }} {{ bool_td(a.has_structured_data) }} {{ bool_td(a.has_og_tags) }} {# Security booleans #} {{ bool_td(a.has_hsts) }} {{ bool_td(a.has_csp) }} {{ bool_td(a.has_x_frame) }} {{ bool_td(a.has_x_content_type) }} {# Content metrics #} {# Scores #} {{ std(f_local.get('local_seo_score'), 70, 40) }} {{ std(f_fresh.get('content_freshness_score'), 80, 40) }} {{ std(f.get('scores', {}).get('overall_seo'), 80, 50) }} {% endfor %}
Data PageSpeed Core Web Vitals On-Page SEO Security Content Scores Notatka
PerfSEOA11yBP LCPFCPCLSTBTTTFBINPLoad TitleDescCanonRobotsSitemapSchemaOG HSTSCSPX-FrameX-CT SłowaObrazyNo-AltLinkiH1 LocalFreshCit.Overall
{{ a.audited_at|local_time('%d.%m %H:%M') }}{% if val is not none %}{{ val }}{% else %}—{% endif %}{% if val is not none %}{{ val }}{{ unit }}{% else %}—{% endif %}{% if val %}{% elif val is sameas false %}{% else %}—{% endif %}{{ f_op.get('word_count', '—') }} {{ f_imgs.get('total_images', '—') }} {% if f_imgs.get('images_without_alt') is not none %}{{ f_imgs.get('images_without_alt') }}{% else %}—{% endif %} {{ f_links.get('total_links', '—') }} {{ f_heads.get('h1_count', '—') }}{% if f_cit %}{{ f_cit_found }}/{{ f_cit|length }}{% else %}—{% endif %}{{ a.notes or '' }} Szczegóły
{% else %}

Brak audytów. Kliknij "Uruchom audyt" aby wykonać pierwszy audyt SEO portalu.

{% endif %} {% endblock %} {% block extra_js %} function startAudit() { var btn = document.getElementById('btn-run-audit'); var panel = document.getElementById('audit-progress'); var bar = document.getElementById('progress-bar'); var stepsList = document.getElementById('progress-steps'); var notes = document.getElementById('audit-notes').value; btn.disabled = true; btn.textContent = 'Trwa audyt...'; panel.classList.add('active'); var items = stepsList.querySelectorAll('li'); items.forEach(function(li) { li.className = ''; li.querySelector('.step-icon').innerHTML = '○'; }); bar.style.width = '0%'; var url = '{{ url_for("admin.admin_portal_seo_run_stream") }}' + '?notes=' + encodeURIComponent(notes); var source = new EventSource(url); source.onmessage = function(e) { var data = JSON.parse(e.data); if (data.status === 'complete') { source.close(); bar.style.width = '100%'; btn.textContent = 'Ukończono!'; panel.querySelector('h3').textContent = 'Audyt zakończony'; setTimeout(function() { window.location.reload(); }, 1500); return; } if (data.status === 'error' && !data.step) { source.close(); btn.disabled = false; btn.textContent = 'Uruchom audyt'; panel.querySelector('h3').textContent = 'Błąd: ' + (data.message || 'Nieznany'); return; } if (data.step) { bar.style.width = Math.round((data.step / data.total) * 100) + '%'; var li = stepsList.querySelector('li[data-step="' + data.step + '"]'); if (!li) return; var icon = li.querySelector('.step-icon'); var label = li.childNodes[li.childNodes.length - 1]; if (data.status === 'running') { li.className = 'active'; icon.innerHTML = ''; } else if (data.status === 'done') { li.className = 'done'; icon.innerHTML = '✓'; label.textContent = ' ' + data.message; } else if (data.status === 'error') { li.className = 'error'; icon.innerHTML = '✗'; label.textContent = ' ' + data.message; } else if (data.status === 'warning') { li.className = 'warning'; icon.innerHTML = '⚠'; label.textContent = ' ' + data.message; } else if (data.status === 'skipped') { li.className = ''; icon.innerHTML = '—'; label.textContent = ' ' + data.message; } } }; source.onerror = function() { source.close(); btn.disabled = false; btn.textContent = 'Uruchom audyt'; if (bar.style.width === '100%') return; panel.querySelector('h3').textContent = 'Połączenie przerwane'; }; } {% endblock %}