Documentation
¶
Index ¶
- Constants
- Variables
- type AgentChannel
- type Bookmark
- type Config
- type Connector
- type ConnectorOperation
- type ConnectorRun
- type ConnectorRunSource
- type ConnectorRunStatus
- type HealthComponent
- type HealthState
- type Job
- type JobRun
- type JobStatus
- type OAuthAuthorizationCode
- type OAuthClient
- type OAuthToken
- type PersonalAccessToken
- type RunStatus
- type RunTrigger
- type SSOProvider
- type Session
- type Tag
- type ToolPermission
- type ToolTag
- type ToolVisibility
- type User
- type UserMetadata
- type UserRole
- type UserTag
Constants ¶
const ( VisibilityPublic = pkgentity.VisibilityPublic VisibilityPrivate = pkgentity.VisibilityPrivate )
const ( HomeViewCompact = "compact" HomeViewDetailed = "detailed" )
const SSOProviderGoogle = "google"
SSOProviderGoogle is the provider key for Google OAuth.
Variables ¶
var StructToConfigs = pkgentity.StructToConfigs
StructToConfigs is re-exported from pkg/entity — see that package for the tag grammar and reflection rules.
Functions ¶
This section is empty.
Types ¶
type AgentChannel ¶ added in v0.9.0
type AgentChannel struct {
ID string `gorm:"primaryKey;type:varchar(64)"`
Type string `gorm:"type:varchar(32);not null;index"` // "slack", "telegram"
Name string `gorm:"type:varchar(128);not null;default:'default'"`
Enabled bool `gorm:"not null;default:true"`
Config string `gorm:"type:text;not null;default:'{}'"`
CreatedAt time.Time
UpdatedAt time.Time
}
AgentChannel is one configured channel instance (Slack, Telegram, etc.). Config holds a JSON blob of channel-specific credentials and settings. Multiple instances of the same type can coexist (e.g., two Slack bots for different teams) distinguished by Name.
func (AgentChannel) TableName ¶ added in v0.9.0
func (AgentChannel) TableName() string
type Bookmark ¶
type Bookmark struct {
UserID string `gorm:"primaryKey;type:varchar(36)"`
ToolPath string `gorm:"primaryKey;type:varchar(255)"`
CreatedAt time.Time
}
Bookmark marks a tool as a favorite for a specific user. Bookmarked tools appear in a dedicated "Bookmarks" group on the home page in addition to any group/category they already belong to.
type Config ¶
Config is re-exported from pkg/entity so existing internal callers keep working. Module authors should import pkg/entity directly.
type Connector ¶ added in v0.4.0
type Connector struct {
ID string `gorm:"type:varchar(36);primaryKey"`
Key string `gorm:"type:varchar(100);index;not null"`
Label string `gorm:"type:varchar(255);not null"`
Disabled bool `gorm:"default:false"`
// RateLimitRPM caps how many times this connector instance may be
// called per minute across all users. 0 means unlimited. Enforced
// in-process via a sliding-window counter — not distributed.
RateLimitRPM int `gorm:"default:0"`
CreatedBy string `gorm:"type:varchar(36)"`
CreatedAt time.Time
UpdatedAt time.Time
}
Connector is one row per running connector — the runtime pairing of a code-registered connector definition (identified by Key) with a credential set, label, and creator.
Connector definitions live in code (see pkg/connector and the internal/connectors registry); this entity is what the admin UI reads, writes, and duplicates. MCP exposes one tool per Connector row per enabled operation.
Key references the code definition's slug (e.g. "loki", "github") — it is NOT unique on this table. Multiple Connector rows share the same Key when admins duplicate a definition into multiple instances (Loki Prod, Loki Staging, Loki Dev). Uniqueness lives on ID; the admin UI distinguishes siblings by Label.
Code-registered Modules survive deletion: when bootstrap runs and finds zero rows for a registered Key, it auto-creates a fresh row (empty Configs, Label = Meta.Name). Admins who delete every row for a connector therefore get an empty-but-working row back on restart; duplicates and edits to existing rows are untouched.
Per-field credential / endpoint values live on the central configs table (owner = "connector:{id}"), one row per field declared on the connector's Creds struct. Wick reflects the typed Creds into rows at boot via entity.StructToConfigs and reconciles them on every connector instance create. Reading credentials goes through connectors.Service.LoadConfigs; the configs.Service cache makes the per-field shape as cheap as a JSON unmarshal but keeps each value query-able and individually editable.
Disabled hides the row from MCP tools/list and the admin UI list view (admins can re-enable from the manager). The tag-filter system (the existing ToolTag table, addressed by path "/connectors/{id}", joined against UserTag) gates which authenticated users see this row at all — Disabled is the orthogonal "off switch" for the whole row.
Tag association reuses ToolTag (with ToolPath = "/connectors/{id}") rather than introducing a connector-specific link table; jobs do the same with "/jobs/{path}". A future rename of ToolTag/SetToolTags into a generic entity-tag API is tracked separately.
type ConnectorOperation ¶ added in v0.4.0
type ConnectorOperation struct {
ConnectorID string `gorm:"primaryKey;type:varchar(36)"`
OperationKey string `gorm:"primaryKey;type:varchar(100)"`
Enabled bool `gorm:"default:true"`
// AdminOnly restricts this operation to admin users only. Non-admin
// MCP callers receive a 403-equivalent error before Execute runs.
// Default: false (all authenticated users may call the operation).
AdminOnly bool `gorm:"default:false"`
// SystemDisabled is set by the health-check mechanism when the
// configured credential lacks the upstream permissions an operation
// needs (e.g. missing OAuth scope). It is orthogonal to Enabled —
// effective availability is `Enabled AND NOT SystemDisabled`. The
// admin UI locks the manual Enable/Disable toggle while this is
// true; the only way to clear it is to fix the upstream permission
// and re-run the health check. Default: false.
SystemDisabled bool `gorm:"default:false"`
// SystemDisabledReason is the human-readable explanation surfaced
// alongside the lock — e.g. "needs scope: chat:write". Empty when
// SystemDisabled is false.
SystemDisabledReason string `gorm:"type:text"`
UpdatedAt time.Time
}
ConnectorOperation stores the enable state of one operation on one connector row. Operations are declared in code (Module.Operations); this table records whether the admin opted them in (or out) for a specific connector row.
Default rule applied when a connector row is created:
- Operation.Destructive == false → Enabled = true
- Operation.Destructive == true → Enabled = false (admin opt-in)
Rows for ops the admin has not touched yet may be missing; readers fall back to the default rule above when no row exists. Toggling in the UI inserts or updates a row.
type ConnectorRun ¶ added in v0.4.0
type ConnectorRun struct {
ID string `gorm:"type:varchar(36);primaryKey"`
ConnectorID string `gorm:"type:varchar(36);not null;index:idx_run_connector_started,priority:1"`
OperationKey string `gorm:"type:varchar(100);not null"`
UserID string `gorm:"type:varchar(36);index:idx_run_user_started,priority:1"`
Source ConnectorRunSource `gorm:"type:varchar(20);not null"`
RequestJSON string `gorm:"type:text"`
ResponseJSON string `gorm:"type:text"`
Status ConnectorRunStatus `gorm:"type:varchar(20);not null;index:idx_run_status_started,priority:1"`
ErrorMsg string `gorm:"type:text"`
LatencyMs int
HTTPStatus int
IPAddress string `gorm:"type:varchar(45);index:idx_run_ip_started,priority:1"`
UserAgent string `gorm:"type:varchar(512)"`
ParentRunID *string `gorm:"type:varchar(36);index"`
StartedAt time.Time `` /* 218-byte string literal not displayed */
EndedAt *time.Time
CreatedAt time.Time
}
ConnectorRun records one execution of one operation on one connector row. Written once per MCP tools/call, panel-test click, or retry, so admins can audit traffic, debug failures, and replay buggy calls.
RequestJSON stores the input arguments the caller (LLM or admin) passed in. Credentials are NOT in this column — they live on the Connector row itself, joined at exec time. Replaying a run rebuilds the call from RequestJSON + the current Connector.Configs, so a retry honors any cred edits the admin has made since the original.
ResponseJSON is the JSON-marshaled return value of ExecuteFunc. Large responses may be truncated by the writer (defense in depth); readers should treat the value as opaque.
IPAddress and UserAgent capture the calling client's network identity at the time of the run. These are recorded for security observability — feeding a future allowlist/blocklist surface — and for incident triage. They are best-effort: behind a proxy the IP is whatever X-Forwarded-For policy the deploy resolves to, and a PAT-using script may not send a recognizable UA at all.
ParentRunID is non-nil only when Source == ConnectorRunSourceRetry, pointing to the run this one re-played. There is no FK constraint — the parent may be deleted by retention, leaving the lineage dangling (the UI tolerates the gap).
Retention: rows older than the configured retention window are purged by a scheduled cleanup job (default 7 days). The single-column index on StartedAt keeps the purge query cheap.
Index strategy (composite, listed by query they serve):
- (connector_id, started_at DESC) → "recent runs for this connector"
- (user_id, started_at DESC) → "user activity timeline"
- (status, started_at DESC) → "recent errors" filter
- (ip_address, started_at DESC) → "activity from this IP" (future allow/block UX)
- started_at → retention purge
- parent_run_id → retry lineage trace
func (*ConnectorRun) BeforeCreate ¶ added in v0.4.0
func (r *ConnectorRun) BeforeCreate(tx *gorm.DB) error
type ConnectorRunSource ¶ added in v0.4.0
type ConnectorRunSource string
ConnectorRunSource describes how a ConnectorRun was triggered.
const ( // ConnectorRunSourceMCP marks runs initiated by an LLM client through // the /mcp endpoint. ConnectorRunSourceMCP ConnectorRunSource = "mcp" // ConnectorRunSourceTest marks runs initiated from the panel-test // view in the admin UI (Postman-style manual exec). ConnectorRunSourceTest ConnectorRunSource = "test" // ConnectorRunSourceRetry marks runs that replay the request payload // of a previous run, identified by ConnectorRun.ParentRunID. ConnectorRunSourceRetry ConnectorRunSource = "retry" )
type ConnectorRunStatus ¶ added in v0.4.0
type ConnectorRunStatus string
ConnectorRunStatus describes the outcome of a ConnectorRun.
const ( ConnectorRunStatusRunning ConnectorRunStatus = "running" ConnectorRunStatusSuccess ConnectorRunStatus = "success" ConnectorRunStatusError ConnectorRunStatus = "error" )
type HealthComponent ¶
type HealthComponent struct {
Database HealthState `json:"database"`
}
type HealthState ¶
type HealthState string
const ( HealthStateOK HealthState = "ok" HealthStateFail HealthState = "fail" )
type Job ¶
type Job struct {
ID string `gorm:"type:varchar(36);primaryKey"`
Key string `gorm:"type:varchar(100);uniqueIndex;not null"`
Name string `gorm:"type:varchar(255);not null"`
Description string `gorm:"type:text"`
Icon string `gorm:"type:varchar(10)"`
Schedule string `gorm:"type:varchar(100)"` // cron expression
Enabled bool `gorm:"default:false"`
MaxRuns int `gorm:"default:0"` // 0 = unlimited, admin-managed
TotalRuns int `gorm:"default:0"`
LastStatus JobStatus `gorm:"type:varchar(20);default:'idle'"`
LastRunAt *time.Time
CreatedBy string `gorm:"type:varchar(36)"`
CreatedAt time.Time
UpdatedAt time.Time
}
Job is a background job definition whose schedule and lifecycle are managed via the DB. Code-defined jobs bootstrap a row on startup; admins can tweak the cron expression, enable/disable, and cap the run count.
type JobRun ¶
type JobRun struct {
ID string `gorm:"type:varchar(36);primaryKey"`
JobID string `gorm:"type:varchar(36);index;not null"`
Status RunStatus `gorm:"type:varchar(20);not null"`
Result string `gorm:"type:text"`
TriggeredBy RunTrigger `gorm:"type:varchar(20);not null"`
UserID string `gorm:"type:varchar(36)"`
StartedAt time.Time
EndedAt *time.Time
CreatedAt time.Time
}
JobRun stores the result of a single execution of a Job.
type OAuthAuthorizationCode ¶ added in v0.4.0
type OAuthAuthorizationCode struct {
ID string `gorm:"type:varchar(36);primaryKey"`
Code string `gorm:"type:varchar(64);uniqueIndex;not null"`
ClientID string `gorm:"type:varchar(64);index;not null"`
UserID string `gorm:"type:varchar(36);not null"`
RedirectURI string `gorm:"type:varchar(512);not null"`
Scope string `gorm:"type:varchar(255)"`
CodeChallenge string `gorm:"type:varchar(128);not null"`
CodeChallengeMethod string `gorm:"type:varchar(10);not null"`
Used bool `gorm:"default:false"`
ExpiresAt time.Time
CreatedAt time.Time
}
OAuthAuthorizationCode is the short-lived PKCE authorization code minted at /oauth/authorize and consumed at /oauth/token.
Code is the opaque string sent in the redirect query string. It's indexed for the lookup at /token but not unique — we let the database flag duplicates if two requests collide (statistically near impossible with 32 random bytes).
CodeChallenge / Method come from the original /authorize request; /token verifies the client's code_verifier against them per RFC 7636.
Used flips to true on first /token redemption to prevent replay. We keep the row for audit instead of deleting; PurgeExpired sweeps later.
func (*OAuthAuthorizationCode) BeforeCreate ¶ added in v0.4.0
func (c *OAuthAuthorizationCode) BeforeCreate(tx *gorm.DB) error
func (OAuthAuthorizationCode) TableName ¶ added in v0.4.0
func (OAuthAuthorizationCode) TableName() string
type OAuthClient ¶ added in v0.4.0
type OAuthClient struct {
ID string `gorm:"type:varchar(36);primaryKey"`
ClientID string `gorm:"type:varchar(64);uniqueIndex;not null"`
Name string `gorm:"type:varchar(255)"`
RedirectURIs string `gorm:"type:text;not null"` // JSON array
CreatedBy string `gorm:"type:varchar(36)"`
CreatedAt time.Time
}
OAuthClient is one Dynamic Client Registration record (RFC 7591).
MCP clients (Claude.ai web, Claude Desktop, Cursor) call POST /oauth/register without prior coordination, hand wick a name + redirect_uris, and receive back the ClientID. There is no client secret in this flow — every client is treated as a public client using PKCE per RFC 7636 (which the MCP authorization spec mandates).
RedirectURIs is a JSON-encoded array of allowed redirect URIs; /oauth/authorize verifies the requested URI is one of them. We store as JSON to keep gorm migrations simple — the list is small and read whole every time.
CreatedBy is non-nil only when the registration happened while a user was logged in (rare — DCR usually fires before any wick session exists). Useful for admin auditing.
func (*OAuthClient) BeforeCreate ¶ added in v0.4.0
func (c *OAuthClient) BeforeCreate(tx *gorm.DB) error
func (OAuthClient) TableName ¶ added in v0.4.0
func (OAuthClient) TableName() string
TableName pins the table name. GORM's default naming would lower- case + snake_case "OAuth" into "o_auth", giving "o_auth_clients". We override so raw SQL in oauth.Repo.ListGrantsByUser stays readable ("oauth_clients", not "o_auth_clients").
type OAuthToken ¶ added in v0.4.0
type OAuthToken struct {
ID string `gorm:"type:varchar(36);primaryKey"`
TokenHash string `gorm:"type:varchar(64);uniqueIndex;not null"`
Kind string `gorm:"type:varchar(10);not null"` // access | refresh
ClientID string `gorm:"type:varchar(64);index;not null"`
UserID string `gorm:"type:varchar(36);index;not null"`
Scope string `gorm:"type:varchar(255)"`
ParentTokenID *string `gorm:"type:varchar(36);index"` // refresh chain ancestor
ExpiresAt time.Time
RevokedAt *time.Time `gorm:"index"`
LastUsedAt *time.Time
CreatedAt time.Time
}
OAuthToken is one issued access or refresh token. Stored opaque (32 hex chars), hashed at rest just like PersonalAccessToken — the plaintext only crosses the wire on the /token response.
Kind is "access" or "refresh". A code redemption mints both: the access has a short TTL (~1h), the refresh a long one (~30d) and is rotated on every use (RFC 6749 §6 + best-current-practice).
ParentTokenID chains refresh-token rotation: when a refresh redeems, the new refresh row carries the previous row's ID here. RevokedAt stamps a row when its child is minted, so reuse of an old refresh is detectable (and the whole chain should be revoked — a sign the token was leaked).
func (*OAuthToken) BeforeCreate ¶ added in v0.4.0
func (t *OAuthToken) BeforeCreate(tx *gorm.DB) error
func (OAuthToken) TableName ¶ added in v0.4.0
func (OAuthToken) TableName() string
type PersonalAccessToken ¶ added in v0.4.0
type PersonalAccessToken struct {
ID string `gorm:"type:varchar(36);primaryKey"`
UserID string `gorm:"type:varchar(36);not null;index"`
Name string `gorm:"type:varchar(120);not null"`
TokenHash string `gorm:"type:varchar(64);not null;uniqueIndex"`
Last4 string `gorm:"type:varchar(8);not null"`
CreatedAt time.Time
LastUsedAt *time.Time
RevokedAt *time.Time `gorm:"index"`
}
PersonalAccessToken is a static bearer credential a user generates from /profile/mcp. The plaintext token is shown to the user exactly once at creation time; only the SHA-256 hash is persisted so it can be looked up on incoming MCP requests but never reconstructed.
Token wire format: "wick_pat_" + 32 hex chars. Last4 stores the last 4 characters of the random suffix so the UI can render a stable "wick_pat_****abcd" preview without keeping the secret around.
LastUsedAt is best-effort — written by the MCP middleware on successful auth. RevokedAt nil means active; non-nil hides the row from active-token queries while keeping the audit trail intact.
func (*PersonalAccessToken) BeforeCreate ¶ added in v0.4.0
func (t *PersonalAccessToken) BeforeCreate(tx *gorm.DB) error
func (*PersonalAccessToken) Masked ¶ added in v0.4.0
func (t *PersonalAccessToken) Masked() string
Masked returns the display form for list views: prefix + asterisks + last 4 characters. Never exposes the secret.
type RunTrigger ¶
type RunTrigger string
RunTrigger describes how a run was initiated.
const ( RunTriggerManual RunTrigger = "manual" RunTriggerCron RunTrigger = "cron" )
type SSOProvider ¶
type SSOProvider struct {
ID uint `gorm:"primaryKey"`
Provider string `gorm:"uniqueIndex;type:varchar(32);not null"` // "google"
ClientID string `gorm:"type:varchar(255)"`
ClientSecret string `gorm:"type:varchar(255)"`
Enabled bool `gorm:"default:false"`
// AllowedDomains is a comma-separated list of email domains allowed
// to sign in through this provider (e.g. "abc.com,abc.net").
// Empty string means no restriction — any email from the provider is
// accepted. Matching is case-insensitive.
AllowedDomains string `gorm:"type:text"`
CreatedAt time.Time
UpdatedAt time.Time
}
SSOProvider holds one OAuth/SSO provider's configuration. The callback URL is never stored — it's derived at runtime from app_variables.app_url + "/auth/callback".
func (SSOProvider) TableName ¶
func (SSOProvider) TableName() string
type Tag ¶
type Tag struct {
ID string `gorm:"type:varchar(36);primaryKey"`
Name string `gorm:"uniqueIndex;type:varchar(100);not null"`
Description string `gorm:"type:varchar(500)"`
IsGroup bool `gorm:"default:false"`
IsFilter bool `gorm:"default:false"`
IsSystem bool `gorm:"default:false"`
SortOrder int `gorm:"default:0"`
CreatedAt time.Time
}
Tag is a first-class label that can be attached to users and tools. Renaming a Tag propagates automatically because associations store TagID, not the name.
A Tag has two orthogonal-but-combinable purposes:
- Access filter: when IsFilter is true and the tag is attached to a Private tool, only users who carry the same tag may access it. Tool-tags without IsFilter are purely cosmetic for access (they don't restrict who can enter).
- Group on home: when IsGroup is true, tools carrying the tag are rendered together on the home page under Name. A tool with multiple group tags appears in each group.
A tag can set any combination of IsGroup and IsFilter independently.
IsSystem marks a tag as code-owned: it can only be assigned to tool/job/connector entities by code (via DefaultTags seeding), never by an admin from the UI to a user. The intent is to gate built-in maintenance items (e.g. the connector-runs-purge job) behind a tag no end user can carry — combined with IsFilter=true, this hides the item from /manager/* for everyone except admin (who bypasses the tag-filter rule wholesale).
type ToolPermission ¶
type ToolPermission struct {
ToolPath string `gorm:"primaryKey;type:varchar(255)"`
Visibility ToolVisibility `gorm:"type:varchar(50);default:'private'"`
// Disabled hides the tool from every user (including admins) and makes
// direct hits to /tools/{slug}/* return 404. Admins re-enable from
// /admin/tools.
Disabled bool `gorm:"default:false"`
UpdatedAt time.Time
}
ToolPermission stores the per-tool visibility override set by an admin. If no row exists for a tool path the code falls back to the tool's declared default visibility.
type ToolTag ¶
type ToolTag struct {
ToolPath string `gorm:"primaryKey;type:varchar(255)"`
TagID string `gorm:"primaryKey;type:varchar(36);index"`
}
ToolTag links a tool to a Tag. When a tool is Private and at least one ToolTag exists, only users carrying one of those tags may access it.
type ToolVisibility ¶
type ToolVisibility = pkgentity.ToolVisibility
ToolVisibility is re-exported from pkg/entity so existing callers continue to work after the public contract moved out of internal.
type User ¶
type User struct {
ID string `gorm:"type:varchar(36);primaryKey"`
Email string `gorm:"uniqueIndex;not null"`
Name string `gorm:"not null"`
Avatar string
Role UserRole `gorm:"type:varchar(50);default:'user'"`
Approved bool `gorm:"default:false"`
PasswordHash string `gorm:"type:varchar(255)"`
Metadata UserMetadata `gorm:"type:jsonb"`
CreatedAt time.Time
UpdatedAt time.Time
}
type UserMetadata ¶
type UserMetadata struct {
// HomeView picks the tool grid density: "compact" (icon+name) or
// "detailed" (wider cards with description). Empty means compact.
HomeView string `json:"home_view,omitempty"`
// Theme picks the UI color palette. Values are Theme.ID from
// internal/pkg/ui/theme.go ("light", "dark", "dracula", …).
// Empty means "no preference" — guests follow the device
// `prefers-color-scheme`, logged-in users can pick in the navbar.
Theme string `json:"theme,omitempty"`
// LightTheme / DarkTheme remember the last light- and dark-mode
// theme the user picked from the dropdown, so the navbar toggle
// can switch straight back to that variant instead of the generic
// "light"/"dark" defaults. Values are Theme.ID.
LightTheme string `json:"light_theme,omitempty"`
DarkTheme string `json:"dark_theme,omitempty"`
}
UserMetadata is the free-form preferences bag stored as JSON on the user row. Add fields here when a new per-user preference is needed — all consumers should default to the zero value when a field is unset so existing rows (NULL metadata) keep working without a backfill.
func (UserMetadata) HomeViewOrDefault ¶
func (m UserMetadata) HomeViewOrDefault() string
HomeViewOrDefault returns a valid HomeView value, falling back to compact when unset or unrecognized.
func (*UserMetadata) Scan ¶
func (m *UserMetadata) Scan(value any) error