- Restructure operator sidebar into a flat task list with indented children; fold plan topology into plan ladders - Expand member catalog non-plan section to all published non-tier products; require recurring Stripe-mapped prices for purchase - Add operator domains placements and terminal-claims ledger; redirect /domains to the FedWiki Sites Domains anchor - Apply canonical vocabulary and chrome/form conventions; migrate seeded FedWiki Sites display name
143 lines
7.0 KiB
HTML
143 lines
7.0 KiB
HTML
<!-- Discourse integration operator page body: group-mapping management and
|
|
link/sync visibility. Swap target for the mutation forms is
|
|
#operator-body (this whole partial re-renders on mutations). -->
|
|
<div class="d-flex justify-content-between align-items-start flex-wrap gap-2 mb-1">
|
|
<h1 class="mb-0">Discourse forum</h1>
|
|
<a href="/operator/integrations/discourse/settings" class="btn btn-sm btn-outline-secondary">Settings</a>
|
|
</div>
|
|
<p class="text-muted">Delivery of forum access to entitled members: group mappings, member↔forum-account links, and sync health. The <em>sweep</em> is the periodic background job that reconciles forum group membership with entitlements. Part of <a href="/operator/integrations">Integrations</a>.</p>
|
|
{{ if not .Configured }}
|
|
<div class="alert alert-info" role="status">
|
|
<strong>Discourse is not configured.</strong>
|
|
Set <code>discourse-base-url</code> and <code>discourse-api-key</code>
|
|
(and optionally <code>discourse-webhook-secret</code>) to enable the
|
|
integration. Until then it is registered but dormant.
|
|
</div>
|
|
{{ else }}
|
|
|
|
{{ if .Error }}
|
|
<div class="alert alert-danger" role="alert">
|
|
<strong>Error:</strong> {{ .Error }}
|
|
</div>
|
|
{{ end }}
|
|
|
|
<div class="card mb-4">
|
|
<div class="card-header"><h2 class="h6 mb-0">Sync health</h2></div>
|
|
<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>
|
|
|
|
<div class="card mb-4">
|
|
<div class="card-header"><h2 class="h6 mb-0">Managed group mappings</h2></div>
|
|
<div class="card-body">
|
|
{{ if eq (len .Mappings) 0 }}
|
|
<p class="text-muted">No groups are mapped yet. Map an entitlement to a Discourse group below to start delivering forum access.</p>
|
|
{{ else }}
|
|
<div class="table-responsive">
|
|
<table class="table table-hover table-sm">
|
|
<thead>
|
|
<tr>
|
|
<th>Entitlement</th>
|
|
<th>Discourse group</th>
|
|
<th>Group ID</th>
|
|
<th>Mapped</th>
|
|
<th></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{{ range .Mappings }}
|
|
<tr>
|
|
<td>{{ if .DisplayName }}{{ .DisplayName }} <span class="text-muted small">({{ .ResourceKey }})</span>{{ else }}<code>{{ .ResourceKey }}</code>{{ end }}</td>
|
|
<td>{{ .GroupName }}</td>
|
|
<td>{{ .GroupID }}</td>
|
|
<td>{{ .CreatedAt }}</td>
|
|
<td>
|
|
<button type="button" class="btn btn-outline-danger btn-sm"
|
|
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 just stops converging it."
|
|
data-action-confirm-label="Remove mapping">
|
|
Remove
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
{{ end }}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{{ end }}
|
|
|
|
<form hx-post="/partials/operator/discourse/mappings"
|
|
hx-target="#operator-body"
|
|
hx-swap="innerHTML"
|
|
class="row g-2 align-items-end mt-2">
|
|
<div class="col-auto">
|
|
<label for="discourse-mapping-resource-key" class="form-label">Entitlement</label>
|
|
<select class="form-select form-select-sm{{ if .FieldErrors.resource_key }} is-invalid{{ end }}"
|
|
id="discourse-mapping-resource-key" name="resource_key" autocomplete="off">
|
|
{{ $selected := .FormValues.resource_key }}
|
|
{{ range .ResourceKeys }}
|
|
<option value="{{ .Key }}" {{ if eq .Key $selected }}selected{{ end }}>{{ .DisplayName }} ({{ .Key }})</option>
|
|
{{ end }}
|
|
</select>
|
|
{{ if .FieldErrors.resource_key }}<div class="invalid-feedback d-block">{{ .FieldErrors.resource_key }}</div>{{ end }}
|
|
</div>
|
|
<div class="col-auto">
|
|
<label for="discourse-mapping-group-name" class="form-label">Discourse group name</label>
|
|
<input type="text" class="form-control form-control-sm{{ if .FieldErrors.group_name }} is-invalid{{ end }}"
|
|
id="discourse-mapping-group-name" name="group_name"
|
|
value="{{ .FormValues.group_name }}" autocomplete="off"
|
|
placeholder="e.g. members">
|
|
{{ if .FieldErrors.group_name }}<div class="invalid-feedback d-block">{{ .FieldErrors.group_name }}</div>{{ end }}
|
|
</div>
|
|
<div class="col-auto">
|
|
<button type="submit" class="btn btn-primary btn-sm">Map group</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h2 class="h6 mb-0">Entitled but unlinked <span class="badge text-bg-secondary">{{ len .Unlinked }}</span></h2>
|
|
</div>
|
|
<div class="card-body">
|
|
{{ if eq (len .Unlinked) 0 }}
|
|
<p class="text-muted mb-0">Every entitled member has a linked forum account.</p>
|
|
{{ else }}
|
|
<p class="text-muted">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 }}
|
|
</div>
|
|
</div>
|
|
|
|
{{ if gt (len .Conflicts) 0 }}
|
|
<div class="card mb-4 border-warning">
|
|
<div class="card-header"><h2 class="h6 mb-0">Link conflicts <span class="badge text-bg-warning">{{ len .Conflicts }}</span></h2></div>
|
|
<div class="card-body">
|
|
<p class="text-muted">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="mb-0">
|
|
{{ range .Conflicts }}
|
|
<li>Forum user <strong>{{ .DiscourseUsername }}</strong> — person <code>{{ .PersonID }}</code></li>
|
|
{{ end }}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
{{ end }}
|
|
|
|
{{ end }}
|