rustango 0.61.13

Batteries-included web framework for Rust: ORM + migrations + auto-admin + multi-tenancy + audit log + auth (sessions, JWT, OAuth2/OIDC, HMAC) + APIs (ViewSet, OpenAPI auto-derive, JSON:API) + jobs (in-mem + Postgres) + email + media (S3 / R2 / B2 / MinIO + presigned uploads + collections + tags) + production middleware (CSRF, CSP, rate-limiting, compression, idempotency, etc.).
Documentation
{% extends "op_layout.html" %}
{% block title %}Database servers — {{ brand_name | default(value='Rustango Operator Console') }}{% endblock %}
{% block content %}
<h1>Database servers</h1>
{% if key_warning %}<div class="alert alert-error key-warning">{{ key_warning }}</div>{% endif %}
{% if error %}<div class="alert alert-error">{{ error }}</div>{% endif %}
{% if notice %}<div class="alert alert-notice">{{ notice }}</div>{% endif %}

<p>Servers that tenant databases can be created on, each with an admin
login. The password is stored encrypted and never shown again. Use
<strong>Test</strong> to check the login and its rights.</p>

{% if servers | length == 0 %}
  <div class="empty">No database servers yet — add one below.</div>
{% else %}
<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Where</th>
      <th>Admin user</th>
      <th>TLS</th>
      <th>Password</th>
      <th>Active</th>
      <th class="actions-col">Actions</th>
    </tr>
  </thead>
  <tbody>
    {% for s in servers %}
    <tr>
      <td><strong>{{ s.name }}</strong></td>
      <td>{% if s.host %}<code>{{ s.host }}{% if s.port %}:{{ s.port }}{% endif %}</code>{% else %}<code>{{ s.sqlite_dir }}</code>{% endif %}</td>
      <td>{{ s.admin_user | default(value='—') }}</td>
      <td>{{ s.tls_mode | default(value='—') }}</td>
      <td>{% if s.has_password %}saved{% else %}—{% endif %}</td>
      <td>{% if s.active %}✓{% else %}—{% endif %}</td>
      <td class="actions-col">
        <button type="button" class="btn-row-action db-server-test" data-name="{{ s.name }}">Test</button>
        <a class="btn-row-action" href="{{ console_prefix }}/db-servers/{{ s.name }}/edit">Edit</a>
        <form method="post" action="{{ console_prefix }}/db-servers/{{ s.name }}/delete" class="inline-form"
              data-confirm="Remove database server {{ s.name }}?">
          {{ csrf_input | default(value='') | safe }}
          <button type="submit" class="btn-row-action">Delete</button>
        </form>
      </td>
    </tr>
    {#- A full-width row, so a long probe result wraps instead of widening the table. -#}
    <tr class="probe-row" hidden>
      <td colspan="7"><div class="probe-result" data-for="{{ s.name }}"></div></td>
    </tr>
    {% endfor %}
  </tbody>
</table>
{% endif %}

<form method="post" action="{{ console_prefix }}/db-servers" class="edit-form" autocomplete="off">
  {{ csrf_input | default(value='') | safe }}
  <fieldset class="section">
    <legend>Add a database server</legend>
    <table>
      <tr>
        <th><label for="name">name</label></th>
        <td>
          <input type="text" id="name" name="name" required pattern="[a-z0-9\-]+" maxlength="64"
                 value="{{ prefill.name | default(value='') }}" placeholder="main-pg">
          <div class="field-helper">Lowercase letters, digits and hyphens.</div>
        </td>
      </tr>
      <tr>
        <th>backend</th>
        <td>
          <code>{{ backend }}</code>
          <div class="field-helper">The registry's backend.</div>
        </td>
      </tr>
      {% if is_sqlite %}
      <tr>
        <th><label for="sqlite_dir">directory</label></th>
        <td>
          <input type="text" id="sqlite_dir" name="sqlite_dir" required
                 value="{{ prefill.sqlite_dir | default(value='') }}" placeholder="/var/lib/app/tenants">
          <div class="field-helper">Where tenant database files are created.</div>
        </td>
      </tr>
      {% else %}
      <tr>
        <th><label for="host">host</label></th>
        <td><input type="text" id="host" name="host" required
                   value="{{ prefill.host | default(value='') }}" placeholder="db.internal"></td>
      </tr>
      <tr>
        <th><label for="port">port</label></th>
        <td>
          <input type="number" id="port" name="port" min="1" max="65535"
                 value="{{ prefill.port | default(value='') }}">
          <div class="field-helper">Blank for the backend's default.</div>
        </td>
      </tr>
      <tr>
        <th><label for="admin_user">admin user</label></th>
        <td><input type="text" id="admin_user" name="admin_user" required
                   value="{{ prefill.admin_user | default(value='') }}" autocomplete="off"></td>
      </tr>
      <tr>
        <th><label for="admin_password">admin password</label></th>
        <td>
          <input type="password" id="admin_password" name="admin_password" autocomplete="new-password">
          <div class="field-helper">Stored encrypted; never shown again.</div>
        </td>
      </tr>
      <tr>
        <th><label for="tls_mode">TLS</label></th>
        <td>
          <select id="tls_mode" name="tls_mode">
            {% set chosen = prefill.tls_mode | default(value='require') %}
            {% for m in tls_modes %}<option value="{{ m }}"{% if chosen == m %} selected{% endif %}>{{ m }}</option>{% endfor %}
          </select>
          <div class="field-helper"><code>require</code> unless you pick another.</div>
        </td>
      </tr>
      {% if backend == "postgres" %}
      <tr>
        <th><label for="maintenance_db">maintenance database</label></th>
        <td>
          <input type="text" id="maintenance_db" name="maintenance_db"
                 value="{{ prefill.maintenance_db | default(value='') }}">
          <div class="field-helper">Blank for <code>postgres</code>.</div>
        </td>
      </tr>
      {% endif %}
      {% endif %}
      <tr>
        <th><label for="active">active</label></th>
        <td><input type="checkbox" id="active" name="active"{% if not prefill or prefill.active %} checked{% endif %}></td>
      </tr>
    </table>
  </fieldset>
  <p class="form-actions">
    <button type="submit" class="btn btn-primary">Save server</button>
  </p>
</form>

<script nonce="{{ csp_nonce }}">
  for (const b of document.querySelectorAll(".db-server-test")) {
    b.addEventListener("click", async () => {
      const name = b.dataset.name;
      const out = document.querySelector('.probe-result[data-for="' + CSS.escape(name) + '"]');
      out.closest("tr").hidden = false;
      b.disabled = true;
      out.textContent = "checking…";
      try {
        const res = await fetch(consoleUrl("/db-servers/" + encodeURIComponent(name) + "/test"), {
          method: "POST",
          headers: { "X-CSRF-Token": csrfToken() },
        });
        await showProbe(out, res);
      } catch (err) {
        out.textContent = "could not reach the console: " + err;
      } finally {
        b.disabled = false;
      }
    });
  }
</script>
{% endblock %}