rustio-admin 0.18.4

Django Admin, but for Rust. A small, focused admin framework.
Documentation
{% extends "admin/_base.html" %}
{% block content %}

{# ---- Welcome header --------------------------------------------
 # Friendly greeting + page title in one row. The greeting reads
 # the identity email's local part (capitalised) so a long
 # address doesn't dominate the header. The deck below pairs the
 # framework's product name with the project app_name. #}
<header class="rio-dashboard-greeting">
  <div class="rio-dashboard-greeting__text">
    <p class="rio-dashboard-greeting__hello">Hello, <strong>{{ greeting_name }}</strong> ๐Ÿ‘‹</p>
    <h1 class="rio-dashboard-greeting__title">{{ index_title }}</h1>
    <p class="rio-dashboard-greeting__deck">Manage <strong>{{ app_name }}</strong> from one console.</p>
  </div>
  <div class="rio-dashboard-greeting__badges">
    <span class="rio-dashboard-greeting__env rio-dashboard-greeting__env--{{ environment_kind }}">{{ environment_label }}</span>
    <span class="rio-dashboard-greeting__ver">v{{ framework_version }}</span>
  </div>
</header>

{# ---- Stats strip โ€” four pastel tiles ---------------------------
 # Each pulls a number from the dashboard context. Tints use the
 # framework's semantic `-bg` tokens so colour stays WCAG-aligned. #}
<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 stats)</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">latest events</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>

{# ---- Flat model grid ------------------------------------------
 # Every registered project model in one CSS-grid. Tiles tile
 # horizontally on wide screens (3-4 columns), collapse gracefully
 # on narrow. Each tile cycles through four accent palettes via
 # `:nth-child` so the grid has visual rhythm. The app label
 # surfaces as a subtle chip when the project has more than one
 # app group (avoids redundancy when every model IS its own app). #}
<section class="rio-dashboard-models" aria-label="Browse data">
  <header class="rio-dashboard-section-head">
    <h2 class="rio-dashboard-section-label">Browse data</h2>
    <span class="rio-dashboard-section-count">{{ 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">
        {# Only show the app-group chip when it adds information โ€”
         # i.e. when the app label differs from the model title.
         # In a project where every model is its own app (no `.`
         # in admin_name) the chip would just duplicate the title. #}
        {% 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 %}
  <p class="rio-card rio-empty">
    No models registered yet. Call <code>Admin::new().model::&lt;YourModel&gt;()</code> in your <code>main.rs</code>.
  </p>
  {% endif %}
</section>

{# ---- Two-column split: activity + framework tools --------------
 # Wide screens: activity 2/3 left, tools 1/3 right.
 # Narrow: stacks. The Framework Tools card gates each link by the
 # operator's role โ€” Administrator-only items hide for Staff. #}
<div class="rio-dashboard-split">

  <section class="rio-dashboard-activity">
    <header class="rio-dashboard-section-head">
      <h2 class="rio-dashboard-section-label">Recent activity</h2>
      {# /admin/history is Administrator-gated (routes.rs) โ€” hide the
       # affordance for Staff/Editor so we don't ship clickable links
       # that 403 on follow-through. #}
      {% if recent_actions and identity.is_admin %}
      <a class="rio-dashboard-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>
      <svg class="rio-sparkline__svg" viewBox="0 0 280 64" preserveAspectRatio="none" role="img" aria-hidden="true">
        {% for p in activity_sparkline %}
        {% set bar_w = 32 %}
        {% set gap = 8 %}
        {% set x = loop.index0 * (bar_w + gap) %}
        {% set max_h = 48 %}
        {% set h = (p.count * max_h) // (activity_sparkline_max or 1) %}
        <rect class="rio-sparkline__bar" x="{{ x }}" y="{{ max_h - h }}" width="{{ bar_w }}" height="{{ h }}" rx="2"></rect>
        <text class="rio-sparkline__tick" x="{{ x + (bar_w / 2) }}" 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 %}
    <p class="rio-card rio-empty">No actions yet โ€” once operators start creating, editing, or deleting rows, the feed lights up here.</p>
    {% endif %}
  </section>

  <aside class="rio-dashboard-tools" aria-label="Framework tools">
    <header class="rio-dashboard-section-head">
      <h2 class="rio-dashboard-section-label">Framework tools</h2>
    </header>
    <ul class="rio-tool-list">
      {# Audit log + Users + Groups are all Administrator-gated
       # on the server side. Merge them into one is_admin block
       # so Staff/Editor operators don't see clickable affordances
       # that 403 on follow-through. #}
      {% 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 &amp; 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 %}