Spread the Rules table's treatment to every table that carries verbs (record-table-actions, archived 2026-09-19): the table is align-middle, the Actions header and cells are text-end, every button in an Actions cell carries ms-1, and the header's word is for assistive technology only (a visually-hidden span; Primer: row actions do not require a visible column header). The two integrations tables and the member domains list, which headed their controls with an empty cell, take the hidden word too. A new anatomy-lint rule, actions-column, refuses a table that drifts from any of it. Decisions on the way: a row tint marks the row in play (the current workspace, a staged change), never a record's status; the member catalog lists only what can be bought, so a Listed product without an active, synced, recurring price is left out instead of shown with "Not available for purchase"; two Actions cells that carried text in a verb's place are empty (the Placements column and the pending panel already say why); and four record tables gain their width floor. Specs: page-anatomy "Tables share one density" modified, ui-quality-gate gains "Lint refuses an Actions column without its treatment", member-product-discovery's Extras bucket and truthful-copy requirements modified. docs/design-system.md §6 states the treatment and its reasons.
112 lines
4.6 KiB
HTML
112 lines
4.6 KiB
HTML
{{- /* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Commercial */ -}}
|
|
{{- /* SPDX-FileCopyrightText: 2025-2026 Christian Galo */ -}}
|
|
|
|
{{/* discourse_operator.html
|
|
Discourse integration operator page body: group-mapping management
|
|
and link/sync visibility, below the Integrations trail with the
|
|
Settings action in the header slot. Swap target for the mutation
|
|
forms is #operator-body (this whole partial re-renders on
|
|
mutations). Built from the page-anatomy parts: the unconfigured
|
|
integration is the blocked empty-state branch, sections are titled
|
|
by sectionHeader (the sweep explainer rides the Sync health
|
|
tooltip), and the map-group form precedes the mappings table the
|
|
way every catalog create form does. */}}
|
|
{{ template "pageHeader" .Header }}
|
|
{{ if not .Configured }}
|
|
{{ template "emptyState" .NotConfigured }}
|
|
{{ else }}
|
|
|
|
{{ if .Error }}
|
|
<div class="alert alert-danger" role="alert">
|
|
<strong>Error:</strong> {{ .Error }}
|
|
</div>
|
|
{{ end }}
|
|
|
|
{{ template "sectionHeader" .SweepHeader }}
|
|
<div class="card mb-3">
|
|
<div class="card-body">
|
|
<dl class="row mb-0">
|
|
<dt class="col-sm-3">Last sweep</dt>
|
|
<dd class="col-sm-9">
|
|
{{ .SweepStatus }}{{ if .SweepTime }} <span class="text-muted">({{ .SweepTime }})</span>{{ end }}
|
|
</dd>
|
|
</dl>
|
|
</div>
|
|
</div>
|
|
|
|
{{ template "sectionHeader" .MapGroupHeader }}
|
|
<div class="card mb-3">
|
|
<div class="card-body">
|
|
{{ template "form" .Form }}
|
|
</div>
|
|
</div>
|
|
|
|
{{ template "sectionHeader" .MappingsHeader }}
|
|
{{ if eq (len .Mappings) 0 }}
|
|
{{ template "emptyState" .NoMappings }}
|
|
{{ else }}
|
|
<div class="table-responsive">
|
|
{{/* list-scale exempt: curated configuration list, one row per entitlement-to-group mapping an operator creates */}}
|
|
<table class="table table-hover table-sm table-record align-middle">
|
|
<thead>
|
|
<tr>
|
|
<th>Entitlement</th>
|
|
<th>Discourse group</th>
|
|
<th>Group ID</th>
|
|
<th>Mapped</th>
|
|
<th class="text-end"><span class="visually-hidden">Actions</span></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{{ range .Mappings }}
|
|
<tr>
|
|
<th scope="row">{{ if .DisplayName }}{{ .DisplayName }} <span class="text-muted small fw-normal">({{ .ResourceKey }})</span>{{ else }}<code>{{ .ResourceKey }}</code>{{ end }}</th>
|
|
<td>{{ .GroupName }}</td>
|
|
<td><code class="text-muted small">{{ .GroupID }}</code></td>
|
|
<td>{{ .CreatedAt }}</td>
|
|
<td class="text-end">
|
|
<button type="button" class="btn btn-outline-danger btn-sm ms-1"
|
|
data-bs-toggle="modal"
|
|
data-bs-target="#confirmActionModal"
|
|
data-action-url="{{ .DeleteURL }}"
|
|
data-action-method="delete"
|
|
data-action-target="#operator-body"
|
|
data-action-title="Remove mapping"
|
|
data-action-body="Stop managing membership of "{{ .GroupName }}"? Current forum-side membership is left as-is; the console stops converging it."
|
|
data-action-confirm-label="Remove mapping">
|
|
Remove
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
{{ end }}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{{ end }}
|
|
|
|
{{ template "sectionHeader" .UnlinkedHeader }}
|
|
{{ if eq (len .Unlinked) 0 }}
|
|
<p class="text-muted">Every entitled member has a linked forum account.</p>
|
|
{{ else }}
|
|
<p class="text-muted small mb-2">These members hold forum access but have no linked Discourse account yet. In <code>oidc</code> mode they link automatically on their first forum login; this is a normal state, not a sync failure.</p>
|
|
<ul class="mb-0">
|
|
{{ range .Unlinked }}
|
|
<li>{{ .DisplayName }} <span class="text-muted">({{ .Email }})</span></li>
|
|
{{ end }}
|
|
</ul>
|
|
{{ end }}
|
|
|
|
{{ if gt (len .Conflicts) 0 }}
|
|
{{ template "sectionHeader" .ConflictsHeader }}
|
|
<div class="alert alert-warning">
|
|
<p class="small mb-2">These links resolved to a forum account that another person's identity also claims. They are excluded from group convergence until resolved; the console never reassigns a forum identity on its own.</p>
|
|
<ul class="small mb-0">
|
|
{{ range .Conflicts }}
|
|
<li>Forum user <strong>{{ .DiscourseUsername }}</strong>: person <a href="/operator/persons/{{ .PersonID }}"><code>{{ .PersonID }}</code></a></li>
|
|
{{ end }}
|
|
</ul>
|
|
</div>
|
|
{{ end }}
|
|
|
|
{{ end }}
|