rustio-admin 0.31.0

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

{# RustIO Console — model list. Masthead + command bar + data board,
 # matching ui_kits/admin. Keeps the framework's real columns, search,
 # filters, sort, saved views, bulk actions, and pagination + their
 # admin.js hooks (data-rio-dropdown / data-rio-bulk / row actions). #}

{% macro filter_icon(kind) -%}
{%- if kind == "date_range" -%}{{ icon("clock", class="rio-icon") }}
{%- elif kind == "fk_autocomplete" -%}{{ icon("database", class="rio-icon") }}
{%- elif kind == "multi_select" -%}{{ icon("menu", class="rio-icon") }}
{%- else -%}{{ icon("flag", class="rio-icon") }}
{%- endif -%}
{%- endmacro %}

{% macro filter_active(group) -%}
{%- if group.kind == "date_range" and group.has_active_range -%}1
{%- elif group.kind == "multi_select" and group.multi_selected -%}1
{%- elif group.kind == "fk_autocomplete" and group.fk_selected_id -%}1
{%- elif group.kind not in ["date_range", "multi_select", "fk_autocomplete"] and group.current -%}1
{%- endif -%}
{%- endmacro %}

{% macro filter_body(group, admin_name, search_query, active_sort_field, active_sort_dir, active_per_page_override, csrf_token) %}
{% if group.kind == "fk_autocomplete" %}
<form method="get" action="/admin/{{ admin_name }}" class="rio-fk-autocomplete" data-rio-fk-autocomplete data-rio-fk-lookup-url="{{ group.fk_lookup_url }}" data-rio-fk-field-name="{{ group.field }}">
  {% for pair in group.hidden_pairs %}<input type="hidden" name="{{ pair[0] }}" value="{{ pair[1] }}">{% endfor %}
  {% if search_query %}<input type="hidden" name="q" value="{{ search_query }}">{% endif %}
  {% if active_sort_field %}<input type="hidden" name="sort" value="{{ active_sort_field }}"><input type="hidden" name="dir" value="{{ active_sort_dir }}">{% endif %}
  {% if active_per_page_override %}<input type="hidden" name="per_page" value="{{ active_per_page_override }}">{% endif %}
  <input type="hidden" name="{{ group.field }}" value="{{ group.fk_selected_id }}" data-rio-fk-id>
  <div class="rio-fk-autocomplete-wrap">
    <input type="search" class="rio-input rio-fk-autocomplete-input" value="{{ group.fk_selected_label or group.fk_selected_id }}" placeholder="Search {{ group.fk_target_label }}…" autocomplete="off" data-rio-fk-search>
    <ul class="rio-fk-autocomplete-results" data-rio-fk-results hidden></ul>
  </div>
  <div class="rio-fk-autocomplete-actions">
    <button type="submit" class="rio-btn rio-btn--primary rio-btn--sm">Apply</button>
    {% if group.fk_selected_id %}<a href="{{ group.all_link }}" class="rio-btn rio-btn--secondary rio-btn--sm">Clear</a>{% endif %}
  </div>
</form>
{% elif group.kind == "multi_select" %}
<form method="get" action="/admin/{{ admin_name }}" class="rio-multi-select">
  {% for pair in group.hidden_pairs %}<input type="hidden" name="{{ pair[0] }}" value="{{ pair[1] }}">{% endfor %}
  {% if search_query %}<input type="hidden" name="q" value="{{ search_query }}">{% endif %}
  {% if active_sort_field %}<input type="hidden" name="sort" value="{{ active_sort_field }}"><input type="hidden" name="dir" value="{{ active_sort_dir }}">{% endif %}
  {% if active_per_page_override %}<input type="hidden" name="per_page" value="{{ active_per_page_override }}">{% endif %}
  <ul class="rio-multi-select-options">
    {% for opt in group.options %}
    <li><label class="rio-multi-select-option"><input type="checkbox" name="{{ group.field }}" value="{{ opt.value }}"{% if opt.selected %} checked{% endif %}><span>{{ opt.label }}</span></label></li>
    {% endfor %}
  </ul>
  <div class="rio-multi-select-actions">
    <button type="submit" class="rio-btn rio-btn--primary rio-btn--sm">Apply</button>
    {% if group.multi_selected %}<a href="{{ group.all_link }}" class="rio-btn rio-btn--secondary rio-btn--sm">Clear</a>{% endif %}
  </div>
</form>
{% elif group.kind == "date_range" %}
<form method="get" action="/admin/{{ admin_name }}" class="rio-date-range">
  {% for pair in group.hidden_pairs %}<input type="hidden" name="{{ pair[0] }}" value="{{ pair[1] }}">{% endfor %}
  {% if search_query %}<input type="hidden" name="q" value="{{ search_query }}">{% endif %}
  {% if active_sort_field %}<input type="hidden" name="sort" value="{{ active_sort_field }}"><input type="hidden" name="dir" value="{{ active_sort_dir }}">{% endif %}
  {% if active_per_page_override %}<input type="hidden" name="per_page" value="{{ active_per_page_override }}">{% endif %}
  <label class="rio-date-range-field"><span class="rio-date-range-label-text">From</span><input type="date" name="{{ group.date_from_name }}" value="{{ group.date_from_value }}" class="rio-input rio-date-range-input"></label>
  <label class="rio-date-range-field"><span class="rio-date-range-label-text">To</span><input type="date" name="{{ group.date_to_name }}" value="{{ group.date_to_value }}" class="rio-input rio-date-range-input"></label>
  <div class="rio-date-range-actions">
    <button type="submit" class="rio-btn rio-btn--primary rio-btn--sm">Apply</button>
    {% if group.has_active_range %}<a href="{{ group.all_link }}" class="rio-btn rio-btn--secondary rio-btn--sm">Clear</a>{% endif %}
  </div>
</form>
{% else %}
<div class="rio-chip-row">
  <a href="{{ group.all_link }}" class="rio-chip{% if not group.current %} is-active{% endif %}">All</a>
  {% for opt in group.options %}
  <a href="{{ opt.link }}" class="rio-chip{% if opt.selected %} is-active{% endif %}">{{ opt.label }}</a>
  {% endfor %}
</div>
{% endif %}
{% endmacro %}


{% block content %}
<nav class="rio-crumbs"><a href="/admin">Home</a><span class="rio-crumb-sep">·</span><span class="rio-crumb-current">{{ display_name }}</span></nav>

<div class="rio-masthead-top">
  <div>
    <h1>{{ display_name }}</h1>
    <p class="rio-masthead-desc">Every {{ singular_name|lower }} rustio generated for {{ app_name }} — searchable, sortable, production-ready.</p>
  </div>
  <div class="rio-masthead-cta">
    <a class="rio-btn rio-btn--secondary rio-btn--md" href="{{ csv_export_url }}" download>{{ icon("download") }} Export</a>
    {% if not read_only %}
    <a class="rio-btn rio-btn--primary rio-btn--md" href="/admin/{{ admin_name }}/new">{{ icon("plus") }} Add {{ singular_name }}</a>
    {% endif %}
  </div>
</div>

{# ---- Command bar ---- #}
<div class="rio-cmdbar">
  <form method="get" action="/admin/{{ admin_name }}" class="rio-search rio-search--lg rio-search--hero{% if search_query %} rio-search--filled{% endif %}" role="search">
    <span class="rio-search-icon">{{ icon("search") }}</span>
    <input class="rio-input" type="search" name="q" value="{{ search_query }}" placeholder="Search {{ display_name|lower }}…" aria-label="Search">
    {% for pair in active_filter_pairs %}<input type="hidden" name="{{ pair[0] }}" value="{{ pair[1] }}">{% endfor %}
    {% if active_sort_field %}<input type="hidden" name="sort" value="{{ active_sort_field }}"><input type="hidden" name="dir" value="{{ active_sort_dir }}">{% endif %}
    {% if active_per_page_override %}<input type="hidden" name="per_page" value="{{ active_per_page_override }}">{% endif %}
    <button type="submit" class="rio-search-go" tabindex="-1">Search</button>
  </form>

  {% if filters %}
    {% for group in filters %}{% if loop.index <= 2 %}
    <div class="rio-dropdown" data-rio-dropdown>
      <button type="button" class="rio-btn rio-btn--secondary rio-btn--lg rio-dropdown-toggle{% if filter_active(group) %} is-active{% endif %}" aria-haspopup="true" aria-expanded="false">{{ filter_icon(group.kind) }} {{ group.label }} {{ icon("chevron-down", class="rio-chev") }}</button>
      <div class="rio-dropdown-panel rio-menu" role="dialog" aria-label="{{ group.label }}"><div class="rio-dropdown-section">{{ filter_body(group, admin_name, search_query, active_sort_field, active_sort_dir, active_per_page_override, csrf_token) }}</div></div>
    </div>
    {% endif %}{% endfor %}
    {% if filters|length > 2 %}
    <div class="rio-dropdown" data-rio-dropdown>
      <button type="button" class="rio-btn rio-btn--secondary rio-btn--lg rio-dropdown-toggle" aria-haspopup="true" aria-expanded="false">{{ icon("filter") }} More filters{% if active_filter_count > 0 %} <span class="rio-dropdown-badge">{{ active_filter_count }}</span>{% endif %} {{ icon("chevron-down", class="rio-chev") }}</button>
      <div class="rio-dropdown-panel rio-menu" role="dialog" aria-label="More filters">
        {% for group in filters %}{% if loop.index > 2 %}<div class="rio-dropdown-section"><span class="rio-dropdown-label">{{ group.label }}</span>{{ filter_body(group, admin_name, search_query, active_sort_field, active_sort_dir, active_per_page_override, csrf_token) }}</div>{% endif %}{% endfor %}
        {% if active_filter_count > 0 %}<div class="rio-dropdown-footer"><a href="{{ clear_all_filters_link }}" class="rio-btn rio-btn--secondary rio-btn--sm">Clear all</a></div>{% endif %}
      </div>
    </div>
    {% endif %}
  {% endif %}

  {% if sort_fields %}
  <div class="rio-dropdown" data-rio-dropdown>
    <button type="button" class="rio-btn rio-btn--secondary rio-btn--lg rio-dropdown-toggle" aria-haspopup="true" aria-expanded="false">{{ icon("sliders") }} Sort: {{ current_sort_field_label }} {{ icon("chevron-down", class="rio-chev") }}</button>
    <div class="rio-dropdown-panel rio-menu" role="dialog" aria-label="Sort">
      <div class="rio-dropdown-section"><span class="rio-dropdown-label">Sort by</span>
        <div class="rio-dropdown-menu">
          <a href="{{ default_sort_link }}" class="rio-menu-item{% if not active_sort_field %} is-active{% endif %}">Default order</a>
          {% for sf in sort_fields %}<a href="{{ sf.asc_link }}" class="rio-menu-item{% if sf.is_active %} is-active{% endif %}">{{ sf.label }}</a>{% endfor %}
        </div>
      </div>
    </div>
  </div>
  {% if active_sort_field %}<a class="rio-btn rio-btn--secondary rio-btn--lg" href="{{ sort_dir_toggle_link }}" title="Switch direction">{{ icon("arrow-up-down") }} {{ current_sort_dir_label }}</a>{% endif %}
  {% endif %}

  {% if per_page_options %}
  <div class="rio-dropdown" data-rio-dropdown>
    <button type="button" class="rio-btn rio-btn--secondary rio-btn--lg rio-dropdown-toggle" aria-haspopup="true" aria-expanded="false">{{ icon("table") }} {{ current_per_page_label }} / page {{ icon("chevron-down", class="rio-chev") }}</button>
    <div class="rio-dropdown-panel rio-menu" role="dialog" aria-label="Rows per page"><div class="rio-dropdown-section"><div class="rio-chip-row">{% for opt in per_page_options %}<a href="{{ opt.link }}" class="rio-chip{% if opt.is_active %} is-active{% endif %}">{{ opt.label }}</a>{% endfor %}</div></div></div>
  </div>
  {% endif %}

  {% if search_query or active_filter_count > 0 %}<a class="rio-btn rio-btn--subtle rio-btn--lg" href="{{ clear_all_filters_link }}">{{ icon("rotate-ccw") }} Reset</a>{% endif %}
</div>

{% if active_filter_pills %}
<div class="rio-active-filters" role="region" aria-label="Active filters">
  <span class="rio-active-filters-label">Active</span>
  {% for pill in active_filter_pills %}<span class="rio-active-pill"><span class="rio-active-pill-key">{{ pill.label }}:</span> {{ pill.value_label }} <a href="{{ pill.remove_link }}" class="rio-active-pill-x" aria-label="Remove {{ pill.label }} filter">×</a></span>{% endfor %}
  <a href="{{ clear_all_filters_link }}" class="rio-active-filters-clear">Clear all</a>
</div>
{% endif %}

{# ---- View-mode switcher (only when a saved ViewSpec exists) ---- #}
{% if mode_links %}
<nav class="rio-view-modes" aria-label="View mode">
  {% for m in mode_links %}<a href="{{ m.href }}" class="rio-view-mode{% if m.active %} is-active{% endif %}">{{ m.label }}</a>{% endfor %}
</nav>
{% endif %}

{# ---- Board ---- #}
{% if not read_only and not adaptive %}
<form method="post" action="/admin/{{ admin_name }}/bulk_delete" class="rio-bulk-form" data-rio-bulk>
<input type="hidden" name="_csrf" value="{{ csrf_token }}">
<input type="hidden" name="_ids" value="" data-rio-bulk-ids>
<div class="rio-bulkbar" role="region" aria-live="polite" aria-label="Bulk actions">
  <span class="rio-bulkbar-count"><strong data-rio-bulk-count>0</strong> selected</span>
  <button type="button" class="rio-btn rio-btn--ghost rio-btn--sm" data-rio-bulk-clear>Clear</button>
  <span style="margin-inline-start:auto;display:flex;gap:8px">
    <button type="submit" class="rio-btn rio-btn--subtle rio-btn--sm" style="color:var(--rio-danger)">{{ icon("trash") }} Delete selected</button>
    {% for btn in bulk_action_buttons %}<button type="submit" formaction="{{ btn.form_action }}" class="rio-btn rio-btn--secondary rio-btn--sm{% if btn.destructive %} rio-btn--danger{% endif %}">{{ btn.label }}</button>{% endfor %}
  </span>
</div>
{% endif %}

<div class="rio-board">
  {% if rows %}
  {% if adaptive %}{% include "admin/_list_adaptive.html" %}{% else %}
  <div style="overflow:auto">
  <table class="rio-dtable">
    <thead>
      <tr>
        {% if not read_only %}<th class="col-check"><input type="checkbox" class="rio-row-checkbox-all" data-rio-bulk-all aria-label="Select all"></th>{% endif %}
        {% for f in fields %}<th class="rio-th--{{ f.kind }}{% if f.sort_active %} is-sort-{{ f.sort_active }}{% endif %}"><a href="{{ f.sort_link }}">{{ f.label }}{% if f.sort_active == "asc" %} ▲{% elif f.sort_active == "desc" %} ▼{% endif %}</a></th>{% endfor %}
        <th class="col-act">&nbsp;</th>
      </tr>
    </thead>
    <tbody>
      {% for row in rows %}
      <tr>
        {% if not read_only %}<td class="col-check"><input type="checkbox" class="rio-row-checkbox" data-rio-bulk-row value="{{ row.id }}" aria-label="Select row {{ row.id }}"></td>{% endif %}
        {% for f in fields %}
        <td class="rio-td--{{ f.kind }}">
          {% if loop.first %}
            <a class="rio-cell-primary" href="/admin/{{ admin_name }}/{{ row.id }}/edit">{% if row.highlights[f.name] %}{{ row.highlights[f.name]|safe }}{% else %}{{ row[f.name]|default(value=row.id) }}{% endif %}</a>
          {% elif f.kind == "checkbox" %}
            <span class="rio-pill rio-pill--{% if row[f.name] %}on{% else %}off{% endif %}">{% if row[f.name] %}Yes{% else %}No{% endif %}</span>
          {% elif row.links[f.name] %}
            <a class="rio-prod-name" href="{{ row.links[f.name] }}">{% if row.highlights[f.name] %}{{ row.highlights[f.name]|safe }}{% else %}{{ row[f.name]|default(value='') }}{% endif %}</a>
          {% elif row.highlights[f.name] %}
            {{ row.highlights[f.name]|safe }}
          {% else %}
            {{ row[f.name]|default(value='') }}
          {% endif %}
        </td>
        {% endfor %}
        <td class="col-act">
          {% if not read_only %}
          <span class="rio-row-actions">
            <a class="rio-iconbtn rio-iconbtn--sm" href="/admin/{{ admin_name }}/{{ row.id }}/edit" aria-label="Edit">{{ icon("pencil") }}</a>
            <a class="rio-iconbtn rio-iconbtn--sm" href="/admin/{{ admin_name }}/{{ row.id }}/delete" aria-label="Delete" style="color:var(--rio-danger)">{{ icon("trash") }}</a>
          </span>
          {% endif %}
        </td>
      </tr>
      {% endfor %}
    </tbody>
  </table>
  </div>
  {% endif %}
  <div class="rio-board-foot">
    <span style="font-size:13px;color:var(--rio-text-mute)">Showing <strong style="color:var(--rio-text-hi);font-weight:600">{{ rows|length }}</strong> of <strong style="color:var(--rio-text-hi);font-weight:600">{{ total_rows }}</strong> {{ display_name|lower }}</span>
    {% if total_pages > 1 %}
    <nav class="rio-pagination" aria-label="Pagination">
      {% if prev_page_link %}<a class="rio-page" href="{{ prev_page_link }}" aria-label="Previous">{{ icon("arrow-left") }}</a>{% endif %}
      {% for item in page_items %}{% if item.kind == "ellipsis" %}<span class="rio-page rio-page--ellipsis"></span>{% elif item.is_active %}<span class="rio-page" aria-current="page">{{ item.number }}</span>{% else %}<a class="rio-page" href="{{ item.link }}">{{ item.number }}</a>{% endif %}{% endfor %}
      {% if next_page_link %}<a class="rio-page rio-page--next" href="{{ next_page_link }}">Next {{ icon("arrow-right") }}</a>{% endif %}
    </nav>
    {% endif %}
  </div>
  {% else %}
  <div class="rio-empty">
    {{ icon("inbox") }}
    <div class="rio-empty-title">No {{ display_name|lower }} yet</div>
    <div>{% if read_only %}There are no records to display here.{% else %}Add the first {{ singular_name|lower }} to get started.{% endif %}</div>
    {% if not read_only %}<div style="margin-block-start:14px"><a class="rio-btn rio-btn--primary rio-btn--md" href="/admin/{{ admin_name }}/new">{{ icon("plus") }} Add {{ singular_name }}</a></div>{% endif %}
  </div>
  {% endif %}
</div>

{% if not read_only and not adaptive %}</form>{% endif %}
{% endblock %}