Documentation
¶
Overview ¶
Admin dashboard: server-rendered page at /admin for the relay owner to tune every config knob live via NIP-86.
The gate here is the cookie session, not NIP-98 — NIP-98 is what the dispatcher uses for the per-action grain_* writes the page issues from the browser. We render the shell only if the cookie-session pubkey matches the relay_metadata.json owner; non-owners get a 303 to "/" with no content leak.
Lives in the client package (not server/api) because rendering goes through RenderTemplate, which is defined here. Putting the handler in server/api would create an import cycle.
Owner-gated proxy that lets the admin dashboard preview the keys at a domain's .well-known/nostr.json without giving the browser direct cross-origin fetch access. Used by the whitelist section to show "this domain admits N keys: name1 hex1, name2 hex2…" after an operator adds a domain.
Curated table of well-known Nostr event kinds → human-readable labels. Sourced from the Event Kinds section of github.com/nostr-protocol/nips/blob/master/README.md.
Used by the admin dashboard's list-of-kinds inputs (event_purge's kinds_to_purge today; whitelist/blacklist kind lists later) so an operator sees "7 — Reaction (NIP-25)" instead of a bare integer.
This is intentionally not exhaustive — only labels we can stand behind. An operator can still add any non-negative integer; the UI just shows "(no description)" for kinds not in this table. Update by reading the upstream README, not by inventing labels.
client/registerEndpoints.go
First-run owner provisioning. /setup is the click-through claim page for operators who haven't set GRAIN_OWNER_PUBKEY in their env.
Security model (deliberately tiny): first POST to /setup while the relay is unowned wins. A signed NIP-98 envelope would add nothing — before ownership exists the relay has no notion of "which pubkeys may claim." The cost of losing the race is "redeploy a fresh relay" so the loud red banner + the post-claim "claimed by <npub>" page are the operator's safety net. See plans/floating-imagining-tome.md.
Index ¶
- Variables
- func GetCoreClient() interface{}
- func GetEmbeddedWWW() fs.FS
- func GetSessionStats() map[string]interface{}
- func HandleAdmin(w http.ResponseWriter, r *http.Request)
- func HandleAdminDomainKeys(w http.ResponseWriter, r *http.Request)
- func HandleSetup(w http.ResponseWriter, r *http.Request)
- func InitializeClient(ctx context.Context, serverCfg *cfgType.ServerConfig) error
- func KindLabel(k int) string
- func RegisterEndpoints(mux *http.ServeMux)
- func RegisterPWARoutes(mux *http.ServeMux)
- func RenderTemplate(w http.ResponseWriter, data PageData, view string)
- func SetAssetVersion(v string)
- func SetEmbeddedWWW(fsys fs.FS)
- func ShutdownClient() error
- type AdminPageData
- type AdminSection
- type BlacklistSectionData
- type ClientInitError
- type EventPurgeSectionData
- type KindRatePreset
- type KindSizePreset
- type LoggingSectionData
- type OpsSectionData
- type PWAIcon
- type PWAManifest
- type PWAScreenshot
- type PageData
- type QuickKind
- type RateLimitSectionData
- type SetupPageData
- type UnifiedPubkey
- type WhitelistSectionData
Constants ¶
This section is empty.
Variables ¶
var KindLabels = map[int]string{
0: "User Metadata (NIP-01)",
1: "Short Text Note (NIP-01)",
2: "Recommend Relay (deprecated)",
3: "Follow List (NIP-02)",
4: "Encrypted Direct Message (NIP-04, deprecated)",
5: "Event Deletion Request (NIP-09)",
6: "Repost (NIP-18)",
7: "Reaction (NIP-25)",
8: "Badge Award (NIP-58)",
9: "Chat Message (NIP-C7)",
13: "Seal (NIP-59)",
14: "Direct Message (NIP-17)",
15: "File Message (NIP-17)",
16: "Generic Repost (NIP-18)",
17: "Reaction to a Website (NIP-25)",
20: "Picture-First Feed (NIP-68)",
21: "Video Event (NIP-71)",
22: "Short-form Portrait Video (NIP-71)",
40: "Channel Creation (NIP-28)",
41: "Channel Metadata (NIP-28)",
42: "Channel Message (NIP-28)",
43: "Channel Hide Message (NIP-28, deprecated)",
44: "Channel Mute User (NIP-28, deprecated)",
1059: "Gift Wrap (NIP-59)",
1063: "File Metadata (NIP-94)",
1311: "Live Chat Message (NIP-53)",
1984: "Reporting (NIP-56)",
1985: "Label (NIP-32)",
9734: "Zap Request (NIP-57)",
9735: "Zap Receipt (NIP-57)",
10000: "Mute List (NIP-51)",
10001: "Pin List (NIP-51)",
10002: "Relay List Metadata (NIP-65)",
10003: "Bookmark List (NIP-51)",
10004: "Communities List (NIP-51)",
10005: "Public Chats List (NIP-51)",
10006: "Blocked Relays List (NIP-51)",
10007: "Search Relays List (NIP-51)",
10015: "Interests List (NIP-51)",
10030: "User Emoji List (NIP-51)",
30000: "Follow Sets (NIP-51)",
30002: "Relay Sets (NIP-51)",
30003: "Bookmark Sets (NIP-51)",
30004: "Curation Sets (NIP-51)",
30008: "Profile Badges (NIP-58)",
30009: "Badge Definition (NIP-58)",
30015: "Interest Sets (NIP-51)",
30017: "Stall (NIP-15)",
30018: "Product (NIP-15)",
30023: "Long-form Content (NIP-23)",
30024: "Draft Long-form Content (NIP-23)",
30030: "Emoji Sets (NIP-51)",
30078: "Application-specific Data (NIP-78)",
30311: "Live Event (NIP-53)",
30315: "User Statuses (NIP-38)",
30402: "Classified Listing (NIP-99)",
30403: "Draft Classified Listing (NIP-99)",
31922: "Date-Based Calendar Event (NIP-52)",
31923: "Time-Based Calendar Event (NIP-52)",
31924: "Calendar (NIP-52)",
31925: "Calendar Event RSVP (NIP-52)",
31989: "Handler Recommendation (NIP-89)",
31990: "Handler Information (NIP-89)",
34550: "Community Definition (NIP-72)",
}
KindLabels maps kind → "Display Name (NIP-XX)" or "Display Name (deprecated)" where applicable. Keep ordered by kind for diffability; ranges (10000+) are NOT enumerated because most are addressable / replaceable buckets without specific well-known assignments.
Functions ¶
func GetCoreClient ¶
func GetCoreClient() interface{}
GetCoreClient returns the core client instance for advanced usage
func GetEmbeddedWWW ¶ added in v0.5.0
GetEmbeddedWWW returns the embedded www filesystem.
func GetSessionStats ¶
func GetSessionStats() map[string]interface{}
GetSessionStats returns current session statistics
func HandleAdmin ¶ added in v0.7.0
func HandleAdmin(w http.ResponseWriter, r *http.Request)
HandleAdmin renders the dashboard for the relay owner only.
Gate: session cookie -> SessionMgr.GetCurrentUser -> compare to GetRelayOwnerPubkey (case-insensitive). Non-owner / no session -> 303 redirect to "/".
func HandleAdminDomainKeys ¶ added in v0.7.0
func HandleAdminDomainKeys(w http.ResponseWriter, r *http.Request)
HandleAdminDomainKeys returns the parsed names map for a single domain's .well-known/nostr.json. Owner-gated via the cookie session — same gate /admin uses — so an unauthenticated visitor can't use grain as a SSRF proxy.
func HandleSetup ¶ added in v0.7.0
func HandleSetup(w http.ResponseWriter, r *http.Request)
HandleSetup renders the first-run claim page (GET) or processes a claim attempt (POST). See package doc for the threat-model rationale.
func InitializeClient ¶
func InitializeClient(ctx context.Context, serverCfg *cfgType.ServerConfig) error
InitializeClient sets up the client package with server configuration. ctx bounds the background goroutines (session cleanup, cache cleanup, relay health check) to the lifetime of the server instance that started them, so a config-reload restart cancels them instead of leaking a fresh set (#93).
func KindLabel ¶ added in v0.7.0
KindLabel returns the human-readable label for a known kind, or "" if the kind isn't in the table. Callers that want a fallback string should `if l := KindLabel(k); l != "" { ... }`.
func RegisterEndpoints ¶
RegisterEndpoints registers all endpoints on the given mux
func RegisterPWARoutes ¶
RegisterPWARoutes registers PWA-related routes
func RenderTemplate ¶
func RenderTemplate(w http.ResponseWriter, data PageData, view string)
RenderTemplate renders a template with the standard layout using the embedded FS
func SetAssetVersion ¶ added in v0.7.0
func SetAssetVersion(v string)
SetAssetVersion wires the build version in so {{assetVersion}} in the templates resolves to it. Called from main once the version is known.
func SetEmbeddedWWW ¶ added in v0.5.0
SetEmbeddedWWW sets the embedded filesystem containing www/ static assets. The FS should have "www" as the root prefix (e.g., files at "www/views/app.html").
func ShutdownClient ¶
func ShutdownClient() error
ShutdownClient gracefully shuts down the client package
Types ¶
type AdminPageData ¶ added in v0.7.0
type AdminPageData struct {
Title string
Theme string
Owner string
Sections []AdminSection
KindLabels map[int]string
}
AdminPageData is what admin.html renders against.
type AdminSection ¶ added in v0.7.0
type AdminSection struct {
ID string
Title string
Icon string
Method string // grain_* write method this section targets (empty for ops)
Config any
}
AdminSection is one panel in the accordion. Config is the typed config blob (or nil for ops) — the template renders it inside the stub <pre> in Phase 1, and Phase 2+ commits read individual fields off the typed struct as they replace each stub with a real form.
type BlacklistSectionData ¶ added in v0.7.0
type BlacklistSectionData struct {
Config cfgType.BlacklistConfig
UnifiedPubkeys []UnifiedPubkey
BrokenPubkeys []string // bad entries from blacklist.yml's pubkeys/npubs
MutelistAuthors []UnifiedPubkey
BrokenMutelistAuthors []string
// Owner's private mute list sync status (#60). Rendered so the owner
// knows when they last synced and how many pubkeys it contributed.
// Zero values mean "never synced".
OwnerMutelistCount int
OwnerMutelistSynced int64 // unix seconds; 0 = never
// The owner's synced private-mute pubkeys, for in-panel display with
// profiles. Owner-only page, so showing the actual keys is fine here
// (the public endpoint stays count-only).
OwnerMutelistPubkeys []UnifiedPubkey
}
BlacklistSectionData wraps BlacklistConfig with the same unified pubkey treatment the whitelist gets: hex + npub merge into one display list, mutelist authors render with profile previews. IP scalars ride through the bulk save now that UpdateBlacklistConfig writes them to config.yml. The IP LIST is edited live (per-row blockip/unblockip) rather than via the section Save, so it has no snapshot-replay path.
type ClientInitError ¶
type ClientInitError struct {
Message string
}
ClientInitError represents initialization errors
func (*ClientInitError) Error ¶
func (e *ClientInitError) Error() string
type EventPurgeSectionData ¶ added in v0.7.0
type EventPurgeSectionData struct {
Config cfgType.EventPurgeConfig
Categories []string
CommonKinds []QuickKind
// KindLabels duplicated here from the page-level data because
// Go templates lose access to the outer dot once a sub-template
// is invoked. Cheap to pass — it's a single map reference.
KindLabels map[int]string
}
EventPurgeSectionData is the per-section template data for the event_purge form. The form renders one checkbox per known purge category (the v0.4-compat names from server/db/nostrdb/purge.go:purgeCategoryForKind); rather than teach the template to construct a literal slice, we hand it the list directly. CommonKinds drives the quick-add chip row above the kinds_to_purge textarea.
type KindRatePreset ¶ added in v0.7.0
KindRatePreset is one suggested kind→{limit, burst} on the per-kind rate quick-add row.
type KindSizePreset ¶ added in v0.7.0
KindSizePreset is one suggested kind→max_size pairing on the per-kind size limits quick-add row. Sizes here are operator- friendly defaults — operators can edit before clicking Add.
type LoggingSectionData ¶ added in v0.7.0
LoggingSectionData is the per-section template data for the logging form. We can't render the suppress-components UI from just LogConfig — the form needs the full set of known component names (so an operator gets checkboxes instead of typing names they have to guess), and that catalog lives in server/utils/log/components.go. Bundling the two here keeps the template clean and the catalog discoverable.
type OpsSectionData ¶ added in v0.7.0
type OpsSectionData struct {
RelayName string
RelayDescription string
RelayIcon string
RelayBanner string
RelayContact string
RelayPrivacyPolicy string
RelayTermsOfService string
RelayPostingPolicy string
}
OpsSectionData is the per-section template data for ops. The dashboard renders the current relay identity (name/description/ icon/banner/contact + policy URLs) so an operator can edit them in place, plus a stats area fetched live via grain_stats_overview. Cache refresh and config reload run via separate buttons.
type PWAIcon ¶
type PWAIcon struct {
Src string `json:"src"`
Sizes string `json:"sizes"`
Type string `json:"type"`
Purpose string `json:"purpose,omitempty"`
}
PWAIcon represents an icon in the manifest
type PWAManifest ¶
type PWAManifest struct {
Name string `json:"name"`
ShortName string `json:"short_name"`
Description string `json:"description"`
StartURL string `json:"start_url"`
Display string `json:"display"`
BackgroundColor string `json:"background_color"`
ThemeColor string `json:"theme_color"`
Orientation string `json:"orientation"`
Scope string `json:"scope"`
Lang string `json:"lang"`
Categories []string `json:"categories"`
Screenshots []PWAScreenshot `json:"screenshots,omitempty"`
Icons []PWAIcon `json:"icons"`
}
PWAManifest represents the web app manifest structure
type PWAScreenshot ¶
type PWAScreenshot struct {
Src string `json:"src"`
Sizes string `json:"sizes"`
Type string `json:"type"`
FormFactor string `json:"form_factor,omitempty"`
Label string `json:"label,omitempty"`
}
PWAScreenshot represents a screenshot in the manifest
type RateLimitSectionData ¶ added in v0.7.0
type RateLimitSectionData struct {
Config cfgType.RateLimitConfig
RateLimitCategories []string
CategoryDefaults map[string]map[string]float64 // category → {limit, burst}
KindSizePresets []KindSizePreset
KindRatePresets []KindRatePreset
}
RateLimitSectionData is the per-section template data for the rate_limit form. Carries the typed config plus reference catalogs the form needs (category list for deterministic order, suggested per-kind size and rate presets for the quick-add chip rows).
type SetupPageData ¶ added in v0.7.0
SetupPageData feeds setup.html (claim form) and setup-claimed.html (already-owned info panel). OwnerNpub is empty in the unowned state; populated with the bech32 form when rendering the claimed panel so the operator can eyeball whether the claimant is them.
type UnifiedPubkey ¶ added in v0.7.0
UnifiedPubkey is one row of the merged hex+npub display. Both forms are precomputed server-side so the page doesn't have to fan out N convert-API calls on first render.
type WhitelistSectionData ¶ added in v0.7.0
type WhitelistSectionData struct {
Config cfgType.WhitelistConfig
UnifiedPubkeys []UnifiedPubkey
BrokenPubkeys []string // raw entries from the YAML that didn't parse
KindLabels map[int]string
KindPresets []QuickKind
}
WhitelistSectionData wraps WhitelistConfig with a unified pubkey view + the kind catalog needed by the form. The dashboard renders one row per UnifiedPubkey showing both hex and npub regardless of which form the operator originally entered; the wire shape (Pubkeys vs Npubs) is collapsed on save into Pubkeys-only by the JS submit path.