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()
|
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'])
|
@app.route('/api/check-email', methods=['POST'])
|
||||||
def api_check_email():
|
def api_check_email():
|
||||||
"""API: Check if email is available"""
|
"""API: Check if email is available"""
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user