feat: add performance metrics, category benchmarks, and WHOIS domain info
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
- Speed Index, Time to Interactive, Total Blocking Time from PageSpeed - Benchmark table: company vs category avg vs all-members avg with arrows - WHOIS via RDAP: domain registration, expiry, last change, registrar Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
04bbf5db18
commit
3690b4d77e
@ -327,11 +327,109 @@ def admin_seo_detail(company_id):
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Category benchmarks — average scores in same category
|
||||
benchmarks = {}
|
||||
if company.category_id and analysis:
|
||||
try:
|
||||
from sqlalchemy import func as sqlfunc
|
||||
cat_stats = db.query(
|
||||
sqlfunc.avg(CompanyWebsiteAnalysis.pagespeed_seo_score),
|
||||
sqlfunc.avg(CompanyWebsiteAnalysis.pagespeed_performance_score),
|
||||
sqlfunc.avg(CompanyWebsiteAnalysis.pagespeed_accessibility_score),
|
||||
sqlfunc.avg(CompanyWebsiteAnalysis.pagespeed_best_practices_score),
|
||||
sqlfunc.count(CompanyWebsiteAnalysis.id),
|
||||
).join(Company, Company.id == CompanyWebsiteAnalysis.company_id
|
||||
).filter(
|
||||
Company.category_id == company.category_id,
|
||||
Company.status == 'active',
|
||||
CompanyWebsiteAnalysis.pagespeed_seo_score.isnot(None),
|
||||
).first()
|
||||
|
||||
if cat_stats and cat_stats[4] > 1:
|
||||
from database import Category
|
||||
cat_name = db.query(Category.name).filter_by(id=company.category_id).scalar()
|
||||
benchmarks = {
|
||||
'category_name': cat_name or '',
|
||||
'count': cat_stats[4],
|
||||
'avg_seo': round(float(cat_stats[0] or 0)),
|
||||
'avg_performance': round(float(cat_stats[1] or 0)),
|
||||
'avg_accessibility': round(float(cat_stats[2] or 0)),
|
||||
'avg_best_practices': round(float(cat_stats[3] or 0)),
|
||||
}
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# All-members benchmark
|
||||
all_benchmarks = {}
|
||||
if analysis:
|
||||
try:
|
||||
from sqlalchemy import func as sqlfunc
|
||||
all_stats = db.query(
|
||||
sqlfunc.avg(CompanyWebsiteAnalysis.pagespeed_seo_score),
|
||||
sqlfunc.avg(CompanyWebsiteAnalysis.pagespeed_performance_score),
|
||||
sqlfunc.avg(CompanyWebsiteAnalysis.pagespeed_accessibility_score),
|
||||
sqlfunc.avg(CompanyWebsiteAnalysis.pagespeed_best_practices_score),
|
||||
sqlfunc.count(CompanyWebsiteAnalysis.id),
|
||||
).join(Company, Company.id == CompanyWebsiteAnalysis.company_id
|
||||
).filter(
|
||||
Company.status == 'active',
|
||||
CompanyWebsiteAnalysis.pagespeed_seo_score.isnot(None),
|
||||
).first()
|
||||
|
||||
if all_stats and all_stats[4] > 1:
|
||||
all_benchmarks = {
|
||||
'count': all_stats[4],
|
||||
'avg_seo': round(float(all_stats[0] or 0)),
|
||||
'avg_performance': round(float(all_stats[1] or 0)),
|
||||
'avg_accessibility': round(float(all_stats[2] or 0)),
|
||||
'avg_best_practices': round(float(all_stats[3] or 0)),
|
||||
}
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# WHOIS domain expiry lookup
|
||||
domain_info = {}
|
||||
if analysis and company.website:
|
||||
try:
|
||||
import re
|
||||
import requests as req
|
||||
domain_match = re.search(r'https?://(?:www\.)?([^/]+)', company.website)
|
||||
if domain_match:
|
||||
domain = domain_match.group(1)
|
||||
resp = req.get(
|
||||
f'https://rdap.org/domain/{domain}',
|
||||
timeout=5,
|
||||
headers={'Accept': 'application/rdap+json'}
|
||||
)
|
||||
if resp.status_code == 200:
|
||||
rdap = resp.json()
|
||||
for event in rdap.get('events', []):
|
||||
if event.get('eventAction') == 'expiration':
|
||||
domain_info['expires'] = event.get('eventDate', '')[:10]
|
||||
elif event.get('eventAction') == 'registration':
|
||||
domain_info['registered'] = event.get('eventDate', '')[:10]
|
||||
elif event.get('eventAction') == 'last changed':
|
||||
domain_info['updated'] = event.get('eventDate', '')[:10]
|
||||
# Registrar from entities
|
||||
for entity in rdap.get('entities', []):
|
||||
if 'registrar' in entity.get('roles', []):
|
||||
vcard = entity.get('vcardArray', [None, []])[1]
|
||||
for item in (vcard or []):
|
||||
if item[0] == 'fn':
|
||||
domain_info['registrar'] = item[3]
|
||||
break
|
||||
domain_info['domain'] = domain
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return render_template('admin/seo_detail.html',
|
||||
company=company,
|
||||
analysis=analysis,
|
||||
recommendations=recommendations,
|
||||
ip_info=ip_info
|
||||
ip_info=ip_info,
|
||||
benchmarks=benchmarks,
|
||||
all_benchmarks=all_benchmarks,
|
||||
domain_info=domain_info
|
||||
)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
@ -290,6 +290,51 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Porownanie z innymi firmami -->
|
||||
{% if all_benchmarks %}
|
||||
<div class="section">
|
||||
<h2>Porownanie z innymi firmami Norda Biznes</h2>
|
||||
<p style="font-size: var(--font-size-xs); color: var(--text-secondary); margin-bottom: var(--spacing-md);">
|
||||
Srednie wyniki {{ all_benchmarks.count }} zbadanych firm w stowarzyszeniu{% if benchmarks %} oraz {{ benchmarks.count }} firm w kategorii "{{ benchmarks.category_name }}"{% endif %}.
|
||||
</p>
|
||||
<table style="width: 100%; border-collapse: collapse; font-size: var(--font-size-sm);">
|
||||
<thead>
|
||||
<tr style="border-bottom: 2px solid var(--border);">
|
||||
<th style="text-align: left; padding: 8px;">Metryka</th>
|
||||
<th style="text-align: center; padding: 8px; font-weight: 700; color: var(--primary);">{{ company.name[:25] }}</th>
|
||||
{% if benchmarks %}<th style="text-align: center; padding: 8px;">{{ benchmarks.category_name[:20] }} ({{ benchmarks.count }})</th>{% endif %}
|
||||
<th style="text-align: center; padding: 8px;">Wszystkie firmy ({{ all_benchmarks.count }})</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% set metrics = [
|
||||
('SEO', analysis.pagespeed_seo_score, benchmarks.get('avg_seo'), all_benchmarks.avg_seo),
|
||||
('Performance', analysis.pagespeed_performance_score, benchmarks.get('avg_performance'), all_benchmarks.avg_performance),
|
||||
('Dostepnosc', analysis.pagespeed_accessibility_score, benchmarks.get('avg_accessibility'), all_benchmarks.avg_accessibility),
|
||||
('Best Practices', analysis.pagespeed_best_practices_score, benchmarks.get('avg_best_practices'), all_benchmarks.avg_best_practices),
|
||||
] %}
|
||||
{% for name, val, cat_avg, all_avg in metrics %}
|
||||
<tr style="border-bottom: 1px solid var(--border);">
|
||||
<td style="padding: 8px;">{{ name }}</td>
|
||||
<td style="text-align: center; padding: 8px; font-weight: 700;">
|
||||
{% if val is not none %}
|
||||
<span class="{% if val >= 90 %}yes{% elif val >= 50 %}{% else %}no{% endif %}">{{ val }}</span>
|
||||
{% if val > all_avg %} <span style="color: var(--success); font-size: 11px;">▲</span>
|
||||
{% elif val < all_avg %} <span style="color: var(--danger); font-size: 11px;">▼</span>
|
||||
{% else %} <span style="color: var(--text-secondary); font-size: 11px;">─</span>{% endif %}
|
||||
{% else %}-{% endif %}
|
||||
</td>
|
||||
{% if benchmarks %}
|
||||
<td style="text-align: center; padding: 8px; color: var(--text-secondary);">{{ cat_avg if cat_avg else '-' }}</td>
|
||||
{% endif %}
|
||||
<td style="text-align: center; padding: 8px; color: var(--text-secondary);">{{ all_avg }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Zalecenia -->
|
||||
<div class="section">
|
||||
<h2>Zalecenia — co poprawic</h2>
|
||||
@ -527,6 +572,34 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if analysis.pagespeed_audits and analysis.pagespeed_audits is mapping and analysis.pagespeed_audits.get('performance') %}
|
||||
{% set perf_data = analysis.pagespeed_audits.performance %}
|
||||
<h3 style="margin-top: var(--spacing-lg); font-size: var(--font-size-base); color: var(--text-secondary);">Szczegolowe metryki wydajnosci (Lighthouse)</h3>
|
||||
<div class="detail-grid" style="margin-top: var(--spacing-sm);">
|
||||
{% if perf_data.get('speed-index') %}
|
||||
{% set si = perf_data['speed-index'] %}
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">Speed Index — jak szybko tresc staje sie widoczna</span>
|
||||
<span class="detail-value {% if si.score is not none %}{% if si.score >= 0.9 %}yes{% elif si.score >= 0.5 %}{% else %}no{% endif %}{% endif %}">{{ si.get('displayValue', '-') }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if perf_data.get('interactive') %}
|
||||
{% set tti = perf_data['interactive'] %}
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">Time to Interactive — kiedy strona reaguje na klikniecia</span>
|
||||
<span class="detail-value {% if tti.score is not none %}{% if tti.score >= 0.9 %}yes{% elif tti.score >= 0.5 %}{% else %}no{% endif %}{% endif %}">{{ tti.get('displayValue', '-') }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if perf_data.get('total-blocking-time') %}
|
||||
{% set tbt = perf_data['total-blocking-time'] %}
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">Total Blocking Time — jak dlugo JS blokuje strone</span>
|
||||
<span class="detail-value {% if tbt.score is not none %}{% if tbt.score >= 0.9 %}yes{% elif tbt.score >= 0.5 %}{% else %}no{% endif %}{% endif %}">{{ tbt.get('displayValue', '-') }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% 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);">
|
||||
@ -1002,10 +1075,40 @@
|
||||
{% 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 %}
|
||||
{% if analysis.ssl_expires_at or analysis.final_url or analysis.hosting_provider or analysis.server_software or analysis.hosting_ip or domain_info %}
|
||||
<div class="section">
|
||||
<h2>Infrastruktura i szczegoly techniczne</h2>
|
||||
<div class="detail-grid">
|
||||
{% if domain_info.get('domain') %}
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">Domena</span>
|
||||
<span class="detail-value" style="font-family: monospace;">{{ domain_info.domain }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if domain_info.get('registered') %}
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">Domena zarejestrowana</span>
|
||||
<span class="detail-value">{{ domain_info.registered }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if domain_info.get('expires') %}
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">Domena wygasa</span>
|
||||
<span class="detail-value {% if domain_info.expires < (now().strftime('%Y-%m-%d') if now is defined else '') %}no{% endif %}" style="font-weight: 700;">{{ domain_info.expires }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if domain_info.get('updated') %}
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">Ostatnia zmiana domeny</span>
|
||||
<span class="detail-value">{{ domain_info.updated }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if domain_info.get('registrar') %}
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">Rejestrator domeny (WHOIS)</span>
|
||||
<span class="detail-value">{{ domain_info.registrar }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if analysis.final_url and analysis.final_url != analysis.website_url %}
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">URL po przekierowaniu</span>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user