{% extends "admin/_base.html" %}
{% block content %}
{# v0.19 — page header now uses the unified `.rio-page-header`
# primitive (cards.css) shared by every list / detail / settings
# page. Pre-0.19 the dashboard had a bespoke `.rio-dashboard-
# greeting` element with its own padding ladder; cross-page
# inconsistency was a real source of "feels random" feedback. #}
<header class="rio-page-header">
<nav class="rio-breadcrumbs">{{ app_name }}</nav>
<div class="rio-page-actions">
<h1>{{ index_title }}</h1>
<div class="rio-page-header__chips">
<span class="rio-env-chip rio-env-chip--{{ environment_kind }}" title="Runtime environment">{{ environment_label }}</span>
<span class="rio-env-chip rio-env-chip--ver" title="Framework version">v{{ framework_version }}</span>
</div>
</div>
</header>
{# ---- KPI strip ---------------------------------------------------
# v0.19 — same four data points, but the tile treatment is now
# the unified `.rio-stat` (single white surface + 3-px top
# accent rail). The festive cycling background fills are gone;
# color is reserved for semantic meaning, not per-tile
# decoration. See pages/dashboard.css. #}
<section class="rio-dashboard-stats" aria-label="At a glance">
<article class="rio-stat rio-stat--info">
<p class="rio-stat__label">Registered models</p>
<p class="rio-stat__value">{{ total_models }}</p>
<p class="rio-stat__meta">Across {{ apps|length }} app{% if apps|length != 1 %}s{% endif %}</p>
</article>
<article class="rio-stat rio-stat--success">
<p class="rio-stat__label">Total rows</p>
<p class="rio-stat__value">{{ total_rows }}</p>
<p class="rio-stat__meta">Approx. — pg_class.reltuples</p>
</article>
<article class="rio-stat rio-stat--warning">
<p class="rio-stat__label">Recent activity</p>
<p class="rio-stat__value">{{ recent_actions_count }}</p>
<p class="rio-stat__meta">Audit events in window</p>
</article>
<article class="rio-stat rio-stat--accent">
<p class="rio-stat__label">Environment</p>
<p class="rio-stat__value rio-stat__value--text">{{ environment_label }}</p>
<p class="rio-stat__meta">RustIO v{{ framework_version }}</p>
</article>
</section>
{# ---- Browse data section ----------------------------------------
# v0.19 — wrapped in the shared `.rio-section` primitive so the
# eyebrow label, title, and right-aligned meta all share one
# rhythm with every other section on the page. #}
<section class="rio-section" aria-label="Browse data">
<header class="rio-section__header">
<div class="rio-section__heading">
<span class="rio-section__label">Browse data</span>
<h2 class="rio-section__title">Models</h2>
</div>
<span class="rio-section__meta">{{ total_models }} model{% if total_models != 1 %}s{% endif %} · {{ total_rows }} row{% if total_rows != 1 %}s{% endif %} total</span>
</header>
{% if apps %}
<div class="rio-model-grid">
{% for app in apps %}
{% for model in app.models %}
<article class="rio-model-tile">
{% if app.label != model.display_name %}
<span class="rio-model-tile__app" title="App group">{{ app.label }}</span>
{% endif %}
<a class="rio-model-tile__title-link" href="/admin/{{ model.admin_name }}">
<h3 class="rio-model-tile__title">{{ model.display_name }}</h3>
</a>
<p class="rio-model-tile__stat">
<span class="rio-model-tile__stat-num">{{ model.row_estimate }}</span>
<span class="rio-model-tile__stat-suffix">{% if model.row_estimate == 1 %}row{% else %}rows{% endif %}</span>
</p>
{% if model.new_this_week is not none %}
<p class="rio-model-tile__stat-secondary" title="Exact count — rows with created_at within the last 7 days">
<span class="rio-model-tile__stat-num">{{ model.new_this_week }}</span>
new this week
</p>
{% endif %}
{% if model.weekly_series %}
<svg class="rio-model-tile__sparkline" viewBox="0 0 140 20"
preserveAspectRatio="none" role="img"
aria-label="7-day creation history for {{ model.display_name }}">
{% for v in model.weekly_series %}
{% set bar_w = 16 %}
{% set gap = 4 %}
{% set x = loop.index0 * (bar_w + gap) %}
{% set max_h = 16 %}
{% set h = (v * max_h) // (model.weekly_series_max or 1) %}
<rect class="rio-model-tile__sparkline-bar"
x="{{ x }}" y="{{ max_h - h }}" width="{{ bar_w }}" height="{{ h }}" rx="1"></rect>
{% endfor %}
</svg>
{% endif %}
<p class="rio-model-tile__meta" title="Approximate — from pg_class.reltuples, refreshed by ANALYZE">
{{ model.field_count }} field{% if model.field_count != 1 %}s{% endif %} · approx. count
</p>
<div class="rio-model-tile__actions">
<a class="rio-button rio-button--ghost rio-button--sm" href="/admin/{{ model.admin_name }}">
{{ icon("table", class="rio-icon") }} Browse
</a>
{% if not read_only %}
<a class="rio-button rio-button--ghost rio-button--sm" href="/admin/{{ model.admin_name }}/new">
{{ icon("plus", class="rio-icon") }} Add
</a>
{% endif %}
</div>
</article>
{% endfor %}
{% endfor %}
</div>
{% else %}
{# v0.19 — proper empty state (icon + headline + meta + CTA-style
# explanation) instead of the pre-0.19 one-paragraph .rio-empty. #}
<div class="rio-card rio-card--quiet">
<div class="rio-empty-state">
<div class="rio-empty-state__icon">{{ icon("database", class="rio-icon") }}</div>
<h3 class="rio-empty-state__title">No models registered yet</h3>
<p class="rio-empty-state__lead">
Run <code>rustio-admin startapp <name></code> to scaffold a model + migration,
then chain <code>.model::<YourModel>()</code> onto
<code>Admin::new()</code> in <code>src/main.rs</code>. It will appear here on
the next boot.
</p>
</div>
</div>
{% endif %}
</section>
{# ---- Activity + Tools split ------------------------------------
# v0.19 — both halves use `.rio-section` headers + `.rio-card`
# bodies so they share visual weight with the Browse data section
# above. Wide ≥1024 px: split 2/3 + 1/3. Narrow: stacks. #}
<div class="rio-dashboard-split">
<section class="rio-section" aria-label="Recent activity">
<header class="rio-section__header">
<div class="rio-section__heading">
<span class="rio-section__label">Activity</span>
<h2 class="rio-section__title">Recent</h2>
</div>
{% if recent_actions and identity.is_admin %}
<a class="rio-section__link" href="/admin/history">View full history →</a>
{% endif %}
</header>
{% if activity_sparkline %}
<figure class="rio-sparkline" aria-label="Admin actions over the last 7 days">
<figcaption class="rio-sparkline__caption">
{{ activity_sparkline_total }} action{% if activity_sparkline_total != 1 %}s{% endif %} in the last 7 days
</figcaption>
{# v0.18.6 area chart unchanged in v0.19 — math + markup
# already match the analytics-tool convention. #}
{% set max_h = 48 %}
{% set spark_max = activity_sparkline_max or 1 %}
{% set ns = namespace(line="") %}
{% for p in activity_sparkline %}
{% set x = loop.index0 * 40 + 16 %}
{% set y = max_h - ((p.count * max_h) // spark_max) %}
{% set ns.line = ns.line ~ x ~ "," ~ y ~ " " %}
{% endfor %}
{% set first_x = 16 %}
{% set last_x = (activity_sparkline|length - 1) * 40 + 16 %}
<svg class="rio-sparkline__svg" viewBox="0 0 280 64" preserveAspectRatio="none" role="img" aria-hidden="true">
<defs>
<linearGradient id="rio-sparkline-grad" x1="0" y1="0" x2="0" y2="1">
<stop class="rio-sparkline__grad-stop-top" offset="0%"/>
<stop class="rio-sparkline__grad-stop-bot" offset="100%"/>
</linearGradient>
</defs>
<line class="rio-sparkline__baseline" x1="{{ first_x }}" x2="{{ last_x }}" y1="{{ max_h }}" y2="{{ max_h }}"/>
<polygon class="rio-sparkline__area" points="{{ ns.line|trim }} {{ last_x }},{{ max_h }} {{ first_x }},{{ max_h }}"/>
<polyline class="rio-sparkline__line" points="{{ ns.line|trim }}"/>
{% for p in activity_sparkline %}
{% set x = loop.index0 * 40 + 16 %}
{% set y = max_h - ((p.count * max_h) // spark_max) %}
<circle class="rio-sparkline__dot" cx="{{ x }}" cy="{{ y }}" r="2.5"/>
<text class="rio-sparkline__tick" x="{{ x }}" y="62" text-anchor="middle">{{ p.label }}</text>
{% endfor %}
</svg>
<ul class="rio-sparkline__data" hidden>
{% for p in activity_sparkline %}
<li>{{ p.date_iso }} ({{ p.label }}): {{ p.count }}</li>
{% endfor %}
</ul>
</figure>
{% endif %}
{% if recent_actions %}
<ul class="rio-activity-feed">
{% for a in recent_actions %}
<li class="rio-activity-feed__item">
<span class="rio-pill rio-pill--{{ a.pill_class }}">{{ a.label }}</span>
<span class="rio-activity-feed__target">
<a href="/admin/{{ a.model_name }}">{{ a.model_name }}</a>
<span class="rio-activity-feed__sep" aria-hidden="true">·</span>
#<a href="/admin/{{ a.model_name }}/{{ a.object_id }}/edit">{{ a.object_id }}</a>
</span>
{% if a.summary %}<span class="rio-activity-feed__summary">{{ a.summary }}</span>{% endif %}
<span class="rio-activity-feed__meta">{{ a.user_email }} · {{ a.when_relative }}</span>
</li>
{% endfor %}
</ul>
{% else %}
<div class="rio-card rio-card--quiet">
<div class="rio-empty-state">
<div class="rio-empty-state__icon">{{ icon("clock", class="rio-icon") }}</div>
<h3 class="rio-empty-state__title">No actions yet</h3>
<p class="rio-empty-state__lead">
Once operators start creating, editing, or deleting rows,
the audit feed lights up here.
</p>
</div>
</div>
{% endif %}
</section>
<aside class="rio-section" aria-label="Framework tools">
<header class="rio-section__header">
<div class="rio-section__heading">
<span class="rio-section__label">Quick links</span>
<h2 class="rio-section__title">Framework tools</h2>
</div>
</header>
<ul class="rio-tool-list">
{% if identity.is_admin %}
<li><a class="rio-tool-link" href="/admin/history">
{{ icon("clock", class="rio-tool-link__icon") }}
<span class="rio-tool-link__text">
<strong>Audit log</strong>
<span>Full history of every authority mutation.</span>
</span>
</a></li>
<li><a class="rio-tool-link" href="/admin/users">
{{ icon("users", class="rio-tool-link__icon") }}
<span class="rio-tool-link__text">
<strong>Users</strong>
<span>Create, role-shift, lock, or recover operator accounts.</span>
</span>
</a></li>
<li><a class="rio-tool-link" href="/admin/groups">
{{ icon("key", class="rio-tool-link__icon") }}
<span class="rio-tool-link__text">
<strong>Groups & permissions</strong>
<span>Bundle perms into reusable groups; assign by role.</span>
</span>
</a></li>
<li><a class="rio-tool-link" href="/admin/feature_flags">
{{ icon("flag", class="rio-tool-link__icon") }}
<span class="rio-tool-link__text">
<strong>Feature flags</strong>
<span>Project-side boolean flags read by your code.</span>
</span>
</a></li>
{% endif %}
{% if identity.is_developer %}
<li><a class="rio-tool-link" href="/admin/db">
{{ icon("database", class="rio-tool-link__icon") }}
<span class="rio-tool-link__text">
<strong>Database explorer</strong>
<span>Read-only Postgres schema view — tables, columns, indexes.</span>
</span>
</a></li>
{% endif %}
<li><a class="rio-tool-link" href="/admin/account/sessions">
{{ icon("log-out", class="rio-tool-link__icon") }}
<span class="rio-tool-link__text">
<strong>Your sessions</strong>
<span>Active sign-ins on this account; revoke from anywhere.</span>
</span>
</a></li>
</ul>
</aside>
</div>
{% endblock %}