[Frontend] Admins should be able to see every campaign from the admin panel #437

Open
opened 2026-08-29 06:39:19 +00:00 by claude-bot · 2 comments
Contributor

What

An instance admin has no way to see the campaigns on their own instance. Campaign visibility is scoped to membership, so an admin sees only the campaigns they happen to belong to — the admin panel has no campaign view at all.

Why it matters

Every admin surface that touches a campaign already assumes you can identify one. The recordings/trash list, retention overrides, and audit entries are all campaign-scoped, but there is nowhere to answer "what campaigns exist on this instance, and who runs them".

It also blocks ordinary operator work:

  • Diagnosing a report from a GM whose campaign the admin is not a member of.
  • Checking which campaigns have per-campaign retention overrides, now that those diverge from the instance default (#119, and more so after #427 changed what the default does).
  • Seeing that a campaign has two GMs — currently a latent 500 on the journal (#411) with no way to spot the condition ahead of a user hitting it.
  • Confirming a campaign's guild_id / notification channel when bot routing misbehaves, which today needs database access.

Found while working on dev: a restored database left the operator's login on a user row with no memberships, so the instance appeared to have no campaigns at all. There was no way to tell from the UI whether the campaigns were missing, hidden, or simply not visible to that identity — that took a SQL query to establish. An admin campaign list would have answered it immediately.

Suggested shape

A read-oriented list in the admin panel: name, GM(s), member count, session count, guild_id presence, retention overrides where set, created date. Enough to identify a campaign and reach it, not a second campaign-management UI.

Worth deciding explicitly as part of the design work:

  • Whether an admin can open a campaign, or only see that it exists. Campaign content is a table's private material — transcripts, notes, lore. "The admin can read every table's session notes" is a different product promise from "the admin can administer the instance", and this issue should not smuggle the former in. Listing metadata is clearly fine; a drill-through needs a deliberate answer, and possibly an audit entry when used.
  • Whether it belongs in the admin panel or as a filter on an existing campaigns view.

Acceptance criteria

  • An admin can see every campaign on the instance, regardless of membership
  • The list surfaces enough to identify and act on a campaign: GM(s), member count, guild linkage, retention overrides
  • Campaigns with more than one GM are visibly distinguishable (see #411)
  • A non-admin gains no new visibility
  • The decision on whether admins can read campaign content is made explicitly and written down, not left to fall out of the implementation
  • If content access is granted, it is audited
  • #411 — two-GM campaigns 500 on the journal; this makes the condition findable
  • #427 — per-campaign retention overrides now have materially different consequences, so seeing which campaigns set them matters more
  • #436 — the same dev-restore situation that surfaced this
## What An instance admin has no way to see the campaigns on their own instance. Campaign visibility is scoped to membership, so an admin sees only the campaigns they happen to belong to — the admin panel has no campaign view at all. ## Why it matters Every admin surface that touches a campaign already assumes you can identify one. The recordings/trash list, retention overrides, and audit entries are all campaign-scoped, but there is nowhere to answer "what campaigns exist on this instance, and who runs them". It also blocks ordinary operator work: - Diagnosing a report from a GM whose campaign the admin is not a member of. - Checking which campaigns have per-campaign retention overrides, now that those diverge from the instance default (#119, and more so after #427 changed what the default *does*). - Seeing that a campaign has two GMs — currently a latent 500 on the journal (#411) with no way to spot the condition ahead of a user hitting it. - Confirming a campaign's `guild_id` / notification channel when bot routing misbehaves, which today needs database access. Found while working on dev: a restored database left the operator's login on a user row with no memberships, so the instance appeared to have **no campaigns at all**. There was no way to tell from the UI whether the campaigns were missing, hidden, or simply not visible to that identity — that took a SQL query to establish. An admin campaign list would have answered it immediately. ## Suggested shape A read-oriented list in the admin panel: name, GM(s), member count, session count, `guild_id` presence, retention overrides where set, created date. Enough to identify a campaign and reach it, not a second campaign-management UI. Worth deciding explicitly as part of the design work: - **Whether an admin can open a campaign, or only see that it exists.** Campaign content is a table's private material — transcripts, notes, lore. "The admin can read every table's session notes" is a different product promise from "the admin can administer the instance", and this issue should not smuggle the former in. Listing metadata is clearly fine; a drill-through needs a deliberate answer, and possibly an audit entry when used. - Whether it belongs in the admin panel or as a filter on an existing campaigns view. ## Acceptance criteria - [ ] An admin can see every campaign on the instance, regardless of membership - [ ] The list surfaces enough to identify and act on a campaign: GM(s), member count, guild linkage, retention overrides - [ ] Campaigns with more than one GM are visibly distinguishable (see #411) - [ ] A non-admin gains no new visibility - [ ] The decision on whether admins can read campaign *content* is made explicitly and written down, not left to fall out of the implementation - [ ] If content access is granted, it is audited ## Related - #411 — two-GM campaigns 500 on the journal; this makes the condition findable - #427 — per-campaign retention overrides now have materially different consequences, so seeing which campaigns set them matters more - #436 — the same dev-restore situation that surfaced this
Author
Contributor

Picking up the backend half now (#535 phase 4); the admin-panel screen follows in the rebuild's last lane.

The decision the body asked for, taken explicitly: metadata only. An admin sees that a campaign exists, who runs it, how big it is, whether it is linked to Discord, whether delivery is failing, and which retention overrides it carries. An admin does not open a campaign, and the endpoint returns no campaign content. If a drill-through is ever wanted it is a separate issue with an audit entry, as the body suggests.

Shape: GET /api/admin/campaigns, admin-only, every campaign regardless of membership, soft-deleted ones excluded unless ?include_deleted=true, counts done in SQL, two-GM campaigns visible through gm_count. No migration.

Picking up the **backend half** now (#535 phase 4); the admin-panel screen follows in the rebuild's last lane. The decision the body asked for, taken explicitly: **metadata only.** An admin sees that a campaign exists, who runs it, how big it is, whether it is linked to Discord, whether delivery is failing, and which retention overrides it carries. An admin does not open a campaign, and the endpoint returns no campaign content. If a drill-through is ever wanted it is a separate issue with an audit entry, as the body suggests. Shape: `GET /api/admin/campaigns`, admin-only, every campaign regardless of membership, soft-deleted ones excluded unless `?include_deleted=true`, counts done in SQL, two-GM campaigns visible through `gm_count`. No migration.
Author
Contributor

Backend half done in PR #538 (auto-merge armed); this issue stays open for the admin screen, which lands in the rebuild's last lane (#535 phase 5b).

GET /api/admin/campaigns, admin-only, every campaign regardless of membership, case-insensitive name order with the id as tie-break. Per row: id, name, game system, GMs in join order, gm_count, member and session counts, has_guild, has_notification_channel, discord_delivery_failing (only when the bot's last word was failing; untested reads as false), retention_overrides with only the fields set, is_archived, created_at, deleted_at; soft-deleted campaigns excluded unless ?include_deleted=true.

The content-access decision is written into the code and docs/API.md: metadata only; no drill-through. A test asserts the exact key set and greps the body for seeded secrets (description, webhook, invite code, share token). Two queries regardless of campaign count, pinned by a statement-counting test. Two judgment calls recorded in the PR: session_count includes cancelled sessions; response models sit inline in admin.py as that file's other models do. Full suite 2,516 passed, +12.

Backend half done in PR #538 (auto-merge armed); this issue stays open for the admin screen, which lands in the rebuild's last lane (#535 phase 5b). `GET /api/admin/campaigns`, admin-only, every campaign regardless of membership, case-insensitive name order with the id as tie-break. Per row: id, name, game system, GMs in join order, `gm_count`, member and session counts, `has_guild`, `has_notification_channel`, `discord_delivery_failing` (only when the bot's last word was `failing`; untested reads as false), `retention_overrides` with only the fields set, `is_archived`, `created_at`, `deleted_at`; soft-deleted campaigns excluded unless `?include_deleted=true`. The content-access decision is written into the code and `docs/API.md`: **metadata only; no drill-through**. A test asserts the exact key set and greps the body for seeded secrets (description, webhook, invite code, share token). Two queries regardless of campaign count, pinned by a statement-counting test. Two judgment calls recorded in the PR: `session_count` includes cancelled sessions; response models sit inline in `admin.py` as that file's other models do. Full suite 2,516 passed, +12.
Sign in to join this conversation.
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
rbrooks/Quest-Board#437
No description provided.