{% extends "op_layout.html" %}
{% block title %}Edit {{ slug }} — {{ brand_name | default(value='Rustango Operator Console') }}{% endblock %}
{% block content %}
<nav class="breadcrumb" aria-label="breadcrumb">
<a href="/orgs">Organizations</a>
<span class="sep">›</span>
<code>{{ slug }}</code>
<span class="sep">›</span>
edit
</nav>
<h1>Edit <code>{{ slug }}</code></h1>
<p class="lead">
Adjust the live config for this tenant. Locked fields stay where they
are — re-provisioning needs <code>cargo run -- create-tenant</code> /
<code>drop-tenant</code>. Inputs are generated from
<code>Org::SCHEMA</code> via
<code>rustango::admin::render::render_input</code>, so new columns
added to <code>Org</code> show up here without template edits.
</p>
{% if error %}<div class="alert alert-error">{{ error }}</div>{% endif %}
{% if notice %}<div class="alert alert-notice">{{ notice }}</div>{% endif %}
{#- `host_pattern` below is the tenant's *base* host, and it is one
field. The extra hostnames it can also answer on are a collection
with their own verbs, so they live on their own page — which needs
a link from here, or nothing reaches it. -#}
{#- A div, not a p — see op_orgs.html: a `<form>` closes an open `<p>`. -#}
<div class="page-actions">
<a href="/orgs/{{ slug }}/hosts" class="btn btn-secondary">Hostnames →</a>
<button type="button" class="btn btn-secondary" id="test-connection">Test connection</button>
<span id="probe-result"></span>
{% if provisioning_enabled %}
<form method="post" action="/orgs/{{ slug }}/migrate" class="inline-form"
onsubmit="return confirm('Run migrations for {{ slug }}?');">
<button type="submit" class="btn btn-secondary">Run migrations</button>
</form>
{% endif %}
</div>
<form method="post" action="/orgs/{{ slug }}/edit" class="edit-form">
<fieldset class="section locked">
<legend>Locked</legend>
<table>
{% for row in locked_rows %}
<tr>
<th>{{ row.name }}</th>
<td>{% if row.value %}<code>{{ row.value }}</code>{% else %}<span class="empty">None</span>{% endif %}</td>
</tr>
{% endfor %}
</table>
</fieldset>
<fieldset class="section">
<legend>Editable</legend>
<table>
{% for row in editable_rows %}
<tr>
<th><label for="{{ row.name }}">{{ row.name }}</label></th>
<td>
{{ row.input | safe }}
{% if row.helper %}<div class="field-helper">{{ row.helper }}</div>{% endif %}
</td>
</tr>
{% endfor %}
</table>
</fieldset>
<div class="form-actions">
<button type="submit" class="btn btn-primary">Save</button>
<a href="/orgs" class="btn-link">Cancel</a>
</div>
</form>
<form method="post" action="/orgs/{{ slug }}/edit/branding" enctype="multipart/form-data" class="edit-form">
<fieldset class="section">
<legend>Branding assets</legend>
<table>
<tr>
<th><label for="logo">Logo</label></th>
<td>
{% if logo_url %}<img class="brand-asset-preview" src="{{ logo_url }}" alt="">{% endif %}
<input type="file" id="logo" name="logo" accept="image/png,image/jpeg,image/webp">
<div class="field-helper">PNG, JPEG, or WebP. Max 1 MB. Leave empty to keep the current logo.</div>
</td>
</tr>
<tr>
<th><label for="favicon">Favicon</label></th>
<td>
{% if favicon_url %}<img class="brand-asset-preview" src="{{ favicon_url }}" alt="">{% endif %}
<input type="file" id="favicon" name="favicon" accept="image/png,image/x-icon">
<div class="field-helper">PNG or ICO. Max 1 MB. Leave empty to keep the current favicon.</div>
</td>
</tr>
</table>
</fieldset>
<div class="form-actions">
<button type="submit" class="btn btn-primary">Save branding assets</button>
</div>
</form>
{# v0.27.8 (#78) — Open admin as superuser. Posts to
/orgs/{slug}/impersonate which mints a short-lived tenant
session cookie (1h default), pinned to this slug, and 302s
to the tenant admin's /__admin/. Banner + audit entries make
the session visible + traceable. Hidden if impersonation
wasn't enabled at boot (the operator console route only
mounts when the tenant session secret is shared). #}
{% if impersonate_enabled %}
<form method="post" action="/orgs/{{ slug }}/impersonate" class="edit-form">
<fieldset class="section">
<legend>Impersonate (sign in as superuser)</legend>
<p style="margin: 0 0 var(--space-3) 0; color: var(--color-fg-muted); font-size: 13px">
Open this tenant's admin as a synthetic operator-superuser.
You'll see an unmissable banner during the session; every
write is recorded as
<code>operator:<your-id>:impersonating</code> in the
tenant's audit log. Session expires after the configured
TTL (default 1 hour).
</p>
</fieldset>
<div class="form-actions">
<button type="submit" class="btn btn-primary">Open admin as superuser →</button>
</div>
</form>
{% endif %}
{#- Two actions with very different consequences, kept visibly apart.
Deactivating is one click and reversible; purging asks for the slug
and destroys the storage. No `edit_enabled` guard: this whole page
is only mounted on a console that can edit. -#}
<fieldset class="section danger">
<legend>Decommission</legend>
<form method="post" action="/orgs/{{ slug }}/deactivate" class="inline-form"
onsubmit="return confirm('Deactivate {{ slug }}? It stops serving immediately; nothing is destroyed.');">
<button type="submit" class="btn btn-secondary">Deactivate</button>
</form>
<div class="field-helper">Stops serving on the next request. The data
stays and the tenant can be switched back on from this page.</div>
<hr class="danger-rule">
<form method="post" action="/orgs/{{ slug }}/purge" class="edit-form">
<p class="danger-lead"><strong>Purge — permanent.</strong>
Drops this tenant's schema or database and removes its registry row.
There is no undo and no backup taken.</p>
<table>
<tr>
<th><label for="confirm">type the slug</label></th>
<td>
<input type="text" id="confirm" name="confirm" autocomplete="off"
placeholder="{{ slug }}">
<div class="field-helper">Type <code>{{ slug }}</code> exactly.</div>
</td>
</tr>
<tr>
<th><label for="purge_database">drop the database</label></th>
<td>
<input type="checkbox" id="purge_database" name="purge_database" value="on">
<div class="field-helper">Required for a database-mode tenant,
where purging means <code>DROP DATABASE</code>. Schema-mode
tenants ignore this.</div>
</td>
</tr>
</table>
<p class="form-actions">
<button type="submit" class="btn btn-danger"
onclick="return confirm('Permanently destroy {{ slug }} and all its data?');">Purge {{ slug }}</button>
</p>
</form>
</fieldset>
<script>
document.getElementById("test-connection").addEventListener("click", async (e) => {
const out = document.getElementById("probe-result");
e.target.disabled = true;
out.textContent = "checking…";
try {
const res = await fetch("/orgs/{{ slug }}/test-connection", { method: "POST" });
out.innerHTML = await res.text();
} catch (err) {
out.textContent = "could not reach the console: " + err;
} finally {
e.target.disabled = false;
}
});
</script>
{% endblock %}