feat: add detailed SEO audit view per company with recommendations
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
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
- New /admin/seo/<id> detail page with full audit breakdown - 4 score cards with visual rings and Polish descriptions - Recommendations section: critical/warning/info issues with actionable text - SEO checklist: meta tags, sitemap, robots, structured data, OG tags - Technical details: SSL, mobile, CWV metrics, security headers - Google Business Profile section when available - Add "Szczegoly" button in company table (replaces profile link) - Add visible column descriptions above table (not hidden tooltips) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
bc7b206741
commit
b44722fd2c
@ -139,6 +139,180 @@ def admin_seo():
|
||||
db.close()
|
||||
|
||||
|
||||
@bp.route('/seo/<int:company_id>')
|
||||
@login_required
|
||||
@role_required(SystemRole.OFFICE_MANAGER)
|
||||
def admin_seo_detail(company_id):
|
||||
"""Detailed SEO audit view for a single company."""
|
||||
if not is_audit_owner():
|
||||
abort(404)
|
||||
|
||||
db = SessionLocal()
|
||||
try:
|
||||
from sqlalchemy import func
|
||||
|
||||
company = db.query(Company).filter_by(id=company_id).first()
|
||||
if not company:
|
||||
flash('Firma nie istnieje.', 'error')
|
||||
return redirect(url_for('admin.admin_seo'))
|
||||
|
||||
analysis = db.query(CompanyWebsiteAnalysis).filter_by(
|
||||
company_id=company_id
|
||||
).first()
|
||||
|
||||
# Build recommendations from available data
|
||||
recommendations = []
|
||||
|
||||
if not analysis:
|
||||
recommendations.append({
|
||||
'severity': 'info',
|
||||
'text': 'Brak danych audytu. Uruchom audyt SEO dla tej firmy.'
|
||||
})
|
||||
else:
|
||||
# SEO score
|
||||
if analysis.pagespeed_seo_score is not None:
|
||||
if analysis.pagespeed_seo_score < 50:
|
||||
recommendations.append({
|
||||
'severity': 'critical',
|
||||
'text': 'Wynik SEO jest bardzo niski. Strona ma powazne problemy z optymalizacja pod wyszukiwarki.'
|
||||
})
|
||||
elif analysis.pagespeed_seo_score < 90:
|
||||
recommendations.append({
|
||||
'severity': 'warning',
|
||||
'text': 'Wynik SEO jest sredni. Sa elementy do poprawy w optymalizacji pod wyszukiwarki.'
|
||||
})
|
||||
|
||||
# Performance
|
||||
if analysis.pagespeed_performance_score is not None:
|
||||
if analysis.pagespeed_performance_score < 50:
|
||||
recommendations.append({
|
||||
'severity': 'critical',
|
||||
'text': 'Strona laduje sie bardzo wolno. Uzytkownicy moga ja opuszczac zanim sie zaladuje.'
|
||||
})
|
||||
elif analysis.pagespeed_performance_score < 90:
|
||||
recommendations.append({
|
||||
'severity': 'warning',
|
||||
'text': 'Szybkosc strony jest srednia. Mozna poprawic czas ladowania.'
|
||||
})
|
||||
|
||||
# Meta tags
|
||||
if not analysis.seo_title and not analysis.meta_title:
|
||||
recommendations.append({
|
||||
'severity': 'critical',
|
||||
'text': 'Brak tytulu strony (meta title). Wyszukiwarki nie wiedza, czego dotyczy strona.'
|
||||
})
|
||||
if not analysis.seo_description and not analysis.meta_description:
|
||||
recommendations.append({
|
||||
'severity': 'critical',
|
||||
'text': 'Brak opisu strony (meta description). To tekst wyswietlany w wynikach Google.'
|
||||
})
|
||||
|
||||
# SSL
|
||||
if analysis.has_ssl is False:
|
||||
recommendations.append({
|
||||
'severity': 'critical',
|
||||
'text': 'Brak certyfikatu SSL (HTTPS). Przegladarki oznaczaja strone jako niebezpieczna.'
|
||||
})
|
||||
|
||||
# Sitemap & robots
|
||||
if analysis.has_sitemap is False:
|
||||
recommendations.append({
|
||||
'severity': 'warning',
|
||||
'text': 'Brak pliku sitemap.xml. Google moze nie odkryc wszystkich podstron.'
|
||||
})
|
||||
if analysis.has_robots_txt is False:
|
||||
recommendations.append({
|
||||
'severity': 'warning',
|
||||
'text': 'Brak pliku robots.txt. Nie ma instrukcji dla robotow wyszukiwarek.'
|
||||
})
|
||||
|
||||
# Structured data
|
||||
if analysis.has_structured_data is False:
|
||||
recommendations.append({
|
||||
'severity': 'warning',
|
||||
'text': 'Brak danych strukturalnych (Schema.org). Strona nie bedzie miala rozszerzonych wynikow w Google.'
|
||||
})
|
||||
|
||||
# Images
|
||||
if analysis.images_without_alt and analysis.images_without_alt > 0:
|
||||
recommendations.append({
|
||||
'severity': 'warning',
|
||||
'text': f'{analysis.images_without_alt} obrazkow bez opisu (alt). Problem z dostepnoscia i SEO.'
|
||||
})
|
||||
|
||||
# H1
|
||||
if analysis.h1_count is not None and analysis.h1_count != 1:
|
||||
if analysis.h1_count == 0:
|
||||
recommendations.append({
|
||||
'severity': 'warning',
|
||||
'text': 'Brak naglowka H1. Strona powinna miec dokladnie jeden glowny naglowek.'
|
||||
})
|
||||
elif analysis.h1_count > 1:
|
||||
recommendations.append({
|
||||
'severity': 'info',
|
||||
'text': f'Strona ma {analysis.h1_count} naglowkow H1 (powinien byc 1).'
|
||||
})
|
||||
|
||||
# Open Graph
|
||||
if analysis.has_og_tags is False:
|
||||
recommendations.append({
|
||||
'severity': 'info',
|
||||
'text': 'Brak tagow Open Graph. Linki udostepnione na Facebooku nie beda mialy podgladu.'
|
||||
})
|
||||
|
||||
# Canonical
|
||||
if analysis.has_canonical is False:
|
||||
recommendations.append({
|
||||
'severity': 'info',
|
||||
'text': 'Brak tagu canonical. Moze prowadzic do duplikacji tresci w wyszukiwarkach.'
|
||||
})
|
||||
|
||||
# Accessibility
|
||||
if analysis.pagespeed_accessibility_score is not None and analysis.pagespeed_accessibility_score < 50:
|
||||
recommendations.append({
|
||||
'severity': 'warning',
|
||||
'text': 'Niska dostepnosc strony. Osoby z niepelnosprawnosciami moga miec problem z korzystaniem.'
|
||||
})
|
||||
|
||||
# Mobile
|
||||
if analysis.is_responsive is False and analysis.is_mobile_friendly is False:
|
||||
recommendations.append({
|
||||
'severity': 'critical',
|
||||
'text': 'Strona nie jest dostosowana do urzadzen mobilnych. Google karze takie strony w wynikach.'
|
||||
})
|
||||
|
||||
# Security headers
|
||||
if analysis.security_headers_count is not None and analysis.security_headers_count < 2:
|
||||
recommendations.append({
|
||||
'severity': 'info',
|
||||
'text': 'Brak waznych naglowkow bezpieczenstwa (HSTS, CSP). Strona jest slabiej zabezpieczona.'
|
||||
})
|
||||
|
||||
# All good?
|
||||
if not recommendations:
|
||||
recommendations.append({
|
||||
'severity': 'success',
|
||||
'text': 'Strona wyglada dobrze! Nie znaleziono powaznych problemow.'
|
||||
})
|
||||
|
||||
# Add issues from seo_issues JSONB if available
|
||||
if analysis.seo_issues:
|
||||
for issue in analysis.seo_issues:
|
||||
if isinstance(issue, dict):
|
||||
recommendations.append({
|
||||
'severity': issue.get('severity', 'info'),
|
||||
'text': issue.get('message', issue.get('text', str(issue)))
|
||||
})
|
||||
|
||||
return render_template('admin/seo_detail.html',
|
||||
company=company,
|
||||
analysis=analysis,
|
||||
recommendations=recommendations
|
||||
)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
# ============================================================
|
||||
# GBP AUDIT ADMIN DASHBOARD
|
||||
# ============================================================
|
||||
|
||||
456
templates/admin/seo_detail.html
Normal file
456
templates/admin/seo_detail.html
Normal file
@ -0,0 +1,456 @@
|
||||
{% 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" onclick="runAudit()">Uruchom audyt ponownie</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% 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">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 %}
|
||||
</div>
|
||||
|
||||
<!-- 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>
|
||||
|
||||
{% 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_business_status %}
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">Status</span>
|
||||
<span class="detail-value">{{ analysis.google_business_status }}</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 %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
var csrfToken = '{{ csrf_token() }}';
|
||||
|
||||
function runAudit() {
|
||||
if (!confirm('Uruchomic audyt SEO ponownie dla {{ company.name }}?')) return;
|
||||
fetch('/api/seo/audit', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'X-CSRFToken': csrfToken },
|
||||
body: JSON.stringify({ slug: '{{ company.slug }}' })
|
||||
}).then(function(r) { return r.json(); })
|
||||
.then(function(data) {
|
||||
if (data.success) {
|
||||
alert('Audyt zakoczony! Strona zostanie odswiezona.');
|
||||
location.reload();
|
||||
} else {
|
||||
alert('Blad: ' + (data.error || 'Nieznany blad'));
|
||||
}
|
||||
}).catch(function(e) { alert('Blad polaczenia: ' + e.message); });
|
||||
}
|
||||
{% endblock %}
|
||||
@ -299,6 +299,23 @@
|
||||
border-color: var(--success);
|
||||
}
|
||||
|
||||
.btn-detail {
|
||||
padding: 4px 10px;
|
||||
border-radius: var(--radius);
|
||||
border: 1px solid var(--primary);
|
||||
background: var(--primary);
|
||||
color: white;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
transition: var(--transition);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.btn-detail:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/* Category badge */
|
||||
.category-badge {
|
||||
display: inline-block;
|
||||
@ -342,6 +359,23 @@
|
||||
.legend-dot.medium { background: #fef3c7; border: 1px solid #92400e; }
|
||||
.legend-dot.poor { background: #fee2e2; border: 1px solid #991b1b; }
|
||||
|
||||
.column-descriptions {
|
||||
display: flex;
|
||||
gap: var(--spacing-lg);
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: var(--spacing-md);
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--text-secondary);
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
background: #f0f9ff;
|
||||
border-radius: var(--radius);
|
||||
border: 1px solid #bae6fd;
|
||||
}
|
||||
|
||||
.column-descriptions strong {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* Problem tags */
|
||||
.problems-cell {
|
||||
max-width: 160px;
|
||||
@ -678,7 +712,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Legend -->
|
||||
<!-- Legend & Column Descriptions -->
|
||||
<div class="legend">
|
||||
<div class="legend-item">
|
||||
<div class="legend-dot good"></div>
|
||||
@ -693,6 +727,13 @@
|
||||
<span>0-49 (slaby)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column-descriptions">
|
||||
<span><strong>SEO</strong> — widocznosc w wyszukiwarkach</span>
|
||||
<span><strong>Performance</strong> — szybkosc ladowania</span>
|
||||
<span><strong>Accessibility</strong> — dostepnosc dla osob z niepelnosprawnosciami</span>
|
||||
<span><strong>Best Practices</strong> — bezpieczenstwo i standardy</span>
|
||||
<span><strong>Szczegoly</strong> — pelne informacje i zalecenia co poprawic</span>
|
||||
</div>
|
||||
|
||||
<!-- Table -->
|
||||
{% if companies %}
|
||||
@ -825,11 +866,8 @@
|
||||
</td>
|
||||
<td>
|
||||
<div class="action-buttons">
|
||||
<a href="{{ url_for('company_detail', company_id=company.id) }}" class="btn-icon" title="Zobacz profil">
|
||||
<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 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
|
||||
</svg>
|
||||
<a href="{{ url_for('admin.admin_seo_detail', company_id=company.id) }}" class="btn-detail" title="Pelne szczegoly audytu">
|
||||
Szczegoly
|
||||
</a>
|
||||
{% if current_user.can_access_admin_panel() %}
|
||||
<button class="btn-icon audit" onclick="runSingleAudit('{{ company.slug }}')" title="Uruchom audyt SEO">
|
||||
|
||||
Loading…
Reference in New Issue
Block a user