auto-claude: 6.2 - Add admin route to app.py that renders SEO dashboard
Added /admin/seo route with: - Admin-only access with @login_required and is_admin check - Fetches all active companies with SEO analysis data using outerjoin - Calculates statistics: good_count (90-100), medium_count (50-89), poor_count (0-49), not_audited_count, avg_score - Gets unique categories for filter dropdown - Passes companies, stats, categories, and now to template - Uses CompanyRow class for Jinja2 attribute access 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
282e807f39
commit
44f009f027
109
app.py
109
app.py
@ -2639,6 +2639,115 @@ def api_seo_audit_trigger():
|
||||
db.close()
|
||||
|
||||
|
||||
# ============================================================
|
||||
# SEO ADMIN DASHBOARD
|
||||
# ============================================================
|
||||
|
||||
@app.route('/admin/seo')
|
||||
@login_required
|
||||
def admin_seo():
|
||||
"""
|
||||
Admin dashboard for SEO metrics overview.
|
||||
|
||||
Displays:
|
||||
- Summary stats (score distribution, average score)
|
||||
- Sortable table of all companies with SEO scores
|
||||
- Color-coded score badges (green 90-100, yellow 50-89, red 0-49)
|
||||
- Filtering by category, score range, and search text
|
||||
- Last audit date with staleness indicator
|
||||
- Actions: view profile, trigger single company audit
|
||||
"""
|
||||
if not current_user.is_admin:
|
||||
flash('Brak uprawnień do tej strony.', 'error')
|
||||
return redirect(url_for('dashboard'))
|
||||
|
||||
db = SessionLocal()
|
||||
try:
|
||||
from sqlalchemy import func
|
||||
|
||||
# Get all active companies with their latest SEO analysis data
|
||||
# Using outerjoin to include companies without SEO data
|
||||
companies_query = db.query(
|
||||
Company.id,
|
||||
Company.name,
|
||||
Company.slug,
|
||||
Company.website,
|
||||
Company.category,
|
||||
CompanyWebsiteAnalysis.pagespeed_seo_score,
|
||||
CompanyWebsiteAnalysis.pagespeed_performance_score,
|
||||
CompanyWebsiteAnalysis.pagespeed_accessibility_score,
|
||||
CompanyWebsiteAnalysis.pagespeed_best_practices_score,
|
||||
CompanyWebsiteAnalysis.seo_audited_at
|
||||
).outerjoin(
|
||||
CompanyWebsiteAnalysis,
|
||||
Company.id == CompanyWebsiteAnalysis.company_id
|
||||
).filter(
|
||||
Company.status == 'active'
|
||||
).order_by(
|
||||
Company.name
|
||||
).all()
|
||||
|
||||
# Build companies list with named attributes for template
|
||||
companies = []
|
||||
for row in companies_query:
|
||||
companies.append({
|
||||
'id': row.id,
|
||||
'name': row.name,
|
||||
'slug': row.slug,
|
||||
'website': row.website,
|
||||
'category': row.category,
|
||||
'seo_score': row.pagespeed_seo_score,
|
||||
'performance_score': row.pagespeed_performance_score,
|
||||
'accessibility_score': row.pagespeed_accessibility_score,
|
||||
'best_practices_score': row.pagespeed_best_practices_score,
|
||||
'seo_audited_at': row.seo_audited_at
|
||||
})
|
||||
|
||||
# Calculate statistics
|
||||
audited_companies = [c for c in companies if c['seo_score'] is not None]
|
||||
not_audited = [c for c in companies if c['seo_score'] is None]
|
||||
|
||||
good_count = len([c for c in audited_companies if c['seo_score'] >= 90])
|
||||
medium_count = len([c for c in audited_companies if 50 <= c['seo_score'] < 90])
|
||||
poor_count = len([c for c in audited_companies if c['seo_score'] < 50])
|
||||
not_audited_count = len(not_audited)
|
||||
|
||||
# Calculate average score (only for audited companies)
|
||||
if audited_companies:
|
||||
avg_score = round(sum(c['seo_score'] for c in audited_companies) / len(audited_companies))
|
||||
else:
|
||||
avg_score = None
|
||||
|
||||
stats = {
|
||||
'good_count': good_count,
|
||||
'medium_count': medium_count,
|
||||
'poor_count': poor_count,
|
||||
'not_audited_count': not_audited_count,
|
||||
'avg_score': avg_score
|
||||
}
|
||||
|
||||
# Get unique categories for filter dropdown
|
||||
categories = sorted(set(c['category'] for c in companies if c['category']))
|
||||
|
||||
# Convert companies list to objects with attribute access for template
|
||||
class CompanyRow:
|
||||
def __init__(self, data):
|
||||
for key, value in data.items():
|
||||
setattr(self, key, value)
|
||||
|
||||
companies_objects = [CompanyRow(c) for c in companies]
|
||||
|
||||
return render_template('admin_seo_dashboard.html',
|
||||
companies=companies_objects,
|
||||
stats=stats,
|
||||
categories=categories,
|
||||
now=datetime.now()
|
||||
)
|
||||
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@app.route('/api/check-email', methods=['POST'])
|
||||
def api_check_email():
|
||||
"""API: Check if email is available"""
|
||||
|
||||
Loading…
Reference in New Issue
Block a user