diff --git a/web/src/padelnomics/admin/templates/admin/base_admin.html b/web/src/padelnomics/admin/templates/admin/base_admin.html index 3ca1820..687335e 100644 --- a/web/src/padelnomics/admin/templates/admin/base_admin.html +++ b/web/src/padelnomics/admin/templates/admin/base_admin.html @@ -95,6 +95,12 @@ Templates +
+ + + pSEO Engine + + diff --git a/web/src/padelnomics/admin/templates/admin/pseo_dashboard.html b/web/src/padelnomics/admin/templates/admin/pseo_dashboard.html new file mode 100644 index 0000000..212883b --- /dev/null +++ b/web/src/padelnomics/admin/templates/admin/pseo_dashboard.html @@ -0,0 +1,195 @@ +{% extends "admin/base_admin.html" %} +{% set admin_page = "pseo" %} + +{% block title %}pSEO Engine - {{ config.APP_NAME }}{% endblock %} + +{% block admin_head %} + +{% endblock %} + +{% block admin_content %} +Operational dashboard for programmatic SEO
+Total Articles
+{{ total_articles }}
+{{ total_published }} published
+Templates
+{{ total_templates }}
+Stale Templates
++ {{ stale_count }} +
+data newer than articles
+Health Checks
+—
+see Health section below
+| Template | +Data rows | +Articles EN | +Articles DE | +Freshness | +Actions | +
|---|---|---|---|---|---|
|
+ {{ t.name }} + {{ t.slug }} + |
+ {{ fr.row_count if fr.row_count is not none else '—' }} | +{{ stats.by_language.get('en', {}).get('total', 0) }} | +{{ stats.by_language.get('de', {}).get('total', 0) }} | ++ {% set status = fr.status | default('no_data') %} + + {% if status == 'fresh' %}🟢 Fresh + {% elif status == 'stale' %}🟡 Stale + {% elif status == 'no_articles' %}🟣 No articles + {% else %}⚪ No data + {% endif %} + + | ++ + + | +
|
+
+
+
+ |
+ |||||
| Job | +Status | +Progress | +Started | +
|---|---|---|---|
| + #{{ job.id }} + {% if job.payload %} + — {{ (job.payload | fromjson).get('template_slug', '') }} + {% endif %} + | ++ {% if job.status == 'complete' %} + Complete + {% elif job.status == 'failed' %} + Failed + {% elif job.status == 'pending' %} + Running + {% else %} + {{ job.status }} + {% endif %} + | +
+ {% if job.progress_total and job.progress_total > 0 %}
+
+
+ {{ job.progress_current }}/{{ job.progress_total }}
+
+ {% else %}
+ —
+ {% endif %}
+ |
+ {{ job.created_at | default('') | truncate(16, True, '') }} | +
Loading health checks…
+✓ No gaps — all {{ template.name }} rows have articles.
+{% else %} +| {{ template.natural_key }} | +Missing languages | + {% for key in (gaps[0].keys() | list | reject('equalto', '_natural_key') | reject('equalto', '_missing_languages') | list)[:4] %} +{{ key }} | + {% endfor %} +|||||||
|---|---|---|---|---|---|---|---|---|---|
| {{ gap._natural_key }} | +{{ gap._missing_languages | join(', ') }} | + {% for key in (gap.keys() | list | reject('equalto', '_natural_key') | reject('equalto', '_missing_languages') | list)[:4] %} +{{ gap[key] | truncate(30) if gap[key] is string else gap[key] }} | + {% endfor %} +|||||||
| … and {{ gaps | length - 100 }} more rows | +|||||||||
✓ No issues found — all articles are healthy.
+ {% else %} + + + {% if health.hreflang_orphans %} +| Template | URL path | Present | Missing |
|---|---|---|---|
| {{ o.template_slug }} | +{{ o.url_path }} | +{{ o.present_languages | join(', ') }} | +{{ o.missing_languages | join(', ') }} | +
| … and {{ health.hreflang_orphans | length - 50 }} more | |||
| Slug | Language | URL path | Expected path |
|---|---|---|---|
| {{ m.slug }} | +{{ m.language }} | +{{ m.url_path }} | +{{ m.expected_path }} | +
| … and {{ health.missing_build_files | length - 50 }} more | |||
| Slug | Language | Broken refs |
|---|---|---|
| {{ b.slug }} | +{{ b.language }} | +{{ b.broken_scenario_refs | join(', ') }} | +
| … and {{ health.broken_scenario_refs | length - 50 }} more | ||
{{ job.error[:500] }}
+ Recent article generation runs
+No generation jobs found. Use the pSEO Engine dashboard to generate articles.
+| # | +Template | +Status | +Progress | +Started | +Completed | +Error | +
|---|---|---|---|---|---|---|
| #{{ job.id }} | ++ {% if job.payload %} + {% set payload = job.payload | fromjson %} + {{ payload.get('template_slug', '—') }} + {% else %}—{% endif %} + | +
+ {% if job.status == 'complete' %}
+ Complete
+ {% elif job.status == 'failed' %}
+ Failed
+ {% elif job.status == 'pending' %}
+ {# Poll live status for running jobs #}
+
+ Running…
+
+ {% else %}
+ {{ job.status }}
+ {% endif %}
+ |
+
+ {% if job.progress_total and job.progress_total > 0 %}
+
+
+ {{ job.progress_current }}/{{ job.progress_total }}
+
+ {% else %}—{% endif %}
+ |
+ {{ job.created_at | default('') | truncate(19, True, '') }} | +{{ job.completed_at | default('') | truncate(19, True, '') }} | +
+ {% if job.error %}
+
+
+ {% else %}—{% endif %}
+ Error+{{ job.error[:500] }}
+ |
+