feat(desktop): in-app admin console for relay operators - #4768
Open
wpfleger96 wants to merge 1 commit into
Open
Conversation
wpfleger96
force-pushed
the
wpfleger/desktop-admin-surface
branch
4 times, most recently
from
August 6, 2026 17:49
42e67e3 to
9bb1666
Compare
wpfleger96
added a commit
that referenced
this pull request
Aug 7, 2026
…orcement states, feedback status, staffing tab Implement Plan v4 Phase 3 for the desktop admin console panel (#4768). ## Probe - AdminProbeResult::Nip98Authorized now carries optional role and source fields (Rust enum variant updated to struct variant). - AdminConsoleSettingsCard propagates role/source from probe result to AdminConsolePanel; AdminConsolePanel renders a role+source badge strip when role is present. ## Report actions (frozen v4 matrix) - Event reports: delete/kick/ban/timeout/dismiss/escalate - Pubkey reports: ban/timeout/dismiss/escalate - Blob reports: dismiss/escalate - ResolveReportForm generates a client UUID request_id per submission attempt (v4 §6a amendment 2); 409/processing errors preserve the request_id for retry idempotency. - Timeout action shows a duration (expiration_secs) input; submit is disabled until a value is provided. ## Enforcement states - processing reports are disabled (non-actionable) in the list with a spinner. - EnforcementStateBlock renders pending/enforcing/succeeded/failed states. - Failed actions surface Retry (reuses same request_id) and Cancel (dismiss with fresh request_id; server-rejected cancel treated as authoritative). ## Feedback status - FeedbackStatusControl: new/reviewed/archived PATCH buttons with optimistic local-state sync; server error surfaces inline. - FeedbackTab list shows non-new status as a badge. ## Staffing tab - Operator-only (gated by role === 'operator' from probe). - SourceBadge distinguishes config/owner_fallback (immutable) from db. - Config-backed operator rows have disabled remove buttons with title explaining why. - PUT 409 (config-backed add conflict) and DELETE 409 surfaced clearly. ## File structure AdminConsolePanel.tsx split into four files to satisfy the 1000-line ratchet (all new files under the limit): - AdminConsolePanelHelpers.tsx: AsyncState, useAsyncLoad, formatTimestamp, DetailRow, LoadingSpinner, ErrorMessage, AttachmentMeta, parseImetaAttachments - AdminConsoleFeedbackTab.tsx: FeedbackTab, FeedbackDetail, and related sub-components - AdminConsoleStaffingTab.tsx: StaffingTab, SourceBadge - AdminConsolePanel.tsx: ReportsTab, ReportDetail, report action components, TabBar, AdminConsolePanel root src-tauri/src/commands/admin/helpers.rs extracted from mod.rs to keep mod.rs under 1000 lines. ## Tests - 7 new tests: probe-role-source-badge, probe-moderator-role, probe-operator-role, probe-no-role, processing-report-not-actionable, action-matrix-types, plus reportButton.disabled assertion. - All 4511 TS tests pass; all 12 jsdom tests pass; Rust compiles clean; desktop-check, desktop-tauri-check, desktop-tauri-test all green. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
wpfleger96
force-pushed
the
wpfleger/desktop-admin-surface
branch
from
August 11, 2026 00:04
fa29ae1 to
99527b1
Compare
Add a NIP-98 client for the /api/admin/v1 relay API, surfaced as an "Admin console" section in Settings. Relay operators view deployment-wide moderation reports and product feedback, resolve/dismiss reports, update feedback status, and manage the operator/moderator staffing roster — no browser extension or bearer token required. The console auto-discovers the relay's admin origin from its NIP-11 document (admin_api field): on mount it fetches the connected relay's relay-information document, validates the advertised origin through AdminOrigin::parse, and auto-probes. The manual origin field remains as a pre-filled fallback for relays that do not advertise. Rust: - AdminOrigin value object: validates scheme+host+optional-port, rejects credentials/path/query/fragment; http:// only for loopback hosts - AdminRoute closed enum: no IPC surface accepts arbitrary URLs or paths; the signed URL is byte-identical to the fetched URL - Dedicated no-redirect reqwest client (SSRF guard: relay 3xx surfaced as error, NIP-98 header never forwarded across origins) - admin_probe: typed state enum (Nip98Authorized/Denied, TokenMode, Disabled, NotAdminApi, NetworkOrIntercepted); Nip98Authorized only on authenticated 2xx - NIP-11 admin-origin discovery command; advertised value treated as untrusted input and revalidated before use - NIP-98 signing via AppState::signing_keys() (Err in recovery mode); one retry on 401 with a fresh event - Response bounds enforced by Content-Length preflight and streaming byte counter; per-pubkey origin storage (atomic write, 0o600) TypeScript: - admin-console API wrappers for all Tauri commands; attachments return a Blob URL from caller-supplied MIME - AdminConsoleSettingsCard: auto-discovery + probe flow, per-pubkey state, honest copy for every probe state - AdminConsolePanel with Reports / Feedback / Staffing tabs - Settings panels split under the file-size ratchet Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
wpfleger96
force-pushed
the
wpfleger/desktop-admin-surface
branch
from
August 11, 2026 00:09
99527b1 to
6bd27a2
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds an in-app admin console for relay operators, surfaced as an "Admin console" section under Settings. Operators view deployment-wide moderation reports and product feedback, resolve or dismiss reports, update feedback status, and manage the operator/moderator staffing roster — all authenticated with the operator's own Nostr key over NIP-98, with no browser extension or bearer token.
Discovery and connection
admin_apifield to auto-discover the admin origin — zero manual input on the happy path.AdminOrigin::parsebefore use, and an absent or invalid value falls back to a manual origin field (pre-filled when discovery succeeds).Auth and transport (Rust,
src-tauri/src/commands/admin/)AdminOriginvalue object: validates scheme + host + optional port, rejects credentials, path, query, and fragment;http://only for loopback hosts,https://otherwise.AdminRouteclosed enum: no IPC surface accepts arbitrary URLs or paths, so the NIP-98-signed URL is byte-identical to the fetched URL.reqwestclient as an SSRF guard: a relay3xxis surfaced as an error and the NIP-98 header is never forwarded across origins.admin_probereturns a typed state (Nip98Authorized/Denied,TokenMode,Disabled,NotAdminApi,NetworkOrIntercepted);Nip98Authorizedonly on an authenticated2xx.AppState::signing_keys()(errors cleanly in recovery mode), with one retry on401using a fresh signed event.Content-Lengthpreflight and a streaming byte counter; per-pubkey origin storage with atomic0o600writes.Console UI (TypeScript,
src/features/admin-console/)AdminConsoleSettingsCard: discovery + probe flow, per-pubkey state, role and source badges when authorized, honest copy for every probe state.AdminConsolePanelwith Reports / Feedback / Staffing tabs (Staffing gated onrole === "operator").target_kind(event → delete/kick/ban/timeout/dismiss/escalate; pubkey → ban/timeout/dismiss/escalate; blob → dismiss/escalate). Enforcement state rendersprocessingas not-actionable andpending/enforcing/succeeded/failedwith retry; cancel is offered only pre-mutation, and a rejected cancel is treated as server-authoritative. Each attempt carries a client-generatedrequest_id, reused on retry after a lost response.new/reviewed/archived) with a generation-fenced attachment viewer (image/*auto-loads; other MIME types load on demand).config/owner_fallback/dbsource badges; config-backed entries disable remove client-side and surface a409conflict as a human-readable message.Settings refactor
SettingsPanels.tsxsplit along panel boundaries (ThemeSettingsCard.tsxextracted) to stay under the 1000-line file-size ratchet; the PTT global-shortcut plugin builder is extracted fromlib.rsfor the same reason. Mechanical, no behavior change.UI invariants
processingreports are never presented as actionable.resolvedreport, never as an enforcement failure.tokenanddisabledauth modes render read-only, with no action or staffing capabilities.Related: #3777 (relay OPERATOR/MODERATOR role model + NIP-98 auth +
admin_apiNIP-11 advertisement — provides the runtime and the discovery field this console consumes)Related: bb-public#339 (Phase 4 rollout config)