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
Removes browser alert/confirm dialogs. Adds inline progress bar with spinner, percentage, status messages and log entries. Auto-reloads page after successful audit. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1137 lines
59 KiB
HTML
1137 lines
59 KiB
HTML
{% extends "base.html" %}
|
||
|
||
{% block title %}Audyt SEO: {{ company.name }} - Panel Admina{% endblock %}
|
||
|
||
{% block extra_css %}
|
||
<style>
|
||
.page-header {
|
||
display: flex; justify-content: space-between; align-items: flex-start;
|
||
margin-bottom: var(--spacing-xl); flex-wrap: wrap; gap: var(--spacing-md);
|
||
}
|
||
.page-header h1 { font-size: var(--font-size-2xl); margin-bottom: var(--spacing-xs); }
|
||
.page-header .subtitle { color: var(--text-secondary); font-size: var(--font-size-sm); }
|
||
.page-header .subtitle a { color: var(--primary); text-decoration: none; }
|
||
|
||
.back-link {
|
||
display: inline-flex; align-items: center; gap: var(--spacing-xs);
|
||
color: var(--text-secondary); text-decoration: none; font-size: var(--font-size-sm);
|
||
margin-bottom: var(--spacing-md);
|
||
}
|
||
.back-link:hover { color: var(--primary); }
|
||
|
||
/* Score cards */
|
||
.scores-grid {
|
||
display: grid; grid-template-columns: repeat(4, 1fr);
|
||
gap: var(--spacing-lg); margin-bottom: var(--spacing-2xl);
|
||
}
|
||
.score-card {
|
||
background: var(--surface); padding: var(--spacing-xl);
|
||
border-radius: var(--radius-lg); box-shadow: var(--shadow);
|
||
text-align: center;
|
||
}
|
||
.score-card .score-value {
|
||
font-size: 48px; font-weight: 700; line-height: 1;
|
||
margin-bottom: var(--spacing-xs);
|
||
}
|
||
.score-card .score-value.good { color: #16a34a; }
|
||
.score-card .score-value.medium { color: #d97706; }
|
||
.score-card .score-value.poor { color: #dc2626; }
|
||
.score-card .score-value.na { color: var(--text-muted); font-size: var(--font-size-2xl); }
|
||
.score-card .score-name { font-weight: 600; color: var(--text-primary); margin-bottom: 4px; }
|
||
.score-card .score-desc { font-size: var(--font-size-xs); color: var(--text-secondary); line-height: 1.4; }
|
||
.score-ring {
|
||
width: 100px; height: 100px; margin: 0 auto var(--spacing-md);
|
||
border-radius: 50%; display: flex; align-items: center; justify-content: center;
|
||
font-size: 32px; font-weight: 700;
|
||
}
|
||
.score-ring.good { background: #dcfce7; color: #16a34a; }
|
||
.score-ring.medium { background: #fef3c7; color: #d97706; }
|
||
.score-ring.poor { background: #fee2e2; color: #dc2626; }
|
||
.score-ring.na { background: var(--border); color: var(--text-muted); }
|
||
|
||
/* Sections */
|
||
.section {
|
||
background: var(--surface); padding: var(--spacing-xl);
|
||
border-radius: var(--radius-lg); box-shadow: var(--shadow);
|
||
margin-bottom: var(--spacing-xl);
|
||
}
|
||
.section h2 {
|
||
font-size: var(--font-size-lg); margin-bottom: var(--spacing-lg);
|
||
color: var(--text-primary); border-bottom: 2px solid var(--border);
|
||
padding-bottom: var(--spacing-sm);
|
||
}
|
||
|
||
/* Recommendations */
|
||
.reco-list { list-style: none; }
|
||
.reco-item {
|
||
padding: var(--spacing-md) var(--spacing-lg);
|
||
border-left: 4px solid var(--border);
|
||
margin-bottom: var(--spacing-sm);
|
||
border-radius: 0 var(--radius) var(--radius) 0;
|
||
background: var(--background);
|
||
font-size: var(--font-size-sm);
|
||
line-height: 1.5;
|
||
}
|
||
.reco-item.critical { border-left-color: #dc2626; background: #fef2f2; }
|
||
.reco-item.warning { border-left-color: #d97706; background: #fffbeb; }
|
||
.reco-item.info { border-left-color: #2563eb; background: #eff6ff; }
|
||
.reco-item.success { border-left-color: #16a34a; background: #f0fdf4; }
|
||
.reco-severity {
|
||
display: inline-block; padding: 1px 6px; border-radius: var(--radius-sm);
|
||
font-size: 10px; font-weight: 600; text-transform: uppercase;
|
||
margin-right: var(--spacing-sm); vertical-align: middle;
|
||
}
|
||
.reco-severity.critical { background: #dc2626; color: white; }
|
||
.reco-severity.warning { background: #d97706; color: white; }
|
||
.reco-severity.info { background: #2563eb; color: white; }
|
||
.reco-severity.success { background: #16a34a; color: white; }
|
||
|
||
/* Detail grid */
|
||
.detail-grid {
|
||
display: grid; grid-template-columns: 1fr 1fr;
|
||
gap: var(--spacing-md);
|
||
}
|
||
.detail-item {
|
||
display: flex; justify-content: space-between; align-items: center;
|
||
padding: var(--spacing-sm) var(--spacing-md);
|
||
background: var(--background); border-radius: var(--radius);
|
||
}
|
||
.detail-label { color: var(--text-secondary); font-size: var(--font-size-sm); }
|
||
.detail-value { font-weight: 600; font-size: var(--font-size-sm); }
|
||
.detail-value.yes { color: #16a34a; }
|
||
.detail-value.no { color: #dc2626; }
|
||
|
||
/* Two columns */
|
||
.two-col { display: grid; grid-template-columns: 1fr 1fr; gap: var(--spacing-xl); }
|
||
|
||
/* Audit info bar */
|
||
.audit-info {
|
||
display: flex; gap: var(--spacing-lg); align-items: center;
|
||
padding: var(--spacing-md) var(--spacing-lg);
|
||
background: var(--background); border-radius: var(--radius);
|
||
margin-bottom: var(--spacing-xl); font-size: var(--font-size-sm);
|
||
color: var(--text-secondary); flex-wrap: wrap;
|
||
}
|
||
.audit-info strong { color: var(--text-primary); }
|
||
|
||
@media (max-width: 768px) {
|
||
.scores-grid { grid-template-columns: repeat(2, 1fr); }
|
||
.detail-grid { grid-template-columns: 1fr; }
|
||
.two-col { grid-template-columns: 1fr; }
|
||
}
|
||
</style>
|
||
{% endblock %}
|
||
|
||
{% block content %}
|
||
<a href="{{ url_for('admin.admin_seo') }}" class="back-link">
|
||
<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/>
|
||
</svg>
|
||
Powrot do listy firm
|
||
</a>
|
||
|
||
<div class="page-header">
|
||
<div>
|
||
<h1>{{ company.name }}</h1>
|
||
<div class="subtitle">
|
||
{% if company.website %}
|
||
<a href="{{ company.website }}" target="_blank" rel="noopener">{{ company.website }}</a>
|
||
{% else %}
|
||
Brak strony WWW
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
{% if analysis and current_user.can_access_admin_panel() %}
|
||
<button class="btn btn-primary btn-sm" id="auditBtn" onclick="runAudit()">Uruchom audyt ponownie</button>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<!-- Pasek postepu audytu -->
|
||
<div id="auditProgress" style="display: none; margin-bottom: var(--spacing-lg); padding: var(--spacing-lg); background: var(--surface); border-radius: var(--radius); border: 1px solid var(--border);">
|
||
<div style="display: flex; align-items: center; gap: var(--spacing-sm); margin-bottom: var(--spacing-md);">
|
||
<div id="auditSpinner" style="width: 20px; height: 20px; border: 3px solid var(--border); border-top-color: var(--primary); border-radius: 50%; animation: spin 0.8s linear infinite;"></div>
|
||
<strong id="auditTitle">Audyt SEO w toku...</strong>
|
||
</div>
|
||
<div style="background: var(--border); border-radius: 999px; height: 8px; overflow: hidden; margin-bottom: var(--spacing-sm);">
|
||
<div id="auditBar" style="height: 100%; background: var(--primary); border-radius: 999px; width: 0%; transition: width 0.3s ease;"></div>
|
||
</div>
|
||
<div id="auditMessage" style="font-size: var(--font-size-sm); color: var(--text-secondary);"></div>
|
||
<div id="auditLog" style="margin-top: var(--spacing-md); font-size: var(--font-size-xs); max-height: 200px; overflow-y: auto;"></div>
|
||
</div>
|
||
<style>@keyframes spin { to { transform: rotate(360deg); } }</style>
|
||
|
||
{% if analysis and analysis.seo_audited_at %}
|
||
<div class="audit-info">
|
||
<span>Ostatni audyt: <strong>{{ analysis.seo_audited_at.strftime('%d.%m.%Y %H:%M') }}</strong></span>
|
||
{% if analysis.cms_detected %}<span>CMS: <strong>{{ analysis.cms_detected }}</strong></span>{% endif %}
|
||
{% if analysis.hosting_provider %}<span>Hosting: <strong>{{ analysis.hosting_provider }}</strong></span>{% endif %}
|
||
{% if analysis.load_time_ms %}<span>Czas ladowania: <strong>{{ analysis.load_time_ms }}ms</strong></span>{% endif %}
|
||
</div>
|
||
{% endif %}
|
||
|
||
<!-- 4 Score Cards -->
|
||
{% macro score_class(val) %}{% if val is none %}na{% elif val >= 90 %}good{% elif val >= 50 %}medium{% else %}poor{% endif %}{% endmacro %}
|
||
|
||
<div class="scores-grid">
|
||
<div class="score-card">
|
||
<div class="score-ring {{ score_class(analysis.pagespeed_seo_score if analysis else none) }}">
|
||
{{ analysis.pagespeed_seo_score if analysis and analysis.pagespeed_seo_score is not none else '-' }}
|
||
</div>
|
||
<div class="score-name">SEO</div>
|
||
<div class="score-desc">Optymalizacja pod wyszukiwarki: meta tagi, struktura, indeksowalnosc</div>
|
||
</div>
|
||
<div class="score-card">
|
||
<div class="score-ring {{ score_class(analysis.pagespeed_performance_score if analysis else none) }}">
|
||
{{ analysis.pagespeed_performance_score if analysis and analysis.pagespeed_performance_score is not none else '-' }}
|
||
</div>
|
||
<div class="score-name">Performance</div>
|
||
<div class="score-desc">Szybkosc ladowania strony, czas do interakcji, plynnosc dzialania</div>
|
||
</div>
|
||
<div class="score-card">
|
||
<div class="score-ring {{ score_class(analysis.pagespeed_accessibility_score if analysis else none) }}">
|
||
{{ analysis.pagespeed_accessibility_score if analysis and analysis.pagespeed_accessibility_score is not none else '-' }}
|
||
</div>
|
||
<div class="score-name">Accessibility</div>
|
||
<div class="score-desc">Dostepnosc dla osob z niepelnosprawnosciami: kontrast, opisy, nawigacja klawiatura</div>
|
||
</div>
|
||
<div class="score-card">
|
||
<div class="score-ring {{ score_class(analysis.pagespeed_best_practices_score if analysis else none) }}">
|
||
{{ analysis.pagespeed_best_practices_score if analysis and analysis.pagespeed_best_practices_score is not none else '-' }}
|
||
</div>
|
||
<div class="score-name">Best Practices</div>
|
||
<div class="score-desc">Bezpieczenstwo (HTTPS), nowoczesne standardy, brak bledow technicznych</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Zalecenia -->
|
||
<div class="section">
|
||
<h2>Zalecenia — co poprawic</h2>
|
||
{% if recommendations %}
|
||
<ul class="reco-list">
|
||
{% for r in recommendations %}
|
||
<li class="reco-item {{ r.severity }}">
|
||
<span class="reco-severity {{ r.severity }}">
|
||
{% if r.severity == 'critical' %}KRYTYCZNE{% elif r.severity == 'warning' %}UWAGA{% elif r.severity == 'success' %}OK{% else %}INFO{% endif %}
|
||
</span>
|
||
{{ r.text }}
|
||
</li>
|
||
{% endfor %}
|
||
</ul>
|
||
{% else %}
|
||
<p class="text-muted">Brak danych do analizy. Uruchom audyt, zeby zobaczyc zalecenia.</p>
|
||
{% endif %}
|
||
</div>
|
||
|
||
{% if analysis %}
|
||
<div class="two-col">
|
||
<!-- SEO & Tresc -->
|
||
<div class="section">
|
||
<h2>SEO i tresc strony</h2>
|
||
<div class="detail-grid">
|
||
<div class="detail-item">
|
||
<span class="detail-label">Tytul strony (meta title)</span>
|
||
<span class="detail-value {% if analysis.meta_title or analysis.seo_title %}yes{% else %}no{% endif %}">
|
||
{% if analysis.meta_title or analysis.seo_title %}Jest{% else %}Brak{% endif %}
|
||
</span>
|
||
</div>
|
||
<div class="detail-item">
|
||
<span class="detail-label">Opis strony (meta description)</span>
|
||
<span class="detail-value {% if analysis.meta_description or analysis.seo_description %}yes{% else %}no{% endif %}">
|
||
{% if analysis.meta_description or analysis.seo_description %}Jest{% else %}Brak{% endif %}
|
||
</span>
|
||
</div>
|
||
<div class="detail-item">
|
||
<span class="detail-label">Sitemap.xml</span>
|
||
<span class="detail-value {% if analysis.has_sitemap %}yes{% else %}no{% endif %}">
|
||
{% if analysis.has_sitemap %}Jest{% else %}Brak{% endif %}
|
||
</span>
|
||
</div>
|
||
<div class="detail-item">
|
||
<span class="detail-label">Robots.txt</span>
|
||
<span class="detail-value {% if analysis.has_robots_txt %}yes{% else %}no{% endif %}">
|
||
{% if analysis.has_robots_txt %}Jest{% else %}Brak{% endif %}
|
||
</span>
|
||
</div>
|
||
<div class="detail-item">
|
||
<span class="detail-label">Dane strukturalne (Schema.org)</span>
|
||
<span class="detail-value {% if analysis.has_structured_data %}yes{% else %}no{% endif %}">
|
||
{% if analysis.has_structured_data %}Jest{% else %}Brak{% endif %}
|
||
</span>
|
||
</div>
|
||
<div class="detail-item">
|
||
<span class="detail-label">Canonical URL</span>
|
||
<span class="detail-value {% if analysis.has_canonical %}yes{% else %}no{% endif %}">
|
||
{% if analysis.has_canonical %}Jest{% else %}Brak{% endif %}
|
||
</span>
|
||
</div>
|
||
<div class="detail-item">
|
||
<span class="detail-label">Open Graph (Facebook)</span>
|
||
<span class="detail-value {% if analysis.has_og_tags %}yes{% else %}no{% endif %}">
|
||
{% if analysis.has_og_tags %}Jest{% else %}Brak{% endif %}
|
||
</span>
|
||
</div>
|
||
<div class="detail-item">
|
||
<span class="detail-label">Twitter Cards</span>
|
||
<span class="detail-value {% if analysis.has_twitter_cards %}yes{% else %}no{% endif %}">
|
||
{% if analysis.has_twitter_cards %}Jest{% else %}Brak{% endif %}
|
||
</span>
|
||
</div>
|
||
<div class="detail-item">
|
||
<span class="detail-label">Naglowki H1</span>
|
||
<span class="detail-value {% if analysis.h1_count == 1 %}yes{% elif analysis.h1_count is not none %}no{% endif %}">
|
||
{{ analysis.h1_count if analysis.h1_count is not none else '-' }}
|
||
</span>
|
||
</div>
|
||
<div class="detail-item">
|
||
<span class="detail-label">Naglowki H2</span>
|
||
<span class="detail-value">{{ analysis.h2_count if analysis.h2_count is not none else '-' }}</span>
|
||
</div>
|
||
<div class="detail-item">
|
||
<span class="detail-label">Naglowki H3</span>
|
||
<span class="detail-value">{{ analysis.h3_count if analysis.h3_count is not none else '-' }}</span>
|
||
</div>
|
||
{% if analysis.has_hreflang is not none %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">Hreflang — wersje jezykowe</span>
|
||
<span class="detail-value {% if analysis.has_hreflang %}yes{% else %}no{% endif %}">
|
||
{% if analysis.has_hreflang %}Jest{% else %}Brak{% endif %}
|
||
</span>
|
||
</div>
|
||
{% endif %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">Obrazki ogolnie</span>
|
||
<span class="detail-value">{{ analysis.total_images if analysis.total_images is not none else '-' }}</span>
|
||
</div>
|
||
<div class="detail-item">
|
||
<span class="detail-label">Obrazki bez opisu (alt)</span>
|
||
<span class="detail-value {% if analysis.images_without_alt and analysis.images_without_alt > 0 %}no{% else %}yes{% endif %}">
|
||
{{ analysis.images_without_alt if analysis.images_without_alt is not none else '-' }}
|
||
</span>
|
||
</div>
|
||
<div class="detail-item">
|
||
<span class="detail-label">Linki wewnetrzne</span>
|
||
<span class="detail-value">{{ analysis.internal_links_count if analysis.internal_links_count is not none else '-' }}</span>
|
||
</div>
|
||
<div class="detail-item">
|
||
<span class="detail-label">Linki zewnetrzne</span>
|
||
<span class="detail-value">{{ analysis.external_links_count if analysis.external_links_count is not none else '-' }}</span>
|
||
</div>
|
||
</div>
|
||
|
||
{% if analysis.meta_title or analysis.seo_title %}
|
||
<div style="margin-top: var(--spacing-lg); padding: var(--spacing-md); background: var(--background); border-radius: var(--radius);">
|
||
<div class="detail-label" style="margin-bottom: 4px;">Tytul strony:</div>
|
||
<div style="font-size: var(--font-size-sm); color: var(--text-primary);">{{ analysis.meta_title or analysis.seo_title }}</div>
|
||
</div>
|
||
{% endif %}
|
||
{% if analysis.meta_description or analysis.seo_description %}
|
||
<div style="margin-top: var(--spacing-sm); padding: var(--spacing-md); background: var(--background); border-radius: var(--radius);">
|
||
<div class="detail-label" style="margin-bottom: 4px;">Opis strony:</div>
|
||
<div style="font-size: var(--font-size-sm); color: var(--text-primary);">{{ analysis.meta_description or analysis.seo_description }}</div>
|
||
</div>
|
||
{% endif %}
|
||
{% if analysis.meta_keywords %}
|
||
<div style="margin-top: var(--spacing-sm); padding: var(--spacing-md); background: var(--background); border-radius: var(--radius);">
|
||
<div class="detail-label" style="margin-bottom: 4px;">Meta keywords (slowa kluczowe w kodzie strony):</div>
|
||
<div style="font-size: var(--font-size-sm); color: var(--text-secondary);">{{ analysis.meta_keywords }}</div>
|
||
</div>
|
||
{% endif %}
|
||
{% if analysis.canonical_url %}
|
||
<div style="margin-top: var(--spacing-sm); padding: var(--spacing-md); background: var(--background); border-radius: var(--radius);">
|
||
<div class="detail-label" style="margin-bottom: 4px;">Canonical URL — adres kanoniczny strony:</div>
|
||
<div style="font-size: var(--font-size-sm); color: var(--text-primary); word-break: break-all;">{{ analysis.canonical_url }}</div>
|
||
</div>
|
||
{% endif %}
|
||
{% if analysis.noindex_reason %}
|
||
<div style="margin-top: var(--spacing-sm); padding: var(--spacing-md); background: #fef2f2; border-radius: var(--radius); border-left: 3px solid var(--danger);">
|
||
<div class="detail-label" style="margin-bottom: 4px; color: var(--danger);">Powod blokady indeksowania:</div>
|
||
<div style="font-size: var(--font-size-sm); color: var(--text-primary);">{{ analysis.noindex_reason }}</div>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<!-- Podglad Open Graph — jak strona wyglada na Facebooku -->
|
||
{% if analysis.og_title or analysis.og_description or analysis.og_image %}
|
||
<div class="section">
|
||
<h2>Open Graph — podglad udostepnienia na Facebooku</h2>
|
||
<p style="font-size: var(--font-size-xs); color: var(--text-secondary); margin-bottom: var(--spacing-md);">
|
||
Tak wyglada link do tej strony udostepniony na Facebooku, LinkedIn czy Messengerze.
|
||
</p>
|
||
<div style="max-width: 500px; border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; background: #f0f2f5;">
|
||
{% if analysis.og_image %}
|
||
<div style="width: 100%; height: 260px; background: #e4e6eb; display: flex; align-items: center; justify-content: center; overflow: hidden;">
|
||
<img src="{{ analysis.og_image }}" alt="OG Image" style="width: 100%; height: 100%; object-fit: cover;" onerror="this.parentElement.innerHTML='<span style=\'color: var(--text-secondary); font-size: var(--font-size-sm);\'>Obrazek niedostepny</span>'">
|
||
</div>
|
||
{% else %}
|
||
<div style="width: 100%; height: 80px; background: #e4e6eb; display: flex; align-items: center; justify-content: center;">
|
||
<span style="color: var(--text-secondary); font-size: var(--font-size-sm);">Brak obrazka Open Graph</span>
|
||
</div>
|
||
{% endif %}
|
||
<div style="padding: var(--spacing-md);">
|
||
<div style="font-size: 11px; color: #65676b; text-transform: uppercase;">{{ analysis.final_url or analysis.website_url or '' }}</div>
|
||
<div style="font-weight: 600; font-size: var(--font-size-base); color: #050505; margin-top: 4px;">{{ analysis.og_title or analysis.meta_title or analysis.seo_title or 'Brak tytulu' }}</div>
|
||
{% if analysis.og_description %}
|
||
<div style="font-size: var(--font-size-sm); color: #65676b; margin-top: 4px; line-height: 1.3;">{{ analysis.og_description[:150] }}{% if analysis.og_description|length > 150 %}...{% endif %}</div>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
|
||
<!-- Techniczne -->
|
||
<div class="section">
|
||
<h2>Dane techniczne</h2>
|
||
<div class="detail-grid">
|
||
<div class="detail-item">
|
||
<span class="detail-label">SSL (HTTPS)</span>
|
||
<span class="detail-value {% if analysis.has_ssl %}yes{% else %}no{% endif %}">
|
||
{% if analysis.has_ssl %}Tak{% else %}Nie{% endif %}
|
||
</span>
|
||
</div>
|
||
<div class="detail-item">
|
||
<span class="detail-label">Responsywnosc (mobile)</span>
|
||
<span class="detail-value {% if analysis.is_responsive or analysis.is_mobile_friendly %}yes{% else %}no{% endif %}">
|
||
{% if analysis.is_responsive or analysis.is_mobile_friendly %}Tak{% else %}Nie{% endif %}
|
||
</span>
|
||
</div>
|
||
<div class="detail-item">
|
||
<span class="detail-label">Indeksowalnosc</span>
|
||
<span class="detail-value {% if analysis.is_indexable %}yes{% else %}no{% endif %}">
|
||
{% if analysis.is_indexable %}Tak{% else %}Nie{% endif %}
|
||
</span>
|
||
</div>
|
||
<div class="detail-item">
|
||
<span class="detail-label">Viewport (meta tag)</span>
|
||
<span class="detail-value {% if analysis.viewport_configured or analysis.has_viewport_meta %}yes{% else %}no{% endif %}">
|
||
{% if analysis.viewport_configured or analysis.has_viewport_meta %}Tak{% else %}Nie{% endif %}
|
||
</span>
|
||
</div>
|
||
<div class="detail-item">
|
||
<span class="detail-label">Jezyk strony (html lang)</span>
|
||
<span class="detail-value">{{ analysis.html_lang or '-' }}</span>
|
||
</div>
|
||
<div class="detail-item">
|
||
<span class="detail-label">CMS / System</span>
|
||
<span class="detail-value">{{ analysis.cms_detected or '-' }}</span>
|
||
</div>
|
||
{% if analysis.largest_contentful_paint_ms %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">LCP — czas ladowania glownej tresci</span>
|
||
<span class="detail-value {% if analysis.largest_contentful_paint_ms <= 2500 %}yes{% elif analysis.largest_contentful_paint_ms <= 4000 %}{% else %}no{% endif %}">
|
||
{{ (analysis.largest_contentful_paint_ms / 1000)|round(1) }}s
|
||
</span>
|
||
</div>
|
||
{% endif %}
|
||
{% if analysis.cumulative_layout_shift is not none %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">CLS — stabilnosc wizualna strony</span>
|
||
<span class="detail-value {% if analysis.cumulative_layout_shift|float <= 0.1 %}yes{% elif analysis.cumulative_layout_shift|float <= 0.25 %}{% else %}no{% endif %}">
|
||
{{ analysis.cumulative_layout_shift }}
|
||
</span>
|
||
</div>
|
||
{% endif %}
|
||
{% if analysis.interaction_to_next_paint_ms %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">INP — szybkosc reakcji na klikniecia</span>
|
||
<span class="detail-value {% if analysis.interaction_to_next_paint_ms <= 200 %}yes{% elif analysis.interaction_to_next_paint_ms <= 500 %}{% else %}no{% endif %}">
|
||
{{ analysis.interaction_to_next_paint_ms }}ms
|
||
</span>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
|
||
{% if analysis.security_headers_count is not none %}
|
||
<h3 style="margin-top: var(--spacing-lg); font-size: var(--font-size-base); color: var(--text-secondary);">Zabezpieczenia</h3>
|
||
<div class="detail-grid" style="margin-top: var(--spacing-sm);">
|
||
<div class="detail-item">
|
||
<span class="detail-label">HSTS — wymuszenie HTTPS</span>
|
||
<span class="detail-value {% if analysis.has_hsts %}yes{% else %}no{% endif %}">
|
||
{% if analysis.has_hsts %}Tak{% else %}Nie{% endif %}
|
||
</span>
|
||
</div>
|
||
<div class="detail-item">
|
||
<span class="detail-label">CSP — ochrona przed wstrzykiwaniem skryptow</span>
|
||
<span class="detail-value {% if analysis.has_csp %}yes{% else %}no{% endif %}">
|
||
{% if analysis.has_csp %}Tak{% else %}Nie{% endif %}
|
||
</span>
|
||
</div>
|
||
<div class="detail-item">
|
||
<span class="detail-label">X-Frame-Options — ochrona przed osadzaniem</span>
|
||
<span class="detail-value {% if analysis.has_x_frame_options %}yes{% else %}no{% endif %}">
|
||
{% if analysis.has_x_frame_options %}Tak{% else %}Nie{% endif %}
|
||
</span>
|
||
</div>
|
||
<div class="detail-item">
|
||
<span class="detail-label">X-Content-Type — ochrona typu plikow</span>
|
||
<span class="detail-value {% if analysis.has_x_content_type_options %}yes{% else %}no{% endif %}">
|
||
{% if analysis.has_x_content_type_options %}Tak{% else %}Nie{% endif %}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Tresc i slowa kluczowe -->
|
||
{% if analysis.content_summary or analysis.services_extracted or analysis.main_keywords or analysis.word_count_homepage %}
|
||
<div class="section">
|
||
<h2>Tresc strony i slowa kluczowe</h2>
|
||
<div class="detail-grid">
|
||
{% if analysis.word_count_homepage %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">Liczba slow na stronie glownej</span>
|
||
<span class="detail-value">{{ analysis.word_count_homepage }}</span>
|
||
</div>
|
||
{% endif %}
|
||
{% if analysis.content_richness_score %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">Bogactwo tresci</span>
|
||
<span class="detail-value {% if analysis.content_richness_score >= 7 %}yes{% elif analysis.content_richness_score >= 4 %}{% else %}no{% endif %}">{{ analysis.content_richness_score }}/10</span>
|
||
</div>
|
||
{% endif %}
|
||
{% if analysis.page_count_estimate %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">Szacowana liczba podstron</span>
|
||
<span class="detail-value">{{ analysis.page_count_estimate }}</span>
|
||
</div>
|
||
{% endif %}
|
||
{% if analysis.google_indexed_pages %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">Stron zaindeksowanych w Google</span>
|
||
<span class="detail-value">{{ analysis.google_indexed_pages }}</span>
|
||
</div>
|
||
{% endif %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">Blog firmowy</span>
|
||
<span class="detail-value {% if analysis.has_blog %}yes{% else %}no{% endif %}">{% if analysis.has_blog %}Jest{% else %}Brak{% endif %}</span>
|
||
</div>
|
||
<div class="detail-item">
|
||
<span class="detail-label">Formularz kontaktowy</span>
|
||
<span class="detail-value {% if analysis.has_contact_form %}yes{% else %}no{% endif %}">{% if analysis.has_contact_form %}Jest{% else %}Brak{% endif %}</span>
|
||
</div>
|
||
{% if analysis.has_portfolio is not none %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">Portfolio / Realizacje</span>
|
||
<span class="detail-value {% if analysis.has_portfolio %}yes{% else %}no{% endif %}">{% if analysis.has_portfolio %}Jest{% else %}Brak{% endif %}</span>
|
||
</div>
|
||
{% endif %}
|
||
{% if analysis.has_live_chat is not none %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">Live chat na stronie</span>
|
||
<span class="detail-value {% if analysis.has_live_chat %}yes{% else %}no{% endif %}">{% if analysis.has_live_chat %}Jest{% else %}Brak{% endif %}</span>
|
||
</div>
|
||
{% endif %}
|
||
{% if analysis.broken_links_count is not none %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">Zlamane linki</span>
|
||
<span class="detail-value {% if analysis.broken_links_count == 0 %}yes{% else %}no{% endif %}">{{ analysis.broken_links_count }}</span>
|
||
</div>
|
||
{% endif %}
|
||
{% if analysis.modern_image_ratio is not none %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">Nowoczesne formaty obrazkow (WebP/AVIF)</span>
|
||
<span class="detail-value {% if analysis.modern_image_ratio|float >= 50 %}yes{% else %}no{% endif %}">{{ analysis.modern_image_ratio|round(0)|int }}%</span>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
|
||
{% if analysis.h1_text %}
|
||
<div style="margin-top: var(--spacing-lg); padding: var(--spacing-md); background: var(--background); border-radius: var(--radius);">
|
||
<div class="detail-label" style="margin-bottom: 4px;">Naglowek H1 strony:</div>
|
||
<div style="font-size: var(--font-size-sm); color: var(--text-primary); font-weight: 600;">{{ analysis.h1_text }}</div>
|
||
</div>
|
||
{% endif %}
|
||
{% if analysis.content_summary %}
|
||
<div style="margin-top: var(--spacing-sm); padding: var(--spacing-md); background: var(--background); border-radius: var(--radius);">
|
||
<div class="detail-label" style="margin-bottom: 4px;">Podsumowanie tresci (AI):</div>
|
||
<div style="font-size: var(--font-size-sm); color: var(--text-primary); line-height: 1.5;">{{ analysis.content_summary }}</div>
|
||
</div>
|
||
{% endif %}
|
||
{% if analysis.services_extracted %}
|
||
<div style="margin-top: var(--spacing-sm); padding: var(--spacing-md); background: var(--background); border-radius: var(--radius);">
|
||
<div class="detail-label" style="margin-bottom: 4px;">Wykryte uslugi:</div>
|
||
<div style="display: flex; flex-wrap: wrap; gap: 4px; margin-top: 4px;">
|
||
{% for service in analysis.services_extracted %}
|
||
<span style="padding: 2px 8px; background: #dbeafe; color: #1e40af; border-radius: var(--radius-sm); font-size: 11px;">{{ service }}</span>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
{% if analysis.main_keywords %}
|
||
<div style="margin-top: var(--spacing-sm); padding: var(--spacing-md); background: var(--background); border-radius: var(--radius);">
|
||
<div class="detail-label" style="margin-bottom: 4px;">Glowne slowa kluczowe:</div>
|
||
<div style="display: flex; flex-wrap: wrap; gap: 4px; margin-top: 4px;">
|
||
{% for kw in analysis.main_keywords %}
|
||
<span style="padding: 2px 8px; background: #f0fdf4; color: #166534; border-radius: var(--radius-sm); font-size: 11px;">{{ kw }}</span>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
{% if analysis.structured_data_types %}
|
||
<div style="margin-top: var(--spacing-sm); padding: var(--spacing-md); background: var(--background); border-radius: var(--radius);">
|
||
<div class="detail-label" style="margin-bottom: 4px;">Typy danych strukturalnych (Schema.org):</div>
|
||
<div style="display: flex; flex-wrap: wrap; gap: 4px; margin-top: 4px;">
|
||
{% for sdt in analysis.structured_data_types %}
|
||
<span style="padding: 2px 8px; background: #fef3c7; color: #92400e; border-radius: var(--radius-sm); font-size: 11px;">{{ sdt }}</span>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
{% endif %}
|
||
|
||
<!-- CrUX — dane od prawdziwych uzytkownikow -->
|
||
{% if analysis.crux_lcp_ms or analysis.crux_inp_ms or analysis.crux_cls is not none %}
|
||
<div class="section">
|
||
<h2>Core Web Vitals — dane od prawdziwych uzytkownikow (CrUX)</h2>
|
||
<p style="font-size: var(--font-size-xs); color: var(--text-secondary); margin-bottom: var(--spacing-md);">
|
||
Te metryki pochodza z przegladarek Chrome prawdziwych odwiedzajacych, nie z testow laboratoryjnych. Wyniki moga sie roznic od Lighthouse.
|
||
</p>
|
||
<div class="detail-grid">
|
||
{% if analysis.crux_lcp_ms %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">LCP — czas glownej tresci (p75)</span>
|
||
<span class="detail-value {% if analysis.crux_lcp_ms <= 2500 %}yes{% elif analysis.crux_lcp_ms <= 4000 %}{% else %}no{% endif %}">{{ (analysis.crux_lcp_ms / 1000)|round(1) }}s</span>
|
||
</div>
|
||
{% endif %}
|
||
{% if analysis.crux_fcp_ms %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">FCP — czas pierwszej tresci (p75)</span>
|
||
<span class="detail-value {% if analysis.crux_fcp_ms <= 1800 %}yes{% elif analysis.crux_fcp_ms <= 3000 %}{% else %}no{% endif %}">{{ (analysis.crux_fcp_ms / 1000)|round(1) }}s</span>
|
||
</div>
|
||
{% endif %}
|
||
{% if analysis.crux_inp_ms %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">INP — szybkosc reakcji (p75)</span>
|
||
<span class="detail-value {% if analysis.crux_inp_ms <= 200 %}yes{% elif analysis.crux_inp_ms <= 500 %}{% else %}no{% endif %}">{{ analysis.crux_inp_ms }}ms</span>
|
||
</div>
|
||
{% endif %}
|
||
{% if analysis.crux_cls is not none %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">CLS — stabilnosc wizualna (p75)</span>
|
||
<span class="detail-value {% if analysis.crux_cls|float <= 0.1 %}yes{% elif analysis.crux_cls|float <= 0.25 %}{% else %}no{% endif %}">{{ analysis.crux_cls }}</span>
|
||
</div>
|
||
{% endif %}
|
||
{% if analysis.crux_ttfb_ms %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">TTFB — czas odpowiedzi serwera (p75)</span>
|
||
<span class="detail-value {% if analysis.crux_ttfb_ms <= 800 %}yes{% elif analysis.crux_ttfb_ms <= 1800 %}{% else %}no{% endif %}">{{ analysis.crux_ttfb_ms }}ms</span>
|
||
</div>
|
||
{% endif %}
|
||
{% if analysis.crux_lcp_good_pct is not none %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">Uzytkownicy z dobrym LCP</span>
|
||
<span class="detail-value {% if analysis.crux_lcp_good_pct|float >= 75 %}yes{% elif analysis.crux_lcp_good_pct|float >= 50 %}{% else %}no{% endif %}">{{ analysis.crux_lcp_good_pct }}%</span>
|
||
</div>
|
||
{% endif %}
|
||
{% if analysis.crux_inp_good_pct is not none %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">Uzytkownicy z dobrym INP</span>
|
||
<span class="detail-value {% if analysis.crux_inp_good_pct|float >= 75 %}yes{% elif analysis.crux_inp_good_pct|float >= 50 %}{% else %}no{% endif %}">{{ analysis.crux_inp_good_pct }}%</span>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
|
||
<!-- Google Search Console -->
|
||
{% if analysis.gsc_clicks or analysis.gsc_impressions %}
|
||
<div class="section">
|
||
<h2>Google Search Console — widocznosc w wyszukiwarce</h2>
|
||
<p style="font-size: var(--font-size-xs); color: var(--text-secondary); margin-bottom: var(--spacing-md);">
|
||
Dane z ostatnich {{ analysis.gsc_period_days or 28 }} dni. Pokazuja, jak strona wypada w wynikach wyszukiwania Google.
|
||
</p>
|
||
<div class="detail-grid">
|
||
<div class="detail-item">
|
||
<span class="detail-label">Klikniecia z Google</span>
|
||
<span class="detail-value">{{ analysis.gsc_clicks or 0 }}</span>
|
||
</div>
|
||
<div class="detail-item">
|
||
<span class="detail-label">Wyswietlenia w Google</span>
|
||
<span class="detail-value">{{ analysis.gsc_impressions or 0 }}</span>
|
||
</div>
|
||
{% if analysis.gsc_ctr %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">CTR — procent klikniec</span>
|
||
<span class="detail-value">{{ analysis.gsc_ctr }}%</span>
|
||
</div>
|
||
{% endif %}
|
||
{% if analysis.gsc_avg_position %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">Srednia pozycja w Google</span>
|
||
<span class="detail-value {% if analysis.gsc_avg_position|float <= 10 %}yes{% elif analysis.gsc_avg_position|float <= 30 %}{% else %}no{% endif %}">{{ analysis.gsc_avg_position }}</span>
|
||
</div>
|
||
{% endif %}
|
||
{% if analysis.gsc_index_status %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">Status indeksowania</span>
|
||
<span class="detail-value {% if analysis.gsc_index_status == 'PASS' %}yes{% else %}no{% endif %}">{{ analysis.gsc_index_status }}</span>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
{% if analysis.gsc_top_queries %}
|
||
<div style="margin-top: var(--spacing-lg); padding: var(--spacing-md); background: var(--background); border-radius: var(--radius);">
|
||
<div class="detail-label" style="margin-bottom: var(--spacing-sm);">Top zapytania, po ktorych ludzie trafiaja na strone:</div>
|
||
<table style="width: 100%; font-size: var(--font-size-sm); border-collapse: collapse;">
|
||
<thead>
|
||
<tr style="border-bottom: 1px solid var(--border);">
|
||
<th style="text-align: left; padding: 4px 8px; color: var(--text-secondary);">Zapytanie</th>
|
||
<th style="text-align: right; padding: 4px 8px; color: var(--text-secondary);">Klikniecia</th>
|
||
<th style="text-align: right; padding: 4px 8px; color: var(--text-secondary);">Wyswietlenia</th>
|
||
<th style="text-align: right; padding: 4px 8px; color: var(--text-secondary);">Pozycja</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for q in analysis.gsc_top_queries[:10] %}
|
||
<tr style="border-bottom: 1px solid var(--border);">
|
||
<td style="padding: 4px 8px;">{{ q.query if q.query is defined else q.get('keys', [''])[0] }}</td>
|
||
<td style="text-align: right; padding: 4px 8px; font-weight: 600;">{{ q.clicks if q.clicks is defined else q.get('clicks', 0) }}</td>
|
||
<td style="text-align: right; padding: 4px 8px;">{{ q.impressions if q.impressions is defined else q.get('impressions', 0) }}</td>
|
||
<td style="text-align: right; padding: 4px 8px;">{{ "%.1f"|format(q.position if q.position is defined else q.get('position', 0)) }}</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
{% endif %}
|
||
{% if analysis.gsc_top_pages %}
|
||
<div style="margin-top: var(--spacing-sm); padding: var(--spacing-md); background: var(--background); border-radius: var(--radius);">
|
||
<div class="detail-label" style="margin-bottom: var(--spacing-sm);">Najpopularniejsze podstrony w Google:</div>
|
||
<table style="width: 100%; font-size: var(--font-size-sm); border-collapse: collapse;">
|
||
<thead>
|
||
<tr style="border-bottom: 1px solid var(--border);">
|
||
<th style="text-align: left; padding: 4px 8px; color: var(--text-secondary);">Strona</th>
|
||
<th style="text-align: right; padding: 4px 8px; color: var(--text-secondary);">Klikniecia</th>
|
||
<th style="text-align: right; padding: 4px 8px; color: var(--text-secondary);">Wyswietlenia</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for p in analysis.gsc_top_pages[:10] %}
|
||
<tr style="border-bottom: 1px solid var(--border);">
|
||
<td style="padding: 4px 8px; word-break: break-all; max-width: 300px;">{{ p.get('page', p.get('keys', [''])[0]) }}</td>
|
||
<td style="text-align: right; padding: 4px 8px; font-weight: 600;">{{ p.get('clicks', 0) }}</td>
|
||
<td style="text-align: right; padding: 4px 8px;">{{ p.get('impressions', 0) }}</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
{% endif %}
|
||
|
||
<!-- Google Business Profile -->
|
||
{% if analysis.google_rating or analysis.google_place_id %}
|
||
<div class="section">
|
||
<h2>Google Business Profile</h2>
|
||
<div class="detail-grid">
|
||
{% if analysis.google_rating %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">Ocena Google</span>
|
||
<span class="detail-value">{{ analysis.google_rating }}/5 ({{ analysis.google_reviews_count or 0 }} opinii)</span>
|
||
</div>
|
||
{% endif %}
|
||
{% if analysis.google_name %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">Nazwa w Google</span>
|
||
<span class="detail-value">{{ analysis.google_name }}</span>
|
||
</div>
|
||
{% endif %}
|
||
{% if analysis.google_address %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">Adres w Google</span>
|
||
<span class="detail-value">{{ analysis.google_address }}</span>
|
||
</div>
|
||
{% endif %}
|
||
{% if analysis.google_phone %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">Telefon z Google</span>
|
||
<span class="detail-value">{{ analysis.google_phone }}</span>
|
||
</div>
|
||
{% endif %}
|
||
{% if analysis.google_website %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">Strona WWW w Google</span>
|
||
<span class="detail-value"><a href="{{ analysis.google_website }}" target="_blank" rel="noopener" style="color: var(--primary); font-size: 11px; word-break: break-all;">{{ analysis.google_website }}</a></span>
|
||
</div>
|
||
{% endif %}
|
||
{% if analysis.google_business_status %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">Status</span>
|
||
<span class="detail-value {% if analysis.google_business_status == 'OPERATIONAL' %}yes{% endif %}">{{ analysis.google_business_status }}</span>
|
||
</div>
|
||
{% endif %}
|
||
{% if analysis.google_types %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">Kategorie w Google</span>
|
||
<span class="detail-value" style="font-size: 11px;">{{ analysis.google_types|join(', ') }}</span>
|
||
</div>
|
||
{% endif %}
|
||
{% if analysis.google_photos_count %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">Zdjecia w Google</span>
|
||
<span class="detail-value">{{ analysis.google_photos_count }}</span>
|
||
</div>
|
||
{% endif %}
|
||
{% if analysis.google_posts_count %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">Posty w Google (Google Posts)</span>
|
||
<span class="detail-value">{{ analysis.google_posts_count }}</span>
|
||
</div>
|
||
{% endif %}
|
||
{% if analysis.google_review_response_rate is not none %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">Odpowiedzi na opinie</span>
|
||
<span class="detail-value {% if analysis.google_review_response_rate|float >= 80 %}yes{% elif analysis.google_review_response_rate|float >= 50 %}{% else %}no{% endif %}">{{ analysis.google_review_response_rate }}%</span>
|
||
</div>
|
||
{% endif %}
|
||
{% if analysis.google_maps_url %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">Link do Google Maps</span>
|
||
<span class="detail-value"><a href="{{ analysis.google_maps_url }}" target="_blank" rel="noopener" style="color: var(--primary);">Otworz</a></span>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
{% if analysis.google_editorial_summary %}
|
||
<div style="margin-top: var(--spacing-lg); padding: var(--spacing-md); background: var(--background); border-radius: var(--radius);">
|
||
<div class="detail-label" style="margin-bottom: 4px;">Opis firmy w Google:</div>
|
||
<div style="font-size: var(--font-size-sm); color: var(--text-primary); line-height: 1.5;">{{ analysis.google_editorial_summary }}</div>
|
||
</div>
|
||
{% endif %}
|
||
{% if analysis.google_opening_hours %}
|
||
<div style="margin-top: var(--spacing-sm); padding: var(--spacing-md); background: var(--background); border-radius: var(--radius);">
|
||
<div class="detail-label" style="margin-bottom: var(--spacing-sm);">Godziny otwarcia z Google:</div>
|
||
{% set hours_data = analysis.google_opening_hours %}
|
||
{% if hours_data is mapping and hours_data.get('weekday_text') %}
|
||
{% for entry in hours_data.weekday_text %}
|
||
<div style="font-size: var(--font-size-sm); padding: 3px 0; border-bottom: 1px solid var(--border);">{{ entry }}</div>
|
||
{% endfor %}
|
||
{% elif hours_data is mapping and hours_data.get('periods') %}
|
||
{% set day_names = {0: 'Niedziela', 1: 'Poniedzialek', 2: 'Wtorek', 3: 'Sroda', 4: 'Czwartek', 5: 'Piatek', 6: 'Sobota'} %}
|
||
{% for period in hours_data.periods %}
|
||
<div style="font-size: var(--font-size-sm); padding: 3px 0; border-bottom: 1px solid var(--border);">
|
||
{{ day_names.get(period.open.day, period.open.day) }}: {{ period.open.time[:2] }}:{{ period.open.time[2:] }}–{{ period.close.time[:2] }}:{{ period.close.time[2:] }}
|
||
</div>
|
||
{% endfor %}
|
||
{% elif hours_data is iterable and hours_data is not string and hours_data is not mapping %}
|
||
{% for entry in hours_data %}
|
||
<div style="font-size: var(--font-size-sm); padding: 3px 0; border-bottom: 1px solid var(--border);">{{ entry }}</div>
|
||
{% endfor %}
|
||
{% else %}
|
||
<div style="font-size: var(--font-size-sm);">{{ hours_data }}</div>
|
||
{% endif %}
|
||
</div>
|
||
{% endif %}
|
||
{% if analysis.google_reviews_data %}
|
||
<div style="margin-top: var(--spacing-sm); padding: var(--spacing-md); background: var(--background); border-radius: var(--radius);">
|
||
<div class="detail-label" style="margin-bottom: var(--spacing-sm);">Ostatnie opinie Google:</div>
|
||
{% for review in analysis.google_reviews_data[:5] %}
|
||
<div style="padding: var(--spacing-sm) 0; border-bottom: 1px solid var(--border); font-size: var(--font-size-sm);">
|
||
<div style="display: flex; justify-content: space-between; margin-bottom: 4px;">
|
||
<span style="font-weight: 600;">{{ review.get('author', review.get('authorAttribution', {}).get('displayName', 'Anonim')) }}</span>
|
||
<span style="color: var(--warning);">
|
||
{% set stars = review.get('rating', review.get('star_rating', 0))|int %}
|
||
{% for i in range(stars) %}★{% endfor %}{% for i in range(5 - stars) %}☆{% endfor %}
|
||
</span>
|
||
</div>
|
||
{% set review_text = review.get('text', review.get('comment', review.get('originalText', {}).get('text', ''))) %}
|
||
{% if review_text %}
|
||
<div style="color: var(--text-secondary); line-height: 1.4;">{{ review_text[:200] }}{% if review_text|length > 200 %}...{% endif %}</div>
|
||
{% endif %}
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
{% endif %}
|
||
|
||
<!-- GBP statystyki -->
|
||
{% if analysis.gbp_impressions_maps or analysis.gbp_impressions_search or analysis.gbp_call_clicks %}
|
||
<div class="section">
|
||
<h2>Statystyki Google Business Profile</h2>
|
||
<p style="font-size: var(--font-size-xs); color: var(--text-secondary); margin-bottom: var(--spacing-md);">
|
||
Dane z ostatnich {{ analysis.gbp_performance_period_days or 30 }} dni.
|
||
</p>
|
||
<div class="detail-grid">
|
||
{% if analysis.gbp_impressions_search %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">Wyswietlenia w wyszukiwarce Google</span>
|
||
<span class="detail-value">{{ analysis.gbp_impressions_search }}</span>
|
||
</div>
|
||
{% endif %}
|
||
{% if analysis.gbp_impressions_maps %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">Wyswietlenia w Google Maps</span>
|
||
<span class="detail-value">{{ analysis.gbp_impressions_maps }}</span>
|
||
</div>
|
||
{% endif %}
|
||
{% if analysis.gbp_website_clicks %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">Klikniecia w strone WWW</span>
|
||
<span class="detail-value">{{ analysis.gbp_website_clicks }}</span>
|
||
</div>
|
||
{% endif %}
|
||
{% if analysis.gbp_call_clicks %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">Klikniecia w telefon</span>
|
||
<span class="detail-value">{{ analysis.gbp_call_clicks }}</span>
|
||
</div>
|
||
{% endif %}
|
||
{% if analysis.gbp_direction_requests %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">Zapytania o dojazd</span>
|
||
<span class="detail-value">{{ analysis.gbp_direction_requests }}</span>
|
||
</div>
|
||
{% endif %}
|
||
{% if analysis.gbp_conversations %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">Wiadomosci (conversations)</span>
|
||
<span class="detail-value">{{ analysis.gbp_conversations }}</span>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
{% if analysis.gbp_search_keywords %}
|
||
<div style="margin-top: var(--spacing-lg); padding: var(--spacing-md); background: var(--background); border-radius: var(--radius);">
|
||
<div class="detail-label" style="margin-bottom: var(--spacing-sm);">Po jakich slowach ludzie szukaja tej firmy w Google Maps:</div>
|
||
<table style="width: 100%; font-size: var(--font-size-sm); border-collapse: collapse;">
|
||
<thead>
|
||
<tr style="border-bottom: 1px solid var(--border);">
|
||
<th style="text-align: left; padding: 4px 8px; color: var(--text-secondary);">Slowo kluczowe</th>
|
||
<th style="text-align: right; padding: 4px 8px; color: var(--text-secondary);">Wyswietlenia</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for kw in analysis.gbp_search_keywords[:15] %}
|
||
<tr style="border-bottom: 1px solid var(--border);">
|
||
<td style="padding: 4px 8px;">{{ kw.get('keyword', kw.get('searchKeyword', kw)) if kw is mapping else kw }}</td>
|
||
<td style="text-align: right; padding: 4px 8px; font-weight: 600;">{{ kw.get('impressions', kw.get('insightsValue', {}).get('value', '-')) if kw is mapping else '' }}</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
{% endif %}
|
||
|
||
<!-- Infrastruktura i szczegoly techniczne -->
|
||
{% if analysis.ssl_expires_at or analysis.final_url or analysis.hosting_provider or analysis.server_software or analysis.hosting_ip %}
|
||
<div class="section">
|
||
<h2>Infrastruktura i szczegoly techniczne</h2>
|
||
<div class="detail-grid">
|
||
{% if analysis.final_url and analysis.final_url != analysis.website_url %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">URL po przekierowaniu</span>
|
||
<span class="detail-value" style="font-size: 11px; word-break: break-all;">{{ analysis.final_url }}</span>
|
||
</div>
|
||
{% endif %}
|
||
{% if analysis.http_status_code %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">Kod odpowiedzi HTTP</span>
|
||
<span class="detail-value {% if analysis.http_status_code == 200 %}yes{% else %}no{% endif %}">{{ analysis.http_status_code }}</span>
|
||
</div>
|
||
{% endif %}
|
||
{% if analysis.ssl_expires_at %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">Certyfikat SSL wygasa</span>
|
||
<span class="detail-value">{{ analysis.ssl_expires_at.strftime('%d.%m.%Y') }}</span>
|
||
</div>
|
||
{% endif %}
|
||
{% if analysis.ssl_issuer %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">Wystawca SSL</span>
|
||
<span class="detail-value">{{ analysis.ssl_issuer }}</span>
|
||
</div>
|
||
{% endif %}
|
||
{% if analysis.hosting_provider or ip_info.get('isp') %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">Hosting</span>
|
||
<span class="detail-value">{{ analysis.hosting_provider or ip_info.get('isp', '') }}</span>
|
||
</div>
|
||
{% endif %}
|
||
{% if ip_info.get('org') and ip_info.get('org') != (analysis.hosting_provider or '') %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">Firma hostingowa</span>
|
||
<span class="detail-value">{{ ip_info.org }}</span>
|
||
</div>
|
||
{% endif %}
|
||
{% if analysis.hosting_ip %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">Adres IP serwera</span>
|
||
<span class="detail-value" style="font-family: monospace;">{{ analysis.hosting_ip }}</span>
|
||
</div>
|
||
{% endif %}
|
||
{% if ip_info.get('city') %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">Lokalizacja serwera</span>
|
||
<span class="detail-value">{{ ip_info.city }}{% if ip_info.get('region') %}, {{ ip_info.region }}{% endif %}{% if ip_info.get('country') %}, {{ ip_info.country }}{% endif %}</span>
|
||
</div>
|
||
{% endif %}
|
||
{% if ip_info.get('as_number') %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">Siec (AS)</span>
|
||
<span class="detail-value" style="font-size: 11px;">{{ ip_info.as_number }}</span>
|
||
</div>
|
||
{% endif %}
|
||
{% if analysis.server_software %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">Oprogramowanie serwera</span>
|
||
<span class="detail-value">{{ analysis.server_software }}</span>
|
||
</div>
|
||
{% endif %}
|
||
{% if analysis.site_author %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">Tworca strony</span>
|
||
<span class="detail-value">{{ analysis.site_author }}</span>
|
||
</div>
|
||
{% endif %}
|
||
{% if analysis.site_generator %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">Generator strony</span>
|
||
<span class="detail-value">{{ analysis.site_generator }}</span>
|
||
</div>
|
||
{% endif %}
|
||
{% if analysis.domain_registrar %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">Rejestrator domeny</span>
|
||
<span class="detail-value">{{ analysis.domain_registrar }}</span>
|
||
</div>
|
||
{% endif %}
|
||
{% if analysis.frameworks_detected %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">Wykryte technologie</span>
|
||
<span class="detail-value">{{ analysis.frameworks_detected|join(', ') }}</span>
|
||
</div>
|
||
{% endif %}
|
||
{% if analysis.last_modified_at %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">Ostatnia modyfikacja strony</span>
|
||
<span class="detail-value">{{ analysis.last_modified_at.strftime('%d.%m.%Y %H:%M') }}</span>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
|
||
<!-- Diagnostyka audytu -->
|
||
{% if analysis and (analysis.audit_source or analysis.audit_errors or analysis.analyzed_at) %}
|
||
<div class="section" style="opacity: 0.7;">
|
||
<h2>Diagnostyka audytu</h2>
|
||
<p style="font-size: var(--font-size-xs); color: var(--text-secondary); margin-bottom: var(--spacing-md);">
|
||
Informacje techniczne o ostatnim uruchomieniu audytu.
|
||
</p>
|
||
<div class="detail-grid">
|
||
{% if analysis.analyzed_at %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">Data audytu</span>
|
||
<span class="detail-value">{{ analysis.analyzed_at.strftime('%d.%m.%Y %H:%M') }}</span>
|
||
</div>
|
||
{% endif %}
|
||
{% if analysis.audit_source %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">Zrodlo danych</span>
|
||
<span class="detail-value">{{ analysis.audit_source }}</span>
|
||
</div>
|
||
{% endif %}
|
||
{% if analysis.audit_version %}
|
||
<div class="detail-item">
|
||
<span class="detail-label">Wersja audytu</span>
|
||
<span class="detail-value">{{ analysis.audit_version }}</span>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
{% if analysis.audit_errors %}
|
||
<div style="margin-top: var(--spacing-md); padding: var(--spacing-md); background: #fef2f2; border-radius: var(--radius); border-left: 3px solid var(--danger);">
|
||
<div class="detail-label" style="margin-bottom: 4px; color: var(--danger);">Bledy podczas audytu:</div>
|
||
<div style="font-size: var(--font-size-sm); color: var(--text-primary); white-space: pre-wrap;">{{ analysis.audit_errors }}</div>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
{% endif %}
|
||
{% endif %}
|
||
{% endblock %}
|
||
|
||
{% block extra_js %}
|
||
var csrfToken = '{{ csrf_token() }}';
|
||
|
||
function runAudit() {
|
||
var btn = document.getElementById('auditBtn');
|
||
var progress = document.getElementById('auditProgress');
|
||
var bar = document.getElementById('auditBar');
|
||
var msg = document.getElementById('auditMessage');
|
||
var title = document.getElementById('auditTitle');
|
||
var spinner = document.getElementById('auditSpinner');
|
||
var log = document.getElementById('auditLog');
|
||
|
||
// Pokazanie postepu
|
||
btn.disabled = true;
|
||
btn.textContent = 'Audyt w toku...';
|
||
progress.style.display = 'block';
|
||
progress.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||
log.innerHTML = '';
|
||
|
||
function addLog(text, type) {
|
||
var entry = document.createElement('div');
|
||
entry.textContent = text;
|
||
entry.style.padding = '2px 0';
|
||
entry.style.borderBottom = '1px solid var(--border)';
|
||
if (type === 'success') { entry.style.color = 'var(--success)'; entry.style.fontWeight = '600'; }
|
||
if (type === 'error') { entry.style.color = 'var(--danger)'; entry.style.fontWeight = '600'; }
|
||
if (type === 'info') { entry.style.color = 'var(--text-secondary)'; entry.style.fontStyle = 'italic'; }
|
||
log.appendChild(entry);
|
||
log.scrollTop = log.scrollHeight;
|
||
}
|
||
|
||
function setProgress(pct, text) {
|
||
bar.style.width = pct + '%';
|
||
if (text) msg.textContent = text;
|
||
}
|
||
|
||
setProgress(10, 'Laczenie z Google PageSpeed Insights...');
|
||
addLog('Rozpoczynam audyt dla: {{ company.name }}', 'info');
|
||
|
||
fetch('/api/seo/audit', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json', 'X-CSRFToken': csrfToken },
|
||
body: JSON.stringify({ slug: '{{ company.slug }}' })
|
||
}).then(function(r) {
|
||
setProgress(70, 'Przetwarzanie wynikow...');
|
||
return r.json();
|
||
}).then(function(data) {
|
||
if (data.success) {
|
||
setProgress(100, 'Audyt zakonczony pomyslnie!');
|
||
title.textContent = 'Audyt zakonczony';
|
||
spinner.style.animation = 'none';
|
||
spinner.style.border = '3px solid var(--success)';
|
||
spinner.innerHTML = '<svg viewBox="0 0 20 20" style="width:14px;height:14px;margin:0"><path d="M5 10l3 3 7-7" stroke="var(--success)" fill="none" stroke-width="2"/></svg>';
|
||
|
||
var seo = data.scores ? data.scores.seo : null;
|
||
var perf = data.scores ? data.scores.performance : null;
|
||
if (seo !== null || perf !== null) {
|
||
addLog('Wyniki: SEO ' + (seo || '-') + ' | Performance ' + (perf || '-'), 'success');
|
||
} else {
|
||
addLog('Audyt zakonczony pomyslnie', 'success');
|
||
}
|
||
addLog('Strona zostanie odswiezona za 2 sekundy...', 'info');
|
||
setTimeout(function() { location.reload(); }, 2000);
|
||
} else {
|
||
setProgress(100, 'Wystapil blad podczas audytu');
|
||
title.textContent = 'Blad audytu';
|
||
spinner.style.animation = 'none';
|
||
spinner.style.border = '3px solid var(--danger)';
|
||
addLog('Blad: ' + (data.error || 'Nieznany blad'), 'error');
|
||
btn.disabled = false;
|
||
btn.textContent = 'Uruchom audyt ponownie';
|
||
}
|
||
}).catch(function(e) {
|
||
setProgress(100, 'Blad polaczenia z serwerem');
|
||
title.textContent = 'Blad polaczenia';
|
||
spinner.style.animation = 'none';
|
||
spinner.style.border = '3px solid var(--danger)';
|
||
addLog('Blad: ' + e.message, 'error');
|
||
btn.disabled = false;
|
||
btn.textContent = 'Uruchom audyt ponownie';
|
||
});
|
||
}
|
||
{% endblock %}
|