rustio-admin 0.31.0

Django Admin, but for Rust. A small, focused admin framework.
Documentation
{% extends "admin/_base.html" %}
{% block content %}
<nav class="rio-crumbs"><a href="/admin">Home</a><span class="rio-crumb-sep">·</span><a href="/admin/dev/view-designer">View designer</a><span class="rio-crumb-sep">·</span><span class="rio-crumb-current">{{ display_name }}</span></nav>
<div class="rio-masthead-top">
  <div>
    <h1>Composition editor</h1>
    <p class="rio-masthead-desc">
      Define how {{ display_name }} renders — the editor only produces a <code>ViewSpec</code>.
      <span class="rio-vd-flag">deterministic · no AI at runtime</span>
    </p>
  </div>
  <div class="rio-masthead-cta">
    <span class="rio-vd-chip">Primary developer</span>
    <button type="submit" form="vdform" class="rio-btn rio-btn--primary rio-btn--md">Save ViewSpec</button>
  </div>
</div>

{% if flash %}<div class="rio-alert rio-alert--{{ flash.kind }}" role="status">{{ flash.message }}</div>{% endif %}

{# ---- Live preview (server-rendered through the runtime render core) ---- #}
<section class="rio-vd-preview" aria-label="Live preview">
  <header class="rio-vd-preview__head">
    <span class="rio-vd-preview__title">Live preview <span class="rio-meta">· {{ preview.mode }} mode</span></span>
    <span class="rio-vd-flag rio-vd-flag--quiet">deterministic · no AI</span>
  </header>
  <div class="rio-vd-filters">
    <span class="rio-vd-filters__label">Filters</span>
    {% for f in fields %}{% if f.filterable %}<span class="rio-vd-chip rio-vd-chip--filter">{{ f.label }}</span>{% endif %}{% endfor %}
  </div>
  <div class="rio-vd-canvas">
    <div class="av-list av-list--{{ preview.mode }}">
      {% for row in preview.rows %}
      <div class="av-row">{% include "admin/view_layer/_row.html" %}</div>
      {% else %}
      <div class="rio-empty-state"><p class="rio-empty-state__lead">No preview rows.</p></div>
      {% endfor %}
    </div>
  </div>
  <p class="rio-vd-note">Reflects the last <strong>saved</strong> state — save to refresh the preview.</p>
</section>

<form id="vdform" method="post" action="/admin/dev/view-designer/{{ admin_name }}/save" data-rio-vd>
  <input type="hidden" name="_csrf" value="{{ csrf_token }}">

  {# ---- Modes ---- #}
  <section class="rio-vd-modes" aria-label="Modes">
    <label class="rio-vd-mode-default">
      <span>Default mode</span>
      <select name="default_mode" class="rio-input">
        {% for opt in mode_options %}<option value="{{ opt.slug }}"{% if opt.slug == default_mode %} selected{% endif %}>{{ opt.label }}</option>{% endfor %}
      </select>
    </label>
    <div class="rio-vd-allowed">
      <span class="rio-vd-allowed__label">In the switcher</span>
      {% for m in mode_choices %}
      <label class="rio-vd-allowed__opt">
        <input type="checkbox" name="mode_allowed__{{ m.slug }}"{% if m.allowed %} checked{% endif %}{% if m.is_default %} disabled{% endif %}>
        <span>{{ m.label }}{% if m.is_default %} (default){% endif %}</span>
      </label>
      {% endfor %}
    </div>
  </section>

  {# ---- Fields as rows ---- #}
  <section class="rio-vd-fields" aria-label="Fields">
    <header class="rio-vd-fields__head">
      <h2 class="rio-vd-fields__title">Fields</h2>
      <span class="rio-meta">Set a role · toggle filter · reorder</span>
    </header>
    <ul class="rio-vd-rows">
      {% for field in fields %}
      <li class="rio-vd-row" data-rio-vd-row>
        <span class="rio-vd-grip" aria-hidden="true"></span>
        <span class="rio-vd-dot rio-vd-dot--{{ field.role }}" data-rio-vd-dot></span>
        <span class="rio-vd-name">
          <code>{{ field.name }}</code>
          <span class="rio-meta">{{ field.label }}</span>
          {% if field.composed %}<span class="rio-vd-badge">Composed</span>{% endif %}
        </span>
        <span class="rio-vd-controls">
          <select name="role__{{ field.name }}" class="rio-input rio-vd-role" data-rio-vd-role aria-label="Role for {{ field.name }}">
            {% for opt in role_options %}<option value="{{ opt.slug }}"{% if opt.slug == field.role %} selected{% endif %}>{{ opt.label }}</option>{% endfor %}
          </select>
          <label class="rio-vd-toggle" title="Show a filter control for this field">
            <input type="checkbox" name="filter__{{ field.name }}"{% if field.filterable %} checked{% endif %}>
            <span class="rio-vd-toggle__face">Filter</span>
          </label>
          <input type="number" name="priority__{{ field.name }}" value="{{ field.priority }}" class="rio-input rio-input--mono rio-vd-prio" data-rio-vd-priority aria-label="Order for {{ field.name }}">
          <span class="rio-vd-reorder">
            <button type="button" class="rio-iconbtn rio-iconbtn--sm" data-rio-vd-up aria-label="Move up"></button>
            <button type="button" class="rio-iconbtn rio-iconbtn--sm" data-rio-vd-down aria-label="Move down"></button>
          </span>
        </span>
      </li>
      {% endfor %}
    </ul>
  </section>

  {# ---- Compositions (merge fields into one cell) ---- #}
  <section class="rio-vd-comp" aria-label="Compositions">
    <header class="rio-vd-fields__head">
      <h2 class="rio-vd-fields__title">Merge fields into one cell</h2>
      <span class="rio-meta">Optional — combine a primary with secondaries (e.g. name over email)</span>
    </header>
    {% for slot in comp_slots %}
    <fieldset class="rio-vd-slot">
      <legend>Composition {{ slot.index }}</legend>
      <label class="rio-form-field">
        <span>Primary field</span>
        <select name="comp{{ slot.index }}_primary" class="rio-input">
          <option value="">— none (disabled) —</option>
          {% for f in slot.fields %}<option value="{{ f.name }}"{% if f.name == slot.primary %} selected{% endif %}>{{ f.label }}</option>{% endfor %}
        </select>
      </label>
      <label class="rio-form-field">
        <span>Label (optional)</span>
        <input type="text" name="comp{{ slot.index }}_label" value="{{ slot.label }}" class="rio-input">
      </label>
      <label class="rio-form-field">
        <span>Style</span>
        <select name="comp{{ slot.index }}_style" class="rio-input">
          {% for s in compose_styles %}<option value="{{ s.slug }}"{% if s.slug == slot.style %} selected{% endif %}>{{ s.label }}</option>{% endfor %}
        </select>
      </label>
      <div class="rio-form-field">
        <span>Secondary fields</span>
        <div class="rio-vd-secs">
          {% for f in slot.fields %}
          <label class="rio-vd-sec"><input type="checkbox" name="comp{{ slot.index }}_sec__{{ f.name }}"{% if f.is_secondary %} checked{% endif %}> <span class="rio-meta">{{ f.name }}</span></label>
          {% endfor %}
        </div>
      </div>
    </fieldset>
    {% endfor %}
  </section>

  <div class="rio-vd-save"><button type="submit" class="rio-btn rio-btn--primary rio-btn--md">Save ViewSpec</button></div>
</form>

{# ---- Generated ViewSpec (the single source of truth) ---- #}
<section class="rio-vd-json" aria-label="Generated ViewSpec">
  <header class="rio-vd-json__bar">
    <span class="rio-vd-json__dots" aria-hidden="true"><i></i><i></i><i></i></span>
    <span class="rio-vd-json__name">{{ admin_name }}.viewspec.json</span>
    <span class="rio-vd-json__hint">generated from the editor above</span>
  </header>
  <pre class="rio-vd-json__body"><code>{{ spec_json }}</code></pre>
  <footer class="rio-vd-json__foot">{% if is_saved %}Saved{% else %}Draft — not yet saved{% endif %} · Ready.</footer>
</section>
{% endblock %}