Files
member-console/internal/embeds/templates/partials/operator_integrations.html
T
cgalo5758 257955c9d3 Add operator list-scale contract and People directory
Governed operator lists (organizations, grants, people, billing×4) gain
server-side search, status filters, and 50-row pages with true totals
from count(*) OVER(); state is URL-addressable, out-of-range pages
clamp,
and no-match is distinct from true-empty.

People is the eighth flat sidebar entry: /operator/persons lists persons
newest-joined first (excluding the reserved system person), rows linking
to the existing detail.

Billing gains an operator invoice detail at
/operator/billing/invoices/{invoiceID} reusing the member projection;
open invoices past due present as Overdue (derived, filterable, stored
status untouched); all four views lead with the linked organization and
mute object IDs.

Grants filter over the derived Live/Superseded/Inactive state, the SQL
HAVING predicate pinned to the Go derivation by test. Embedded lists
(org composite ledger, Tier changes) adopt the shared controls under
namespaced params with sibling-state-preserving URLs and scoped htmx
swaps that hold the viewport.

Review corrections: blocked ladder Delete renders disabled with tooltip
and mutations fire toasts; collapse triggers paint their open state;
sections use outside headings; plan topology drops the orphan-product
check; domains policy collapses behind a disclosure.
2026-08-24 03:58:18 -05:00

108 lines
6.2 KiB
HTML

<!-- Integrations landing — registry-driven list of every registered
integration (all provider kinds, so payments integrations appear
alongside provisioning ones). Adding a conforming integration
populates this with no edit here. Browse-only except the
unrecognized-override cleanup, which is confirm-modal-gated. -->
<h1 class="mb-1">Integrations</h1>
<p class="text-muted">External services this deployment integrates with.</p>
{{ if .Error }}
<div class="alert alert-danger" role="alert"><strong>Error:</strong> {{ .Error }}</div>
{{ else if eq (len .Integrations) 0 }}
<div class="text-center py-5">
<p class="text-muted mb-0">No integrations registered.</p>
</div>
{{ else }}
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead>
<tr>
<th>Integration</th>
<th>Registry status<span title="An operator-managed record, not a live health or connectivity check."><svg class="form-help-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg></span></th>
<th>Configuration<span title="Whether the integration's required keys actually resolve to a value."><svg class="form-help-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg></span></th>
<th>Kind<span title="What the integration does for members: a provisioning integration creates and manages member resources; a payment integration handles billing."><svg class="form-help-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg></span></th>
<th>Operations</th>
<th>Resource keys</th>
<th></th>
</tr>
</thead>
<tbody>
{{ range .Integrations }}
<tr>
<td>
<span class="fw-semibold">{{ .DisplayName }}</span>
<span class="text-muted small ms-1">{{ .Slug }}</span>
</td>
<td>
{{ if eq .Status "active" }}
<span class="badge text-bg-success">Active</span>
{{ else }}
<span class="badge text-bg-secondary">{{ .Status }}</span>
{{ end }}
</td>
<td>
{{ if not .Configured }}
<span class="badge text-bg-warning" {{ if .MissingKeysText }}title="Missing: {{ .MissingKeysText }}"{{ end }}>Not configured</span>
{{ else }}
<span class="text-muted small">&mdash;</span>
{{ end }}
</td>
<td><span class="text-capitalize">{{ .Kind }}</span></td>
<td>
{{ range .Operations }}<span class="badge text-bg-light border me-1">{{ . }}</span>{{ end }}
{{ if eq (len .Operations) 0 }}<span class="text-muted small"></span>{{ end }}
</td>
<td>
{{ range .ResourceKeys }}<span class="d-block">{{ if .DisplayName }}{{ .DisplayName }} <span class="text-muted small">({{ .ResourceKey }})</span>{{ else }}<code class="small">{{ .ResourceKey }}</code>{{ end }}</span>{{ end }}
{{ if eq (len .ResourceKeys) 0 }}<span class="text-muted small"></span>{{ end }}
</td>
<td class="text-end text-nowrap">
{{ if .SurfacePath }}
<a href="{{ .SurfacePath }}" class="btn btn-sm btn-outline-primary">Manage</a>
{{ end }}
{{ if .SettingsPath }}
<a href="{{ .SettingsPath }}" class="btn btn-sm btn-outline-secondary">Settings</a>
{{ end }}
{{ if and (not .SurfacePath) (not .SettingsPath) }}
<span class="text-muted small"></span>
{{ end }}
</td>
</tr>
{{ end }}
</tbody>
</table>
</div>
<p class="text-muted small mt-2">{{ len .Integrations }} integration(s) · driven by the provider registry.</p>
{{ end }}
{{ if .Orphans }}
<h2 class="h5 mt-4">Unrecognized overrides</h2>
<p class="text-muted small">Stored configuration overrides whose key matches no installed integration &mdash; left behind by a removed integration or a retired key. They have no effect; remove them when no longer wanted.</p>
<div class="table-responsive">
<table class="table table-sm align-middle">
<thead><tr><th>Key</th><th>Value</th><th></th></tr></thead>
<tbody>
{{ range .Orphans }}
<tr>
<td><code>{{ .Key }}</code></td>
<td><code>{{ .Value }}</code></td>
<td class="text-end">
<button type="button" class="btn btn-sm btn-outline-danger"
data-bs-toggle="modal"
data-bs-target="#confirmActionModal"
data-action-url="{{ routeURL "/operator/integrations/config-orphans/{key}" .Key }}"
data-action-method="delete"
data-action-target="#operator-body"
data-action-title="Delete stored override"
data-action-body="Delete the stored override for {{ .Key }}? It has no effect; this only removes the leftover row."
data-action-confirm-label="Delete override">
Delete
</button>
</td>
</tr>
{{ end }}
</tbody>
</table>
</div>
{{ end }}