Documentation
¶
Overview ¶
Package api provides HTTP handlers and routing for the admin API.
Index ¶
- Constants
- func AppendDiscoveryChange(ctx context.Context, pool *pgxpool.Pool, source string, providerID *uuid.UUID, ...) (bool, error)
- func ConfirmMissingModels(ctx context.Context, svc *provider.DiscoveryService, prov *provider.Provider, ...) (confirmedPresent []string, suspect bool)
- func DampenOpenRouterPriceJitter(providerType string, snapshot map[string]ModelSnapshot, models []*model.Model)
- func EvaluateClaimAgeAlert(ctx context.Context, pool *pgxpool.Pool, store SettingsStore, now time.Time) error
- func InitAppLogBuffer(pool *pgxpool.Pool)
- func NewAppSlogHandler(level slog.Level) slog.Handler
- func PruneDiscoveryChanges(ctx context.Context, pool *pgxpool.Pool, before time.Time) (int64, error)
- func ResyncFailoverAfterModelDelete(ctx context.Context, repo *failover.Repository, modelIDs []string, ...)
- func SnapshotProviderModels(ctx context.Context, repo *model.Repository, providerID uuid.UUID) (map[string]ModelSnapshot, error)
- func StopAppLogWriter()
- type AdminAuthenticator
- type AppLogEntry
- type AppLogsCursorResponse
- type AppStats
- type AuditListResponse
- type BackupHandler
- func (h *BackupHandler) ApplyPrune(w http.ResponseWriter, r *http.Request)
- func (h *BackupHandler) BackupSignature(w http.ResponseWriter, r *http.Request)
- func (h *BackupHandler) CreateBackup(w http.ResponseWriter, r *http.Request)
- func (h *BackupHandler) DeleteBackup(w http.ResponseWriter, r *http.Request)
- func (h *BackupHandler) DownloadBackup(w http.ResponseWriter, r *http.Request)
- func (h *BackupHandler) ListBackups(w http.ResponseWriter, r *http.Request)
- func (h *BackupHandler) PrunePreview(w http.ResponseWriter, r *http.Request)
- func (h *BackupHandler) Register(r chi.Router)
- func (h *BackupHandler) RestoreBackup(w http.ResponseWriter, r *http.Request)
- func (h *BackupHandler) SetSessionAuth(sessionMgr WebAuthnSessionManager, totpEnabled func() bool)
- func (h *BackupHandler) SetSigningKey(masterKey string)
- func (h *BackupHandler) StartScheduler(ctx context.Context)
- func (h *BackupHandler) StopScheduler()
- type BackupScheduler
- type BulkDeleteRequest
- type BulkDeleteResponse
- type CacheHits
- type CandidateModelResponse
- type CircuitBreakerControl
- type CircuitBreakerQuotaPinner
- type CircuitBreakerReader
- type CircuitBreakerResetAllResponse
- type CircuitBreakerResetResponse
- type CircuitBreakerResetter
- type CircuitBreakerStatusResponse
- type ClaimState
- type ConfigEnvelope
- type ConfigPayload
- type ConfigSyncHandler
- type CreateFailoverGroupRequest
- type CreateVirtualKeyRequest
- type DBStats
- type DemoLoginResponse
- type DiscoverAllResult
- type DiscoveryChangeEntry
- type DiscoveryChangesResponse
- type DiscoveryDiff
- type DiscoveryStatusResponse
- type DismissDiscoveryClaimsRequest
- type ExportFailoverEntry
- type ExportFailoverGroup
- type ExportModelRef
- type ExportProvider
- type ExportUser
- type ExportVK
- type FailoverEntryResponse
- type FailoverGroupBrief
- type FailoverGroupResponse
- type FailoverHandler
- func (h *FailoverHandler) Candidates(w http.ResponseWriter, r *http.Request)
- func (h *FailoverHandler) CircuitBreakerStatus(w http.ResponseWriter, r *http.Request)
- func (h *FailoverHandler) Create(w http.ResponseWriter, r *http.Request)
- func (h *FailoverHandler) Delete(w http.ResponseWriter, r *http.Request)
- func (h *FailoverHandler) Get(w http.ResponseWriter, r *http.Request)
- func (h *FailoverHandler) GetByModelUUID(w http.ResponseWriter, r *http.Request)
- func (h *FailoverHandler) List(w http.ResponseWriter, r *http.Request)
- func (h *FailoverHandler) Register(r chi.Router)
- func (h *FailoverHandler) ResetAllCircuitBreakers(w http.ResponseWriter, _ *http.Request)
- func (h *FailoverHandler) ResetCircuitBreaker(w http.ResponseWriter, r *http.Request)
- func (h *FailoverHandler) Sync(w http.ResponseWriter, r *http.Request)
- func (h *FailoverHandler) Update(w http.ResponseWriter, r *http.Request)
- type FailoverListResponse
- type FieldChange
- type FleetHandler
- type FleetStatus
- type GroupClaim
- type Handler
- func (h *Handler) AckDiscoveryChanges(w http.ResponseWriter, r *http.Request)
- func (h *Handler) AdminTokenExchange(w http.ResponseWriter, r *http.Request)
- func (h *Handler) AuthLogout(w http.ResponseWriter, r *http.Request)
- func (h *Handler) AuthMiddleware(next http.Handler) http.Handler
- func (h *Handler) BulkDeleteModels(w http.ResponseWriter, r *http.Request)
- func (h *Handler) ChangeOwnPassword(w http.ResponseWriter, r *http.Request)
- func (h *Handler) ChatUserContextMiddleware(next http.Handler) http.Handler
- func (h *Handler) ClearAppLogs(w http.ResponseWriter, r *http.Request)
- func (h *Handler) ClearQuotaAdvice(_ context.Context)
- func (h *Handler) CreateProvider(w http.ResponseWriter, r *http.Request)
- func (h *Handler) CreateUser(w http.ResponseWriter, r *http.Request)
- func (h *Handler) CreateVirtualKey(w http.ResponseWriter, r *http.Request)
- func (h *Handler) DeleteModel(w http.ResponseWriter, r *http.Request)
- func (h *Handler) DeleteProvider(w http.ResponseWriter, r *http.Request)
- func (h *Handler) DeleteUser(w http.ResponseWriter, r *http.Request)
- func (h *Handler) DeleteVirtualKey(w http.ResponseWriter, r *http.Request)
- func (h *Handler) DisableQuotaAdvice(ctx context.Context)
- func (h *Handler) DiscoverAllModels(w http.ResponseWriter, r *http.Request)
- func (h *Handler) DiscoverProviderModels(w http.ResponseWriter, r *http.Request)
- func (h *Handler) DismissDiscoveryClaims(w http.ResponseWriter, r *http.Request)
- func (h *Handler) GetAlertEvents(w http.ResponseWriter, _ *http.Request)
- func (h *Handler) GetAlertStatus(w http.ResponseWriter, r *http.Request)
- func (h *Handler) GetAlertTargets(w http.ResponseWriter, r *http.Request)
- func (h *Handler) GetAppLogs(w http.ResponseWriter, r *http.Request)
- func (h *Handler) GetAppLogsCursor(w http.ResponseWriter, r *http.Request)
- func (h *Handler) GetDemoLogin(w http.ResponseWriter, _ *http.Request)
- func (h *Handler) GetDiscoveryStatus(w http.ResponseWriter, r *http.Request)
- func (h *Handler) GetLatestVersion(w http.ResponseWriter, r *http.Request)
- func (h *Handler) GetLog(w http.ResponseWriter, r *http.Request)
- func (h *Handler) GetOllamaCloudAccount(w http.ResponseWriter, r *http.Request)
- func (h *Handler) GetProvider(w http.ResponseWriter, r *http.Request)
- func (h *Handler) GetProviderBalance(w http.ResponseWriter, r *http.Request)
- func (h *Handler) GetProviderUsage(w http.ResponseWriter, r *http.Request)
- func (h *Handler) GetPublicConfig(w http.ResponseWriter, _ *http.Request)
- func (h *Handler) GetSettings(w http.ResponseWriter, r *http.Request)
- func (h *Handler) GetVirtualKey(w http.ResponseWriter, r *http.Request)
- func (h *Handler) ListAudit(w http.ResponseWriter, r *http.Request)
- func (h *Handler) ListAuthSessions(w http.ResponseWriter, r *http.Request)
- func (h *Handler) ListGrantCatalog(w http.ResponseWriter, _ *http.Request)
- func (h *Handler) ListLogs(w http.ResponseWriter, r *http.Request)
- func (h *Handler) ListLogsCursor(w http.ResponseWriter, r *http.Request)
- func (h *Handler) ListModels(w http.ResponseWriter, r *http.Request)
- func (h *Handler) ListModelsCursor(w http.ResponseWriter, r *http.Request)
- func (h *Handler) ListProviders(w http.ResponseWriter, r *http.Request)
- func (h *Handler) ListUsers(w http.ResponseWriter, r *http.Request)
- func (h *Handler) ListVirtualKeys(w http.ResponseWriter, r *http.Request)
- func (h *Handler) Me(w http.ResponseWriter, r *http.Request)
- func (h *Handler) MetricsHandler() http.Handler
- func (h *Handler) NudgeQuotaPoll(providerID uuid.UUID)
- func (h *Handler) PollQuotasOnce(ctx context.Context)
- func (h *Handler) Pool() *db.DB
- func (h *Handler) ProbeAlert(w http.ResponseWriter, r *http.Request)
- func (h *Handler) PurgeAudit(w http.ResponseWriter, r *http.Request)
- func (h *Handler) PurgeLogs(w http.ResponseWriter, r *http.Request)
- func (h *Handler) RefreshAllQuotas(w http.ResponseWriter, r *http.Request)
- func (h *Handler) RefreshQuotaAdvice(ctx context.Context)
- func (h *Handler) RefreshTotpEnabled(ctx context.Context)
- func (h *Handler) Register(r chi.Router)
- func (h *Handler) RegisterAlerts(r chi.Router)
- func (h *Handler) RegisterAppLogs(r chi.Router)
- func (h *Handler) RegisterAudit(r chi.Router)
- func (h *Handler) RegisterAuthExchange(r chi.Router)
- func (h *Handler) RegisterDemoLogin(r chi.Router)
- func (h *Handler) RegisterEvents(r chi.Router)
- func (h *Handler) RegisterLogs(r chi.Router)
- func (h *Handler) RegisterModels(r chi.Router)
- func (h *Handler) RegisterProviderDiscovery(r chi.Router)
- func (h *Handler) RegisterPublicConfig(r chi.Router)
- func (h *Handler) RegisterSettings(r chi.Router)
- func (h *Handler) RegisterUserTotp(r chi.Router)
- func (h *Handler) RegisterUsers(r chi.Router)
- func (h *Handler) RegisterVersion(r chi.Router)
- func (h *Handler) RegisterVirtualKeys(r chi.Router)
- func (h *Handler) RequireGrant(g user.Grant) func(http.Handler) http.Handler
- func (h *Handler) ResetSettings(w http.ResponseWriter, r *http.Request)
- func (h *Handler) ResetUserTotp(w http.ResponseWriter, r *http.Request)
- func (h *Handler) RevokeAuthSessionByID(w http.ResponseWriter, r *http.Request)
- func (h *Handler) RevokeOtherSessions(w http.ResponseWriter, r *http.Request)
- func (h *Handler) SendAlertTest(w http.ResponseWriter, r *http.Request)
- func (h *Handler) SetAudit(rec *audit.Recorder)
- func (h *Handler) SetCircuitBreaker(cb CircuitBreakerControl)
- func (h *Handler) SetClientIPSource(ips webauthn.ClientIPSource)
- func (h *Handler) SetDockerStatsCollector(fn dockerStatsCollector)
- func (h *Handler) SetPwnedChecker(c PwnedChecker)
- func (h *Handler) SetQuotaAdvisor(a *QuotaAdvisor)
- func (h *Handler) SetTotpStatus(src TotpStatus)
- func (h *Handler) SetUserAuth(users UserStore, revoker SessionRevoker)
- func (h *Handler) SetUserPassword(w http.ResponseWriter, r *http.Request)
- func (h *Handler) SetUserTotp(factory UserTotpFactory)
- func (h *Handler) SetWebAuthnSessionManager(mgr WebAuthnSessionManager)
- func (h *Handler) StartBackupScheduler(ctx context.Context)
- func (h *Handler) StopBackupScheduler()
- func (h *Handler) StreamEvents(w http.ResponseWriter, r *http.Request)
- func (h *Handler) TestModel(w http.ResponseWriter, r *http.Request)
- func (h *Handler) TotpEnabled() bool
- func (h *Handler) UnpinDiscoveryClaims(w http.ResponseWriter, r *http.Request)
- func (h *Handler) UpdateModel(w http.ResponseWriter, r *http.Request)
- func (h *Handler) UpdateProvider(w http.ResponseWriter, r *http.Request)
- func (h *Handler) UpdateSettings(w http.ResponseWriter, r *http.Request)
- func (h *Handler) UpdateUser(w http.ResponseWriter, r *http.Request)
- func (h *Handler) UpdateVirtualKey(w http.ResponseWriter, r *http.Request)
- func (h *Handler) UserTotpDisable(w http.ResponseWriter, r *http.Request)
- func (h *Handler) UserTotpEnrollStart(w http.ResponseWriter, r *http.Request)
- func (h *Handler) UserTotpEnrollVerify(w http.ResponseWriter, r *http.Request)
- func (h *Handler) UserTotpStatus(w http.ResponseWriter, r *http.Request)
- type HealthHandler
- type LogEntry
- type LogsCursorResponse
- type LogsResponse
- type ModelChange
- type ModelClaim
- type ModelResponse
- type ModelSnapshot
- type ModelUpdate
- type ModelsCursorResponse
- type ProviderClaims
- type ProviderDistributionItem
- type ProviderDistributionStats
- type ProviderLatencyEntry
- type ProviderStore
- type PublicConfigResponse
- type PurgeLogsRequest
- type PwnedChecker
- type QuotaAdvisor
- type QuotaFleetHandler
- type QuotaRefreshResult
- type QuotaSnapshotWire
- type SessionRevoker
- type SettingsStore
- type StatsHandler
- type StatsResponse
- type SuspectStreak
- type SystemHandler
- type SystemStats
- type TestModelResponse
- type TimeSeriesPoint
- type TimeSeriesStats
- type TotpStatus
- type UnpinDiscoveryClaimsRequest
- type UpdateFailoverGroupRequest
- type UpdateVirtualKeyRequest
- type UserStore
- type UserTotpFactory
- type VirtualKeyStore
- type WebAuthnSessionManager
Constants ¶
const ClaimWindow = 30 * 24 * time.Hour
ClaimWindow bounds three things that MUST agree:
- how far back flap counts are computed,
- how long journal rows are retained before pruning,
- how long a quiet gone model waits before it stops counting.
They are one constant by necessity, not convenience: pruning discards flap history past the window, so an auto-dismiss horizon longer than the pruning horizon would read a flap count that has already been deleted and conclude "never flapped" from missing data.
const ClaimWindowDays = int(ClaimWindow / (24 * time.Hour))
ClaimWindowDays is ClaimWindow in whole days. Derived, never written as a literal, so the operator-facing ceiling below and the settings UI move together with the constant.
const DefaultClaimAlertDays = 7
DefaultClaimAlertDays is the shipped threshold: a week of a discrepancy going unlooked-at is long enough to be a real oversight and short enough that the alert still arrives well inside the claim window.
const EventTypeClaimsOutstanding = "discovery.claims_outstanding"
EventTypeClaimsOutstanding is published when the oldest counted discovery claim has been sitting in the Models badge for longer than the operator's configured threshold. It is the nudge that replaces staring at the badge: the badge itself is a passive count, and an operator who does not open the dashboard never learns that models stopped working.
const MaxClaimAlertDays = ClaimWindowDays - 1
MaxClaimAlertDays is the highest age threshold the outstanding-claims alert can be given, and it is deliberately one day BELOW the claim window rather than equal to it.
A gone model stops counting the moment its age exceeds ClaimWindow: the stale predicate in buildProviderClaims is strictly greater, so at that instant the claim leaves the badge count entirely. (A traffic-retired model never ages out this way — it is checked before the stale predicate and stays counted indefinitely — so the ceiling is not needed for its sake. It is kept as one ceiling for both because the gone case is what can go silently dead, and a per-state maximum would be a configuration nobody could reason about.) The alert fires when the oldest still-COUNTED claim exceeds the threshold. Set the threshold to exactly ClaimWindow and those two conditions have no overlap at all: the claim ages out of the count at precisely the instant the alert would trigger. The firing window would be measure-zero, no real scan interval would ever land inside it, and the alert would look configured while being silently dead forever. One whole day of margin means a crossed claim is still counted across at least one full discovery cycle.
const MaxModelPruneDays = 180
MaxModelPruneDays is the longest model_prune_days horizon accepted. Half a year of retained retired rows is already far past any operator's review cadence, and the ceiling keeps the horizon arithmetic in cmd/server well clear of the int64 overflow that would land it in the future. There is no floor above 0: the prune tells a retired row from a flapping one by whether the model came back inside the claim window, not by how long it has been gone, so a horizon of a few days is as safe as one of 30.
const SettingKeyClaimAlertDays = "discovery_claim_alert_days"
SettingKeyClaimAlertDays is the operator-tunable threshold, in whole days. It is in BOTH settings allowlists (api.allowedSettings and settings.AllowedSettings), which TestAllowedSettingsSync enforces in both directions.
Variables ¶
This section is empty.
Functions ¶
func AppendDiscoveryChange ¶ added in v0.9.68
func AppendDiscoveryChange(ctx context.Context, pool *pgxpool.Pool, source string, providerID *uuid.UUID, providerName string, diff *DiscoveryDiff) (bool, error)
AppendDiscoveryChange records one provider's background-discovery diff for later review. Empty diffs are skipped. Returns true when a row was written so the caller can decide whether to publish a live-update event. providerID may be nil. Exported for the scheduled discovery loop in package main.
func ConfirmMissingModels ¶ added in v0.9.84
func ConfirmMissingModels(ctx context.Context, svc *provider.DiscoveryService, prov *provider.Provider, masterKey string, presentIDs []string, snapshot map[string]ModelSnapshot, streak *SuspectStreak) (confirmedPresent []string, suspect bool)
ConfirmMissingModels gives absent models a second opinion before any miss is recorded. presentIDs is the initial listing's membership; every snapshot model that is enabled but unlisted triggers up to len(confirmProbeDelays) fresh listings with backoff, and the union of all probes' model IDs is returned as the confirmed membership. suspect=true means this scan's membership cannot be trusted (a confirmation probe failed, ctx was cancelled, or the mass-vanish guard tripped) and the caller must skip miss recording entirely. Probes affect membership only; metadata still comes exclusively from the initial listing's upserts. streak, when non-nil, tracks consecutive mass-vanish scans per provider: it is reset on any healthy scan and bumped (with escalation) when the mass-vanish guard trips, so a genuine bulk removal eventually raises a loud alert while a one-off broken listing does not. It is nil on paths that must not touch the counter (unit tests).
func DampenOpenRouterPriceJitter ¶ added in v0.9.75
func DampenOpenRouterPriceJitter(providerType string, snapshot map[string]ModelSnapshot, models []*model.Model)
DampenOpenRouterPriceJitter neutralizes sub-tolerance price wiggles from OpenRouter's volatile per-upstream pricing. For OpenRouter providers only, it REPLACES any freshly discovered price sitting within priceRelTolerance of the stored (pre-scan) value with that stored value, so Upsert persists the same number (prices follow the incoming value on unpinned rows) and diffModelFields sees no change. Large, genuine price moves exceed the band and pass through untouched. No-op for every other provider type, for models with no snapshot, and for nil endpoints.
Call it after snapshotting and before upserting, at every discovery path.
func EvaluateClaimAgeAlert ¶ added in v0.9.92
func EvaluateClaimAgeAlert(ctx context.Context, pool *pgxpool.Pool, store SettingsStore, now time.Time) error
EvaluateClaimAgeAlert publishes EventTypeClaimsOutstanding when the oldest currently-counted claim has been outstanding for longer than the operator's threshold. Three outcomes, in the switch below:
- not latched, or the situation is worse than the level last reported: publish and record the new level;
- still crossed but improved: lower the recorded level silently, so the next worsening is measured from here rather than from an old peak;
- nothing crossed: drop the latch entirely and re-arm.
See claimAlertLatch for why the level is stored at all: a plain flag is muted forever by a single permanently-disabled failover group.
The oldest counted claim is a sound proxy for "the badge has been non-zero continuously for at least this long": a claim that is still counted has been counted for its whole life, so its age is a lower bound on how long the badge has been asking for attention.
Errors are returned rather than swallowed so the caller can log them, but the caller must treat this as housekeeping: a failure here says nothing about whether the discovery run itself succeeded.
func InitAppLogBuffer ¶
InitAppLogBuffer initializes the application log ring buffer and optional DB writer.
func NewAppSlogHandler ¶
NewAppSlogHandler returns a slog.Handler that writes structured log entries through the app log pipeline (ring buffer + DB writer + filtered stderr). Call after InitAppLogBuffer and pass to debuglog.SetHandler to route all slog output through the app logging system.
The docker-logs (stderr) surface honors LOG_FORMAT: when JSON is requested it emits one JSON object per line (level/source/msg + the slog attrs as fields) for log collectors; otherwise the human-readable text form. The ring buffer / DB / SSE path (the App Logs page) is unchanged either way.
func PruneDiscoveryChanges ¶ added in v0.9.92
func PruneDiscoveryChanges(ctx context.Context, pool *pgxpool.Pool, before time.Time) (int64, error)
PruneDiscoveryChanges deletes seen journal rows older than the window. Safe only because claims are derived from `models`: a journal row can no longer be the sole evidence of a pending claim.
func ResyncFailoverAfterModelDelete ¶ added in v0.9.100
func ResyncFailoverAfterModelDelete(ctx context.Context, repo *failover.Repository, modelIDs []string, deletedIDs []uuid.UUID)
ResyncFailoverAfterModelDelete rebuilds the auto-groups of every affected base model name and drops the deleted UUIDs from custom groups. Best-effort: each failure is logged and the rest continues, because the rows are already gone and a half-synced group is better than an aborted cleanup. Shared by the dashboard bulk delete and the discovery-pass prune.
func SnapshotProviderModels ¶ added in v0.9.68
func SnapshotProviderModels(ctx context.Context, repo *model.Repository, providerID uuid.UUID) (map[string]ModelSnapshot, error)
SnapshotProviderModels maps model_id to its pre-scan state for one provider. The pins come from their own query because Model carries no field for them.
func StopAppLogWriter ¶
func StopAppLogWriter()
StopAppLogWriter stops the database log writer goroutine.
Types ¶
type AdminAuthenticator ¶
AdminAuthenticator defines admin token validation.
type AppLogEntry ¶
type AppLogEntry struct {
ID string `json:"id,omitempty"`
CreatedAt string `json:"created_at,omitempty"` // RFC3339Nano, DB insertion time (keyset sort key)
Timestamp string `json:"timestamp"` // RFC3339Nano, event time
Level string `json:"level"` // "info", "warning", "error"
Source string `json:"source"` // "proxy", "auth", "discovery", etc. (without brackets)
Message string `json:"message"`
// Escaped marks messages produced by the slog handler, whose attribute
// values use the flattened quoteLogValue encoding (spaces as \x20). The
// dashboard decodes that escaping only when this is set; legacy rows and
// raw io.Writer lines stay false and render verbatim (migration 075).
Escaped bool `json:"escaped,omitempty"`
// AttrsAt is the offset in Message where the encoded attribute suffix
// begins (everything before it is raw message text, everything from it
// on is quoteLogValue output), counted in UTF-16 code units: the unit
// JavaScript strings index by, so the dashboard's String.slice lands on
// the same boundary for non-ASCII text. The dashboard decodes \x20 only
// from this offset. Meaningful only when Escaped is set.
AttrsAt int `json:"attrs_at"`
}
AppLogEntry represents a single captured application log line.
type AppLogsCursorResponse ¶ added in v0.9.1
type AppLogsCursorResponse struct {
Entries []AppLogEntry `json:"entries"`
Total int `json:"total"`
HasBefore bool `json:"has_before"`
HasAfter bool `json:"has_after"`
LevelCounts map[string]int `json:"level_counts"`
SourceCounts map[string]int `json:"source_counts"`
}
AppLogsCursorResponse is the cursor-based paginated response for app logs.
type AppStats ¶
type AppStats struct {
HeapAllocMB float64 `json:"heap_alloc_mb"`
SysMemoryMB float64 `json:"sys_memory_mb"`
Goroutines int `json:"goroutines"`
GCCycles uint64 `json:"gc_cycles"`
MemoryCurrent int64 `json:"memory_current_bytes"`
MemoryLimit int64 `json:"memory_limit_bytes"`
InContainer bool `json:"in_container"`
UptimeSeconds int64 `json:"uptime_seconds"`
CPUPercent float64 `json:"cpu_percent"`
RequestsToday int64 `json:"requests_today"`
NetRxBytesSec float64 `json:"net_rx_bytes_sec"`
NetTxBytesSec float64 `json:"net_tx_bytes_sec"`
DiskReadBytesSec float64 `json:"disk_read_bytes_sec"`
DiskWriteBytesSec float64 `json:"disk_write_bytes_sec"`
Procs int `json:"procs"`
}
AppStats contains application-level metrics (memory, CPU, network, disk).
type AuditListResponse ¶ added in v0.9.84
type AuditListResponse struct {
Entries []audit.Entry `json:"entries"`
Total int `json:"total"`
HasMore bool `json:"has_more"`
// Cursor of the last returned row, to pass back for the next (older) page.
NextCursor string `json:"next_cursor,omitempty"`
}
AuditListResponse is the cursor-paginated audit page.
type BackupHandler ¶
type BackupHandler struct {
// contains filtered or unexported fields
}
BackupHandler manages PostgreSQL database backups via pg_dump and restores via pg_restore.
func NewBackupHandler ¶
func NewBackupHandler(databaseURL, backupDir string, adminMgr AdminAuthenticator, settingsRepo SettingsStore) *BackupHandler
NewBackupHandler creates a new BackupHandler. backupDir is the directory where backup files are stored (typically DATA_DIR/backups).
func (*BackupHandler) ApplyPrune ¶ added in v0.9.47
func (h *BackupHandler) ApplyPrune(w http.ResponseWriter, r *http.Request)
ApplyPrune runs the rotation and deletes backups that fall outside the son/father/grandfather retention scheme.
func (*BackupHandler) BackupSignature ¶ added in v0.9.99
func (h *BackupHandler) BackupSignature(w http.ResponseWriter, r *http.Request)
BackupSignature serves a backup's signature sidecar so the operator can carry it to a restore without shell access to the backup directory. The sidecar is the only proof of the dump's integrity that survives the round trip through a download and a re-upload, and the restore form has nowhere else to get it. The signature is not verified here: this hands over what is on disk, and the restore checks it against the uploaded bytes. Unsigned backups get a 404, which is what the listing's "signed: false" already promises.
func (*BackupHandler) CreateBackup ¶
func (h *BackupHandler) CreateBackup(w http.ResponseWriter, r *http.Request)
CreateBackup runs pg_dump and saves the output to a timestamped file. An operator-initiated call records origin "manual"; a fleet caller may pass ?origin=frontdesk so its snapshot is badged distinctly (and, like manual backups, spared from GFS rotation). "auto" is scheduler-internal and never accepted here.
func (*BackupHandler) DeleteBackup ¶
func (h *BackupHandler) DeleteBackup(w http.ResponseWriter, r *http.Request)
DeleteBackup removes a backup file.
func (*BackupHandler) DownloadBackup ¶
func (h *BackupHandler) DownloadBackup(w http.ResponseWriter, r *http.Request)
DownloadBackup serves a backup file for download.
Opens the file up front and serves from that handle rather than stat-ing the path and letting http.ServeFile reopen it. A stat-then-open pair races the delete and retention-prune paths: the file can vanish between the two checks and the client gets a truncated or failed transfer. Holding an open descriptor removes the window - an unlink during the transfer leaves this handle readable until it is closed. Deliberately does NOT take backupMu: downloads are long-lived, and the create/restore/prune paths TryLock and give up when busy, so a slow client would silently cancel scheduled backups for the length of its transfer.
func (*BackupHandler) ListBackups ¶
func (h *BackupHandler) ListBackups(w http.ResponseWriter, r *http.Request)
ListBackups returns all backup files sorted by creation time (newest first).
func (*BackupHandler) PrunePreview ¶ added in v0.9.47
func (h *BackupHandler) PrunePreview(w http.ResponseWriter, r *http.Request)
PrunePreview returns which backups would be pruned under the current son/father/grandfather rotation scheme without actually deleting anything.
func (*BackupHandler) Register ¶
func (h *BackupHandler) Register(r chi.Router)
Register registers backup routes on the given router.
func (*BackupHandler) RestoreBackup ¶ added in v0.4.3
func (h *BackupHandler) RestoreBackup(w http.ResponseWriter, r *http.Request)
RestoreBackup validates and restores a database backup from an uploaded .dump file. The request must include the admin token in the multipart form for explicit confirmation. Pre-restore validation:
- pg_restore --list validates the dump format and checks for dangerous objects
- Extracts schema_migrations from the dump and compares against known migrations
- Rejects dumps from newer versions (unknown migrations)
After successful restore, the process exits so Docker can restart it with fresh caches.
func (*BackupHandler) SetSessionAuth ¶ added in v0.9.71
func (h *BackupHandler) SetSessionAuth(sessionMgr WebAuthnSessionManager, totpEnabled func() bool)
SetSessionAuth wires the WebAuthn session manager and TOTP-enabled flag so restore (a destructive, second independent auth gate via multipart form field) honors 2FA: when TOTP is enabled, a raw admin token in the form field is rejected and a session token from /totp/login is required instead. Mirrors Handler.AuthMiddleware's gate. Called after NewBackupHandler in Handler.Register.
func (*BackupHandler) SetSigningKey ¶ added in v0.9.94
func (h *BackupHandler) SetSigningKey(masterKey string)
SetSigningKey wires the master key used to derive the backup signing key, so dumps are signed on creation and verified on the way back out. Left empty (no MASTER_KEY configured) signing is skipped entirely and backups behave as they did before signing existed.
func (*BackupHandler) StartScheduler ¶ added in v0.9.47
func (h *BackupHandler) StartScheduler(ctx context.Context)
StartScheduler starts the periodic backup scheduler goroutine.
The goroutine always runs (regardless of the current backup_enabled value) and re-reads backup_enabled and backup_interval from the settings store on every tick. This lets the toggle take effect at runtime without a server restart: when disabled it polls on a short idle interval; when enabled it creates a backup and applies the rotation scheme, then sleeps for backup_interval.
func (*BackupHandler) StopScheduler ¶ added in v0.9.47
func (h *BackupHandler) StopScheduler()
StopScheduler stops the periodic backup scheduler.
type BackupScheduler ¶ added in v0.9.47
BackupScheduler defines the interface for the periodic backup scheduler.
type BulkDeleteRequest ¶ added in v0.9.91
type BulkDeleteRequest struct {
IDs []string `json:"ids"`
}
BulkDeleteRequest is the JSON body for POST /api/models/bulk-delete.
type BulkDeleteResponse ¶ added in v0.9.91
type BulkDeleteResponse struct {
Requested int64 `json:"requested"`
Deleted int64 `json:"deleted"`
}
BulkDeleteResponse reports the outcome of a bulk delete. Deleted may be less than Requested when some IDs no longer exist (idempotent, not an error).
type CacheHits ¶ added in v0.9.46
CacheHits is an alias for the shared CacheHits type defined in util. The API uses this alias for clarity in LogEntry — the underlying type is the same one the proxy produces.
type CandidateModelResponse ¶
type CandidateModelResponse struct {
ModelUUID string `json:"model_uuid"`
ModelID string `json:"model_id"`
ProviderID string `json:"provider_id"`
ProviderName string `json:"provider_name"`
DisplayName string `json:"display_name"`
ContextLength *int `json:"context_length"`
OwnedBy string `json:"owned_by"`
}
CandidateModelResponse represents a model candidate for failover groups.
type CircuitBreakerControl ¶ added in v0.9.91
type CircuitBreakerControl interface {
CircuitBreakerReader
CircuitBreakerResetter
CircuitBreakerQuotaPinner
}
CircuitBreakerControl is the whole breaker surface the failover API needs. Composed from the narrow interfaces above so internal/api still depends on behaviour it names rather than on *failover.CircuitBreaker.
type CircuitBreakerQuotaPinner ¶ added in v0.9.91
type CircuitBreakerQuotaPinner interface {
// ReleaseQuotaPins clears the quota cooldown override on every tracked
// circuit whose provider appears in recovered, returning how many pins it
// lifted. It must not change any circuit's state. recovered carries only
// providers a fresh snapshot was assessed for and found not exhausted;
// anything absent (stale, unassessable, or never snapshotted) keeps its pin.
ReleaseQuotaPins(recovered map[uuid.UUID]struct{}) int
// ReleaseAllQuotaPins clears the override on every pinned circuit, for the
// one case where absence of evidence is decisive: quota polling has been
// switched off, so no refresh will ever report a recovery again. It must
// not change any circuit's state either.
ReleaseAllQuotaPins() int
// ApplyQuotaPins retargets the cooldown of every already-open circuit whose
// provider appears in advice, returning how many it retargeted. It only
// lengthens a wait: it must not change any circuit's state, must leave
// closed and half-open circuits alone, and must never shorten a pin already
// reaching further than the advice. Implementations may read advice only for
// the duration of the call.
ApplyQuotaPins(advice map[uuid.UUID]time.Time) int
}
CircuitBreakerQuotaPinner lets a successful quota refresh lift the cooldown pins of providers that are no longer exhausted. Separate from the reset contract because it is a different power: it shortens a wait, it never clears a circuit. Keeping it its own interface is also what keeps internal/failover free of any dependency on internal/quota — the set of recovered providers crosses the boundary as plain UUIDs.
type CircuitBreakerReader ¶ added in v0.9.38
type CircuitBreakerReader interface {
Status() []failover.ProviderStatus
}
CircuitBreakerReader provides read-only access to circuit breaker status.
type CircuitBreakerResetAllResponse ¶ added in v0.9.91
type CircuitBreakerResetAllResponse struct {
Cleared int `json:"cleared"`
Recovered int `json:"recovered"`
}
CircuitBreakerResetAllResponse reports the outcome of a bulk reset: Cleared counts every circuit discarded, Recovered only those that were actually sidelining their provider.
type CircuitBreakerResetResponse ¶ added in v0.9.91
type CircuitBreakerResetResponse struct {
ProviderID string `json:"provider_id"`
PreviousState string `json:"previous_state"`
Reset bool `json:"reset"`
}
CircuitBreakerResetResponse reports the outcome of resetting one provider's circuit. PreviousState is what the breaker reported for that provider a moment before it was cleared; Reset is false when there was nothing to clear (an already-closed or never-tracked provider), so the UI can say "no change" instead of claiming a recovery that did not happen.
type CircuitBreakerResetter ¶ added in v0.9.91
type CircuitBreakerResetter interface {
// Reset clears one provider's circuit and returns the state it was in
// beforehand (closed for an untracked provider, which is a no-op).
Reset(providerID uuid.UUID) failover.State
// ResetAll clears every circuit, returning how many were discarded and how
// many of those were actually sidelining a provider.
ResetAll() (cleared, recovered int)
}
CircuitBreakerResetter clears breaker state so an operator can force a sidelined provider back into rotation early. Kept separate from CircuitBreakerReader so a status-only consumer never acquires the ability to mutate breaker state just by depending on the read contract.
type CircuitBreakerStatusResponse ¶ added in v0.9.38
type CircuitBreakerStatusResponse struct {
Closed int `json:"closed"`
HalfOpen int `json:"half_open"`
Open int `json:"open"`
Providers []failover.ProviderStatus `json:"providers,omitempty"`
}
CircuitBreakerStatusResponse contains counts of providers in each circuit breaker state.
type ClaimState ¶ added in v0.9.92
type ClaimState string
ClaimState is what discovery currently believes about one model.
const ( // ClaimStateGone means discovery disabled it and it is still missing. Counted. ClaimStateGone ClaimState = "gone" // ClaimStateStale means gone for longer than ClaimWindow with no flapping, // so it is almost certainly retired rather than broken. Shown, not counted. ClaimStateStale ClaimState = "stale" // ClaimStateSuspect means still enabled but mid-streak, one bad scan from // being disabled. Early warning only, never counted. ClaimStateSuspect ClaimState = "suspect" // ClaimStateRetired means the PROXY disabled it from live traffic: the // provider kept listing the model and refused every request for it // (models.auto_retired_at, migration 063). Counted, like gone. // // It is a separate state because the operator's next step is different and // the other states' wording is actively wrong here. A gone model is missing // from the provider's listing, so "last seen" dates it and a retest is the // obvious move. A retired model is still listed and was seen moments ago, so // a retest finds it present and proves nothing — what happened is that // requests for it failed. ClaimStateRetired ClaimState = "retired" // ClaimStatePinned means the operator enabled the model by hand while the // provider's listing still omits it. The pin blocks listing-based // auto-disable, so the row is informational: shown so a forgotten pin stays // visible, never counted, because the operator has already adjudicated it. ClaimStatePinned ClaimState = "pinned" )
type ConfigEnvelope ¶ added in v0.9.79
type ConfigEnvelope struct {
SchemaVersion int `json:"schema_version"`
AppVersion string `json:"app_version"`
ExportedAt time.Time `json:"exported_at"`
Config ConfigPayload `json:"config"`
}
ConfigEnvelope is the JSON exchanged between members. []byte fields marshal as base64, so the provider key ciphertext rides safely inside JSON.
type ConfigPayload ¶ added in v0.9.79
type ConfigPayload struct {
Providers []ExportProvider `json:"providers"`
VirtualKeys []ExportVK `json:"virtual_keys"`
Settings map[string]string `json:"settings"`
// Not omitempty: a member running this code always emits the key, as [] when it
// has no custom groups. That lets import tell "primary genuinely has zero custom
// groups" (present empty array, reconcile to zero) apart from "envelope predates
// this field" (key absent, decodes to nil, leave the member's groups alone).
FailoverGroups []ExportFailoverGroup `json:"failover_groups"`
// Same nil-vs-empty contract as FailoverGroups: always emitted by a member
// running this code ([] when there are no accounts), absent in an envelope
// from an older primary (decodes to nil, import leaves users alone).
Users []ExportUser `json:"users"`
// DisabledModels are the models the operator switched off by hand, by stable
// ref. Same nil-vs-empty contract again: [] means "the primary has none, clear
// yours", absent means an older primary whose per-model state must be left
// alone.
DisabledModels []ExportModelRef `json:"disabled_models"`
// EnabledModels are the models the operator pinned enabled by hand while the
// provider's listing omits them (models.manually_enabled_at), by stable ref.
// Same nil-vs-empty contract as DisabledModels: [] means "the primary has no
// pins, clear yours", absent means an older primary whose pins must be left
// alone. Import force-enables and pins; reconcile only clears pins, never
// disables, so a member's own listing-based disable machinery resumes.
EnabledModels []ExportModelRef `json:"enabled_models"`
}
ConfigPayload is the config-only body of the envelope.
type ConfigSyncHandler ¶ added in v0.9.79
type ConfigSyncHandler struct {
// contains filtered or unexported fields
}
ConfigSyncHandler serves the member-side config export/import endpoints. It is mounted inside the admin-authenticated /api group, so every call already requires the admin token (or a session when TOTP is on): a caller able to import config controls the data plane, so no weaker gate is acceptable.
func NewConfigSyncHandler ¶ added in v0.9.79
func NewConfigSyncHandler(database *db.DB, settingsRepo SettingsStore, masterKey, appVersion string, discoverAll func(context.Context) error, validateProviderURL func(string) error) *ConfigSyncHandler
NewConfigSyncHandler builds the handler. masterKey is needed only to verify (on import) that this member can decrypt the incoming provider keys; the plaintext is never produced here. validateProviderURL applies the admin API's SSRF check to imported provider base_urls (see the field doc); production passes config.ValidateProviderURL, tests may pass nil to skip it.
func (*ConfigSyncHandler) Export ¶ added in v0.9.79
func (h *ConfigSyncHandler) Export(w http.ResponseWriter, r *http.Request)
Export returns this member's full config envelope so Front Desk can replicate it onto the fleet.
func (*ConfigSyncHandler) Import ¶ added in v0.9.79
func (h *ConfigSyncHandler) Import(w http.ResponseWriter, r *http.Request)
Import applies an envelope onto this member. With ?dryRun=1 it returns the diff without writing. Otherwise it converges this member to the envelope inside a single transaction: all-or-nothing.
func (*ConfigSyncHandler) Register ¶ added in v0.9.79
func (h *ConfigSyncHandler) Register(r chi.Router)
Register mounts GET/POST /config/{export,import} and GET /config/version. The parent router must apply admin auth (see type doc).
func (*ConfigSyncHandler) Version ¶ added in v0.9.81
func (h *ConfigSyncHandler) Version(w http.ResponseWriter, r *http.Request)
Version returns a stable content hash of this member's syncable config, so Front Desk's auto-sync poller can cheaply detect that the primary's config changed without pulling and diffing the full export every tick. The hash covers only the Config payload (providers, virtual keys, syncable settings, custom failover groups, users), never the volatile envelope fields (exported_at), so it changes if and only if a synced entity changed. Same auth as Export.
Alongside the overall hash it names a hash per payload section, keyed by the section's JSON field name. Front Desk compares a diverged member's section hashes with the primary's to say WHICH part of the config differed in the config.auto_synced event, without pulling either export.
type CreateFailoverGroupRequest ¶
type CreateFailoverGroupRequest struct {
DisplayModel string `json:"display_model"`
DisplayName *string `json:"display_name"`
Description *string `json:"description"`
EntryIDs []string `json:"entry_ids"`
}
CreateFailoverGroupRequest is the request body for creating a failover group.
type CreateVirtualKeyRequest ¶
type CreateVirtualKeyRequest struct {
Name string `json:"name"`
RateLimitRPS *float64 `json:"rate_limit_rps,omitempty"`
RateLimitBurst *int `json:"rate_limit_burst,omitempty"`
RateLimitTPM *int `json:"rate_limit_tpm,omitempty"`
AllowedProviders *[]string `json:"allowed_providers,omitempty"`
StripReasoning *bool `json:"strip_reasoning,omitempty"`
// OwnerUserID assigns the key to a dashboard user (admin callers only;
// for non-admins the key is always created as their own). Empty string or
// null means unowned.
OwnerUserID *string `json:"owner_user_id,omitempty"`
}
CreateVirtualKeyRequest is the request body for creating a virtual key.
type DBStats ¶
type DBStats struct {
SizeMB float64 `json:"size_mb"`
Connections int `json:"connections"`
CacheHitRatio float64 `json:"cache_hit_ratio"`
// CacheWindowBlocks is how many block accesses (hits + reads) the window
// behind CacheHitRatio contained. Zero/omitted means the ratio is not backed
// by fresh activity (first sample, counter reset, or an idle window), so
// consumers can grey the ratio out instead of colour-coding stale history.
CacheWindowBlocks int64 `json:"cache_window_blocks,omitempty"`
TxPerSec float64 `json:"tx_per_sec"`
DeadTuples int64 `json:"dead_tuples"`
LockWaits int `json:"lock_waits"`
}
DBStats contains PostgreSQL database metrics.
type DemoLoginResponse ¶ added in v0.9.69
type DemoLoginResponse struct {
Token string `json:"token"`
}
DemoLoginResponse carries the admin token to display on the login screen of a public demo instance, so operators can share only the URL. The token is a secret, so unlike PublicConfigResponse this is served by a dedicated endpoint that is only ever populated when the demo gating below is satisfied.
type DiscoverAllResult ¶
type DiscoverAllResult struct {
ProviderName string `json:"provider_name"`
Discovered int `json:"discovered"`
Diff *DiscoveryDiff `json:"diff,omitempty"`
Error string `json:"error,omitempty"`
}
DiscoverAllResult holds the result of discovering models from a single provider.
type DiscoveryChangeEntry ¶ added in v0.9.68
type DiscoveryChangeEntry struct {
// ProviderID is empty when the provider was deleted after the row was
// recorded (the column is nullable). The dashboard uses it to offer a
// per-provider Retest action from the changes modal.
ProviderID string `json:"provider_id,omitempty"`
ProviderName string `json:"provider_name"`
Source string `json:"source"`
DetectedAt time.Time `json:"detected_at"`
Diff *DiscoveryDiff `json:"diff"`
}
DiscoveryChangeEntry is one provider's recorded background-discovery diff.
type DiscoveryChangesResponse ¶ added in v0.9.68
type DiscoveryChangesResponse struct {
Entries []DiscoveryChangeEntry `json:"entries"`
Count int `json:"count"`
}
DiscoveryChangesResponse is the payload for POST /api/discovery/changes/ack: exactly the rows that call marked seen, newest-first. Count is always 0 there (the badge is empty once the rows are acked) and the field is kept only so the shape stays self-describing. The GET that used to serve this type is gone; GET /api/discovery/status replaced it.
type DiscoveryDiff ¶ added in v0.9.52
type DiscoveryDiff struct {
Added []ModelChange `json:"added,omitempty"`
Reenabled []ModelChange `json:"reenabled,omitempty"`
Disabled []ModelChange `json:"disabled,omitempty"`
Updated []ModelUpdate `json:"updated,omitempty"`
FailoverDeletedGroups []failover.DeletedGroupInfo `json:"failover_deleted_groups,omitempty"`
FailoverUpdatedGroups []failover.UpdatedGroupInfo `json:"failover_updated_groups,omitempty"`
FailoverDisabledGroups []failover.DisabledGroupInfo `json:"failover_disabled_groups,omitempty"`
}
DiscoveryDiff summarizes the state changes one provider scan caused.
func BuildDiscoveryDiff ¶ added in v0.9.68
func BuildDiscoveryDiff(snapshot map[string]ModelSnapshot, upserted []*model.Model, disabledRefs []model.DisabledModelRef) *DiscoveryDiff
BuildDiscoveryDiff classifies one provider scan against its before-snapshot: upserted models absent from the snapshot are new; a snapshot model the scan actually brought back counts as reappeared; an unchanged-membership model whose pricing/context fields moved is an update; disabledRefs are the models this scan just disabled.
The re-enable is read off the row Upsert returned rather than re-derived from the conditions Upsert applies. Those conditions have grown — a model the operator disabled by hand stays off, and so does one the proxy retired from traffic (auto_retired_at, migration 063) — and a copy of them here drifted from the SQL and reported a revival that never happened. An auto-retired model is the case that made it permanent rather than occasional: it never left the listing, so it is sighted on every single scan, and every scan claimed to have re-enabled it while the write correctly declined to.
type DiscoveryStatusResponse ¶ added in v0.9.92
type DiscoveryStatusResponse struct {
Claims []ProviderClaims `json:"claims"`
GroupClaims []GroupClaim `json:"group_claims"`
Informational []DiscoveryChangeEntry `json:"informational"`
ClaimCount int `json:"claim_count"`
InformationalUnseen int `json:"informational_unseen"`
}
DiscoveryStatusResponse powers the Models nav badge and its modal. ClaimCount counts Gone and Retired models: Stale, Suspect and Pinned are shown but never inflate the badge, so a non-zero badge always means something might actually be wrong. Retired counts because it is the same kind of fact as Gone — a model that was working and now is not — even though it came from the proxy refusing traffic rather than from the provider dropping it from its listing. InformationalUnseen drives the badge dot when ClaimCount is 0, and counts only the entries carrying something other than metadata `updated` changes: prices move on nearly every scan, so counting them would leave the dot permanently lit (see countInformationalUnseen). GroupClaims are the failover groups discovery disabled; they count toward ClaimCount alongside Gone models, because a disabled group means `hotel/` routing for that model has stopped working.
type DismissDiscoveryClaimsRequest ¶ added in v0.9.92
type DismissDiscoveryClaimsRequest struct {
ProviderID string `json:"provider_id"`
ModelIDs []string `json:"model_ids"`
}
DismissDiscoveryClaimsRequest carries the models to dismiss on one provider.
Dismiss-only, deliberately: there is no un-dismiss direction. A dismissal self-heals, because models.Upsert clears discovery_dismissed_at on any sighting, so the next discovery run undoes it for any model that came back. That is the only reversal the feature needs, and it needs no endpoint.
A traffic-retired model gets there by a different route, since Upsert deliberately preserves its dismissal (it is sighted on every scan, so clearing on a sighting would make it impossible to silence). For those, the operator enabling the model clears the dismissal in the same statement as the enable (models.SetEnabled and models.Update), so a model retired again afterwards raises a fresh claim. That has to be atomic rather than left to the next sighting: traffic reaches a re-enabled model in seconds and a scan is about an hour away, so a second retirement would otherwise arrive first and re-arm the preserve-the-dismissal rule around a stamp nothing could clear. Still no endpoint needed.
type ExportFailoverEntry ¶ added in v0.9.79
type ExportFailoverEntry struct {
ProviderName string `json:"provider_name"`
ModelID string `json:"model_id"`
Enabled bool `json:"enabled"`
}
ExportFailoverEntry is one member of a failover group, identified by the stable (provider name, model_id) pair rather than the instance-local model UUID.
type ExportFailoverGroup ¶ added in v0.9.79
type ExportFailoverGroup struct {
DisplayModel string `json:"display_model"`
DisplayName *string `json:"display_name,omitempty"`
Description string `json:"description,omitempty"`
GroupEnabled bool `json:"group_enabled"`
Entries []ExportFailoverEntry `json:"entries"`
}
ExportFailoverGroup is a CUSTOM (non-auto-created) failover group. Its priority_order / entry_enabled reference instance-local model UUIDs, so it is carried as ordered (provider name, model_id) entry refs, resolved back to this member's model UUIDs on import. Auto-created groups are excluded: they regenerate identically on every member from the synced providers.
type ExportModelRef ¶ added in v0.9.94
type ExportModelRef struct {
ProviderName string `json:"provider_name"`
ModelID string `json:"model_id"`
}
ExportModelRef is a model's stable cross-member identity: the provider's name plus the provider-scoped model_id. The same pair a failover group's entries travel by, for the same reason (model UUIDs are instance-local).
func (ExportModelRef) String ¶ added in v0.9.94
func (r ExportModelRef) String() string
String renders a ref the way the gateway names that model everywhere else: the provider, a slash, then the provider-scoped id. Model ids routinely contain slashes themselves (meta-llama/Llama-3-70b), which makes this look ambiguous and is not: the proxy resolves an incoming name with SplitN(name, "/", 2) (proxy_request.go), so the first slash separates and the rest is the id. An operator reading openai/meta-llama/Llama-3-70b in an alert sees exactly the string they would send to /v1/chat/completions.
type ExportProvider ¶ added in v0.9.79
type ExportProvider struct {
Name string `json:"name"`
BaseURL string `json:"base_url"`
ProviderType string `json:"provider_type"`
Enabled bool `json:"enabled"`
AutodiscoveryEnabled bool `json:"autodiscovery_enabled"`
EncryptedKey []byte `json:"encrypted_key,omitempty"`
KeyNonce []byte `json:"key_nonce,omitempty"`
KeySalt []byte `json:"key_salt,omitempty"`
MaskedKey *string `json:"masked_key,omitempty"`
ScheduledDisableOn *string `json:"scheduled_disable_on,omitempty"`
}
ExportProvider is a provider with its encrypted key material verbatim.
type ExportUser ¶ added in v0.9.84
type ExportUser struct {
Username string `json:"username"`
DisplayName string `json:"display_name,omitempty"`
Email *string `json:"email,omitempty"`
PasswordHash string `json:"password_hash"`
Role string `json:"role"`
Grants []string `json:"grants"`
Enabled bool `json:"enabled"`
// Aggregate per-user proxy limits (phase 2 of multi-user).
RateLimitRPS *float64 `json:"rate_limit_rps,omitempty"`
RateLimitBurst *int `json:"rate_limit_burst,omitempty"`
RateLimitTPM *int `json:"rate_limit_tpm,omitempty"`
// AllowedProviderNames carries the account provider cap by NAME, with the
// same three-state contract as ExportVK.AllowedProviderNames (nil = no cap,
// non-empty = capped and resolves, present-but-empty = capped with nothing
// resolving on the exporting member). Where a key with an unresolvable cap
// is skipped, a user cannot be: skipping a user this member does not have
// yet makes her keys import unowned, which removes the owner side of the
// proxy's cap intersection outright (see errUnresolvableUserProviders). So
// applyUsers splits the third state by which side failed to resolve - a
// present-but-empty list is written through as an empty array (the primary
// itself resolves nothing, and an empty cap denies everything), while names
// that arrive non-empty and resolve to nothing are anomalous and refuse the
// import. Writing NULL is never an option: it would promote a capped account
// to unrestricted.
AllowedProviderNames *[]string `json:"allowed_provider_names,omitempty"`
}
ExportUser is a dashboard user account, keyed by username. The password hash travels verbatim: it is argon2id-encoded (never plaintext) and the whole envelope only moves between admin-authenticated fleet members. Deliberately NOT wrapped in MASTER_KEY encryption for transit: the envelope uniformly carries what the DB stores (provider keys travel as ciphertext only because they are encrypted at rest), the identical bytes ride the pg_dump backup at the same trust boundary, and argon2id is the one field here actually designed to survive exfiltration (VK sha256 hashes are the weaker neighbours).
type ExportVK ¶ added in v0.9.79
type ExportVK struct {
Name string `json:"name"`
KeyHash string `json:"key_hash"`
KeyPreview string `json:"key_preview"`
RateLimitRPS *float64 `json:"rate_limit_rps,omitempty"`
RateLimitBurst *int `json:"rate_limit_burst,omitempty"`
RateLimitTPM *int `json:"rate_limit_tpm,omitempty"`
// AllowedProviderNames carries the key's provider restriction by NAME
// (UUIDs are instance-local). Three distinct states, and the distinction is
// load-bearing:
// nil - no restriction, every provider
// ["openai"] - restricted, and the names resolve here
// [] (non-nil) - restricted, but NOTHING resolves on this member
// Collapsing the last two is a privilege escalation: a key whose providers
// were all deleted would import as unrestricted.
//
// Two separate JSON mechanisms keep those states distinct on the wire.
// Marshalling: omitempty tests the POINTER, not the slice length, so a
// present-but-empty restriction is emitted as [] rather than dropped.
// Unmarshalling: an absent field leaves the zero value untouched, so an
// older primary that never emits the field yields nil and reads as
// unrestricted, exactly as it did before this became a pointer.
AllowedProviderNames *[]string `json:"allowed_provider_names,omitempty"`
StripReasoning bool `json:"strip_reasoning"`
// OwnerUsername carries key ownership by username (user ids are
// instance-local; usernames are the users sync key). Nil = unowned. An
// owner that does not resolve on the member imports as unowned rather
// than failing the sync.
OwnerUsername *string `json:"owner_username,omitempty"`
}
ExportVK is a virtual key carried by its hash (the plaintext never existed server-side). allowed_providers is carried as provider NAMES, resolved back to this member's provider UUIDs on import.
type FailoverEntryResponse ¶
type FailoverEntryResponse struct {
ModelUUID string `json:"model_uuid"`
ModelID string `json:"model_id"`
ProviderID string `json:"provider_id"`
ProviderName string `json:"provider_name"`
DisplayName string `json:"display_name"`
Enabled bool `json:"enabled"`
ModelEnabled bool `json:"model_enabled"`
ProviderEnabled bool `json:"provider_enabled"`
// DisabledManually distinguishes a user-disabled model from one auto-disabled
// by discovery (model no longer offered by the provider), so the UI can tell
// the operator *why* a member shows N/A instead of just that it is.
DisabledManually bool `json:"disabled_manually"`
ContextLength *int `json:"context_length"`
OwnedBy string `json:"owned_by"`
}
FailoverEntryResponse represents a failover group entry in API responses.
type FailoverGroupBrief ¶
type FailoverGroupBrief struct {
ID string `json:"id"`
DisplayModel string `json:"display_model"`
Position int `json:"position"`
TotalEntries int `json:"total_entries"`
}
FailoverGroupBrief contains brief failover group info for list views.
type FailoverGroupResponse ¶
type FailoverGroupResponse struct {
ID string `json:"id"`
DisplayModel string `json:"display_model"`
DisplayName *string `json:"display_name"`
Description string `json:"description"`
GroupEnabled bool `json:"group_enabled"`
AutoCreated bool `json:"auto_created"`
Entries []FailoverEntryResponse `json:"entries"`
TotalTokens int `json:"total_tokens"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
FailoverGroupResponse represents a failover group in API responses.
type FailoverHandler ¶
type FailoverHandler struct {
// contains filtered or unexported fields
}
FailoverHandler handles failover group API endpoints.
func NewFailoverHandler ¶
func NewFailoverHandler(dbPool *pgxpool.Pool, failoverRepo *failover.Repository, modelRepo *model.Repository, settingsRepo SettingsStore, cb CircuitBreakerControl) *FailoverHandler
NewFailoverHandler creates a new failover group handler.
func (*FailoverHandler) Candidates ¶
func (h *FailoverHandler) Candidates(w http.ResponseWriter, r *http.Request)
Candidates returns available models that can be added to failover groups.
func (*FailoverHandler) CircuitBreakerStatus ¶ added in v0.9.38
func (h *FailoverHandler) CircuitBreakerStatus(w http.ResponseWriter, r *http.Request)
CircuitBreakerStatus returns the current circuit breaker state for all tracked providers.
func (*FailoverHandler) Create ¶
func (h *FailoverHandler) Create(w http.ResponseWriter, r *http.Request)
Create creates a new failover group.
func (*FailoverHandler) Delete ¶
func (h *FailoverHandler) Delete(w http.ResponseWriter, r *http.Request)
Delete deletes a failover group by ID.
func (*FailoverHandler) Get ¶
func (h *FailoverHandler) Get(w http.ResponseWriter, r *http.Request)
Get retrieves a failover group by ID.
func (*FailoverHandler) GetByModelUUID ¶
func (h *FailoverHandler) GetByModelUUID(w http.ResponseWriter, r *http.Request)
GetByModelUUID retrieves a failover group by model UUID.
func (*FailoverHandler) List ¶
func (h *FailoverHandler) List(w http.ResponseWriter, r *http.Request)
List returns all failover groups.
func (*FailoverHandler) Register ¶
func (h *FailoverHandler) Register(r chi.Router)
Register mounts failover group routes on the given router.
func (*FailoverHandler) ResetAllCircuitBreakers ¶ added in v0.9.91
func (h *FailoverHandler) ResetAllCircuitBreakers(w http.ResponseWriter, _ *http.Request)
ResetAllCircuitBreakers clears every tracked circuit at once, for recovering a whole fleet-wide upstream incident without resetting providers one by one.
func (*FailoverHandler) ResetCircuitBreaker ¶ added in v0.9.91
func (h *FailoverHandler) ResetCircuitBreaker(w http.ResponseWriter, r *http.Request)
ResetCircuitBreaker clears one provider's circuit, returning it to rotation immediately instead of waiting out the cooldown. Resetting an untracked or already-closed provider is a successful no-op (reset=false), not an error: the breaker only tracks providers it has routed, so "no circuit" and "closed circuit" are the same healthy state.
func (*FailoverHandler) Sync ¶
func (h *FailoverHandler) Sync(w http.ResponseWriter, r *http.Request)
Sync synchronizes failover groups with model database.
func (*FailoverHandler) Update ¶
func (h *FailoverHandler) Update(w http.ResponseWriter, r *http.Request)
Update updates an existing failover group by ID.
type FailoverListResponse ¶
type FailoverListResponse struct {
Groups []FailoverGroupResponse `json:"groups"`
LastSyncedAt *string `json:"last_synced_at"`
}
FailoverListResponse is the response for listing failover groups.
type FieldChange ¶ added in v0.9.68
type FieldChange struct {
Field string `json:"field"`
Old *float64 `json:"old,omitempty"`
New *float64 `json:"new,omitempty"`
}
FieldChange describes one pricing/context metadata field whose value changed for an existing model between scans. Old/New ride as nullable JSON numbers (context ints and prices alike); a nil pointer means the field was unset. The Field code tells the client how to format the value.
type FleetHandler ¶ added in v0.9.79
type FleetHandler struct {
// contains filtered or unexported fields
}
FleetHandler serves POST /fleet/announce, the admin-authenticated heartbeat Front Desk pings on its poll. It is mounted inside the admin-authenticated /api group (see Handler.Register), so an unauthenticated caller cannot forge a member's fleet state.
func NewFleetHandler ¶ added in v0.9.79
func NewFleetHandler(settings fleetSettings) *FleetHandler
NewFleetHandler builds the member-side fleet handler.
func (*FleetHandler) Announce ¶ added in v0.9.79
func (h *FleetHandler) Announce(w http.ResponseWriter, r *http.Request)
Announce records a Front Desk contact. It writes only routing metadata (timestamps, a primary flag, display names) — never request content — to the instance-local _fleet_* settings, and returns 204. Writes go through Set so the (non-allowlisted) keys are accepted; the same property keeps them out of config-sync's declarative replace.
func (*FleetHandler) Register ¶ added in v0.9.79
func (h *FleetHandler) Register(r chi.Router)
Register mounts POST /fleet/announce. The parent router must apply admin auth.
type FleetStatus ¶ added in v0.9.79
type FleetStatus struct {
// State is one of "primary", "member", "warning", or "member_sync_blocked".
// "member_sync_blocked" is reserved for a later change (when Front Desk
// forwards a blocked-sync signal) and is not emitted yet.
State string `json:"state"`
IsPrimary bool `json:"is_primary"`
PrimaryName string `json:"primary_name,omitempty"`
FrontdeskID string `json:"frontdesk_id,omitempty"`
ManagedSeenAt string `json:"managed_seen_at,omitempty"`
ConfigSyncedAt string `json:"config_synced_at,omitempty"`
}
FleetStatus is the member's own view of its fleet membership, surfaced on the system-status payload. It is nil (omitted) for a standalone instance.
type GroupClaim ¶ added in v0.9.92
type GroupClaim struct {
DisplayModel string `json:"display_model"`
// MemberCount and RoutableCount together are what make the row actionable:
// "1 of 3 members routable" points the operator at a specific broken member,
// where a bare "group disabled" would not. Both are counted live, not read
// back from the journal entry that recorded the disable.
MemberCount int `json:"member_count"`
RoutableCount int `json:"routable_count"`
// DisabledAt is when discovery disabled it (model_failover_groups
// .auto_disabled_at), so the modal can age the row like ModelClaim's
// LastSeenAt.
DisabledAt time.Time `json:"disabled_at"`
}
GroupClaim is one failover group that discovery disabled, i.e. one model name whose `hotel/` routing is dead until someone fixes it. It is the group-level peer of ModelClaim and, like it, is derived from live state on every request: the row disappears from the response the moment the group is re-enabled.
Deleted groups are deliberately NOT represented here. They are not derivable (the row is gone), and both deletion reasons — "no enabled providers found" and "only 1 enabled provider" — are downstream of gone-model claims that are already counted, so claiming them would double-count the root cause and put the journal back in the position of sole evidence. They stay informational.
type Handler ¶
type Handler struct {
// contains filtered or unexported fields
}
Handler manages admin API operations for providers, models, and virtual keys.
func NewHandler ¶
func NewHandler(cfg *config.Config, providerRepo ProviderStore, database *db.DB, adminMgr AdminAuthenticator, vkRepo VirtualKeyStore, settingsRepo SettingsStore, appVersion string, testModelTransport *http.Transport, testModelCheckRedirect func(req *http.Request, via []*http.Request) error, discoveryDialCtx func(ctx context.Context, network, addr string) (net.Conn, error), discoveryCheckRedirect func(req *http.Request, via []*http.Request) error) *Handler
NewHandler creates a new admin API handler with the given dependencies.
func (*Handler) AckDiscoveryChanges ¶ added in v0.9.68
func (h *Handler) AckDiscoveryChanges(w http.ResponseWriter, r *http.Request)
AckDiscoveryChanges atomically marks all unseen background-discovery diffs as seen and returns exactly the rows it cleared, so the client can populate the review modal from this response instead of a possibly-stale poll. Count is 0: the badge is now empty (Entries carries the just-acked rows for display only).
func (*Handler) AdminTokenExchange ¶ added in v0.9.91
func (h *Handler) AdminTokenExchange(w http.ResponseWriter, r *http.Request)
AdminTokenExchange trades a raw global admin token for an HttpOnly session cookie so the dashboard never has to keep the raw admin token in the browser. It is a login front-end (the exchange IS the login) and therefore lives in the auth-exempt route group. When TOTP 2FA is enabled the admin token alone is not a sufficient credential, so this refuses to mint a session and directs callers to the /api/totp/login flow instead.
func (*Handler) AuthLogout ¶ added in v0.9.91
func (h *Handler) AuthLogout(w http.ResponseWriter, r *http.Request)
AuthLogout revokes the caller's session (if any) and clears the auth cookies. Always mounted, unlike the passkey-gated /webauthn/logout, so the dashboard can log out regardless of whether WebAuthn is configured. Safe unauthenticated: it only revokes the token the caller presents and clears the caller's own cookies.
Credential-gated against forced-logout CSRF. A cross-site POST cannot carry either auth cookie (both are SameSite=Strict) nor an Authorization header, so it arrives here bare; emitting Set-Cookie deletions unconditionally would let any third-party page log the victim out. Requests with no credential at all therefore get a success answer with no cookie headers - there is nothing to log out. Same-site logout is unaffected, including an already-revoked or otherwise invalid session, because the cookies still ride along.
Deliberately not gated on the CSRF double-submit header instead: SameSite strips the CSRF cookie from a cross-site request too, so the server cannot tell "no session" from "cross-site", and requiring the header would break bearer-token callers that never have one.
func (*Handler) AuthMiddleware ¶
AuthMiddleware validates admin token or webAuthn session token authentication. Admin token has priority (fast in-memory hash comparison). If the admin token is invalid, the session-based token is tried as a fallback.
func (*Handler) BulkDeleteModels ¶ added in v0.9.91
func (h *Handler) BulkDeleteModels(w http.ResponseWriter, r *http.Request)
BulkDeleteModels removes many models in a single request and resyncs the affected failover groups once at the end. The Models page uses it to clear a large selection without firing one HTTP DELETE per model — a concurrent burst that trips the admin IP rate limiter and surfaces spurious "N failed" toasts.
func (*Handler) ChangeOwnPassword ¶ added in v0.9.84
func (h *Handler) ChangeOwnPassword(w http.ResponseWriter, r *http.Request)
ChangeOwnPassword lets a users-row identity rotate its own password. The current password must be presented (an admin resetting someone else goes through POST /users/{id}/password instead), failed checks back off per user so a hijacked session cannot brute-force the password it rode in on, and on success every session of the account is revoked - including the caller's, which signs back in with the new password. The env-token admin has no users row (its credential is the token itself) and is refused.
func (*Handler) ChatUserContextMiddleware ¶ added in v0.9.92
ChatUserContextMiddleware publishes the calling account's per-user limits to the admin chat routes: its provider cap (users.allowed_providers), its aggregate rate limits (RPS/burst/TPM) and its own id, under the ctxkeys the proxy and the rate limiters already read on every request, whichever surface the request came in on.
The public /v1 proxy gets all of this from ProxyKeyMiddleware (internal/proxy/handler.go), which resolves a virtual key and, when the key is owned, reads its owner's row. /api/chat/* has no key: it authenticates a dashboard session, so the caller IS the account whose limits apply and there is no key side to combine with. The per-KEY ctxkeys (VirtualKeyAllowedProvidersKey, VirtualKeyRateLimit*Key) are therefore deliberately left unset, and each consumer already handles their absence: effectiveAllowedProviders reads a nil key-side cap as "this side restricts nothing" and returns the account cap unchanged, while the two rate limiters handle it differently and neither is left guessing. ratelimit.Limiter keeps its per-key stage on this surface: extractKey falls back to r.RemoteAddr and the bucket is sized from the global settings defaults, which is exactly what an unkeyed request here already got before this middleware existed. ratelimit.TPMLimiter has NO per-key stage here at all — RegisterAdminChat mounts UserMiddleware rather than Middleware precisely because that same address-keyed fallback bucket would be admitted against and never debited (Debit is driven by the virtual-key hash), i.e. a cap that looks enforced and is not.
Without this the `chat` grant was an escape hatch around all of them: it is an ordinary assignable non-admin grant (internal/user/grants.go), so a user could open the dashboard Chat page and reach a provider outside their cap, or burn request and token budget that their /v1 traffic is metered against, while the Users page still displayed the limits.
What each published key buys, verified against the wiring at this commit:
- UserAllowedProvidersKey — read by resolveCandidates (internal/proxy/proxy_request.go) via effectiveAllowedProviders.
- VirtualKeyOwnerIDKey — the shared "user:<uuid>" bucket identity for both rate limiters, and the owner stamped by newPendingRequestLog on the request lifecycle SSE events (which is what scopes a non-admin's live log feed, eventOwnedBy in events.go) and, because this surface has no key to resolve an owner through, on request_logs.owner_user_id itself, which is what puts chat traffic in the caller's own REST logs and stats (migration 067).
- UserRateLimitRPSKey / UserRateLimitBurstKey — read by ratelimit.Limiter.Middleware, which RegisterAdminChat already mounts.
- UserRateLimitTPMKey — read by ratelimit.TPMLimiter.UserMiddleware, mounted on this group by RegisterAdminChat.
It lives in internal/api because reading the users row needs the user store, which internal/proxy deliberately does not depend on. Mount it after AuthMiddleware (which resolves the identity this reads) and before RegisterAdminChat.
func (*Handler) ClearAppLogs ¶
func (h *Handler) ClearAppLogs(w http.ResponseWriter, r *http.Request)
ClearAppLogs clears application logs from the ring buffer and DB, returning the count of entries removed. The request body optionally carries an older_than range token (1h/1d/1w/1m/all); an empty or missing body means "all", preserving the original clear-everything behaviour.
func (*Handler) ClearQuotaAdvice ¶ added in v0.9.91
ClearQuotaAdvice drops all quota advice immediately. Used whenever the in-memory map cannot be trusted to reflect current reality: quota polling has gone from enabled to disabled (the background loop stops calling RefreshQuotaAdvice entirely, so the last computed map would otherwise be retained for the process lifetime), or a poll pass could not even list providers. Safe to call when no advisor was ever wired (no-op).
func (*Handler) CreateProvider ¶
func (h *Handler) CreateProvider(w http.ResponseWriter, r *http.Request)
CreateProvider creates a new provider.
func (*Handler) CreateUser ¶ added in v0.9.84
func (h *Handler) CreateUser(w http.ResponseWriter, r *http.Request)
CreateUser adds a user account.
func (*Handler) CreateVirtualKey ¶
func (h *Handler) CreateVirtualKey(w http.ResponseWriter, r *http.Request)
CreateVirtualKey creates a new virtual API key.
func (*Handler) DeleteModel ¶
func (h *Handler) DeleteModel(w http.ResponseWriter, r *http.Request)
DeleteModel removes a model from the database.
func (*Handler) DeleteProvider ¶
func (h *Handler) DeleteProvider(w http.ResponseWriter, r *http.Request)
DeleteProvider removes a provider by ID and cleans up associated data.
func (*Handler) DeleteUser ¶ added in v0.9.84
func (h *Handler) DeleteUser(w http.ResponseWriter, r *http.Request)
DeleteUser removes a user and revokes their sessions. Self-delete is refused for the same reason as self-disable.
func (*Handler) DeleteVirtualKey ¶
func (h *Handler) DeleteVirtualKey(w http.ResponseWriter, r *http.Request)
DeleteVirtualKey deletes a virtual API key by ID.
func (*Handler) DisableQuotaAdvice ¶ added in v0.9.91
DisableQuotaAdvice is the path taken when quota polling itself is switched off: it drops all advice *and* releases every quota pin already in force.
The two must happen together. Clearing the advice alone stops new pins, but the pins already stamped on would be served out to the 24h ceiling with no refresh left to ever report a recovery — a provider benched for a day on evidence the operator deliberately stopped collecting, from an operator action whose documented meaning is "turn this feature off on this node". Absence of evidence keeps a pin only while the gateway is still looking.
Deliberately not folded into ClearQuotaAdvice, which the failed-refresh paths also call: a database blip is exactly the case where the gateway is still looking and the pins must stand.
func (*Handler) DiscoverAllModels ¶
func (h *Handler) DiscoverAllModels(w http.ResponseWriter, r *http.Request)
DiscoverAllModels discovers and imports models from all enabled providers.
func (*Handler) DiscoverProviderModels ¶
func (h *Handler) DiscoverProviderModels(w http.ResponseWriter, r *http.Request)
DiscoverProviderModels discovers and imports models from a specific provider.
func (*Handler) DismissDiscoveryClaims ¶ added in v0.9.92
func (h *Handler) DismissDiscoveryClaims(w http.ResponseWriter, r *http.Request)
DismissDiscoveryClaims stamps the operator dismissal for models on one provider. setModelsDismissed only touches rows that are currently enabled=false and not manually disabled, so a suspect (still enabled) or healthy model cannot be pre-dismissed; those affect zero rows and fall through the 404 path below like any other unmatched model ID.
Deliberately NOT added to httpx.IsReadOnlyExemptPost: unlike the discovery-change ack it sits beside, this suppresses a real discrepancy from every operator's view, which is a genuine state change.
func (*Handler) GetAlertEvents ¶ added in v0.9.68
func (h *Handler) GetAlertEvents(w http.ResponseWriter, _ *http.Request)
GetAlertEvents returns the static catalog of operator-subscribable events. The dashboard renders its event picker from this, so a new Go-side event surfaces in the UI without any frontend change.
func (*Handler) GetAlertStatus ¶ added in v0.9.68
func (h *Handler) GetAlertStatus(w http.ResponseWriter, r *http.Request)
GetAlertStatus reports whether the configured apprise-api container is reachable, so an unset/wrong URL or a stopped container is visible in the UI rather than failing silently when an event later fires.
func (*Handler) GetAlertTargets ¶ added in v0.9.99
func (h *Handler) GetAlertTargets(w http.ResponseWriter, r *http.Request)
GetAlertTargets (GET /alert/targets) returns the stored destinations in plaintext for the admin UI's readable list; the only place the decrypted list leaves the server. On a normal instance nothing new is revealed: an admin can already write any target and trigger delivery to it.
A read-only demo is the exception, and the reason this handler carries a guard the other two do not. DEMO_SHOW_TOKEN publishes the admin token on the login screen, which config.Load only permits alongside DEMO_READONLY, because that pairing is safe exactly while every admin surface is either non-mutating or non-secret. This read is the one that is neither, so it is refused there rather than handing a visitor the operator's bot tokens. readOnlyGuard cannot do it: it passes every GET through by design, so the dashboard stays browsable.
func (*Handler) GetAppLogs ¶
func (h *Handler) GetAppLogs(w http.ResponseWriter, r *http.Request)
GetAppLogs returns recent application log entries as a JSON array. Supports query parameters:
- ?history=true — query from DB with filtering/pagination (returns paginated response)
- ?limit=N — return at most N entries from ring buffer (default 500, max 1000)
- ?after=<RFC3339 timestamp> — only return entries after the given time (ring buffer mode)
When history=true, additional query parameters are supported:
- ?level=info|warning|error — filter by log level
- ?source=proxy|auth|... — filter by source
- ?search=text — text search in message (ILIKE)
- ?from=<RFC3339> — start timestamp
- ?to=<RFC3339> — end timestamp
- ?page=N — page number (default 1)
- ?per_page=N — page size (default 20, max 100)
- ?sort_by=time|level|source|message — sort column (default: time)
- ?sort_dir=asc|desc — sort direction (default: desc)
func (*Handler) GetAppLogsCursor ¶ added in v0.9.1
func (h *Handler) GetAppLogsCursor(w http.ResponseWriter, r *http.Request)
GetAppLogsCursor returns app logs using keyset (cursor) pagination.
Query parameters:
- cursor: encoded cursor from a previous response
- direction: "after" (default) or "before"
- limit: page size (default 20, max 200)
- level, source, search, from, to: same filters as getAppLogsHistory
- sort_dir: "desc" (default) or "asc"
func (*Handler) GetDemoLogin ¶ added in v0.9.69
func (h *Handler) GetDemoLogin(w http.ResponseWriter, _ *http.Request)
GetDemoLogin returns the admin token for the login screen of a demo instance, or an empty token when the feature is disabled. The token is exposed only when DEMO_SHOW_TOKEN and DEMO_READONLY are both set (publishing the admin credential is acceptable only when every admin mutation is already refused). The response is always 200 with an empty token when disabled, keeping the frontend gate trivial.
It publishes the configured ADMIN_TOKEN only when the admin manager actually accepts it. Validate works whether the token is held in plaintext or only as a stored hash (the normal case after a restart from a persisted volume, where the manager no longer has the plaintext), and returns false when ADMIN_TOKEN is unset or was changed without clearing the token file, so we never advertise a token that would fail to log in. Responses are marked no-store so a proxy or browser never retains the credential after rotation or after the feature is turned off.
func (*Handler) GetDiscoveryStatus ¶ added in v0.9.92
func (h *Handler) GetDiscoveryStatus(w http.ResponseWriter, r *http.Request)
GetDiscoveryStatus derives the current claim set from live model state and pairs it with the informational journal feed.
With ?review=1 (the modal-open fetch) it reads the previous last-reviewed stamp, computes flap counts against it, and only THEN writes the new stamp. Reading before writing is what makes "since your last visit" describe the previous visit instead of collapsing to zero. The 60s badge poll omits the parameter and never writes.
func (*Handler) GetLatestVersion ¶ added in v0.9.6
func (h *Handler) GetLatestVersion(w http.ResponseWriter, r *http.Request)
GetLatestVersion proxies the GitHub latest-release API with server-side caching. This avoids CSP connect-src violations in the browser (the frontend fetches /api/version/latest instead of api.github.com directly).
func (*Handler) GetLog ¶ added in v0.9.10
func (h *Handler) GetLog(w http.ResponseWriter, r *http.Request)
GetLog returns a single request log entry by ID.
func (*Handler) GetOllamaCloudAccount ¶
func (h *Handler) GetOllamaCloudAccount(w http.ResponseWriter, r *http.Request)
GetOllamaCloudAccount serves Ollama Cloud account info from the read-through snapshot store (cold-filling on first view).
func (*Handler) GetProvider ¶
func (h *Handler) GetProvider(w http.ResponseWriter, r *http.Request)
GetProvider returns a single provider by ID.
func (*Handler) GetProviderBalance ¶
func (h *Handler) GetProviderBalance(w http.ResponseWriter, r *http.Request)
GetProviderBalance serves balance information for a provider from the read-through snapshot store (cold-filling on first view).
func (*Handler) GetProviderUsage ¶
func (h *Handler) GetProviderUsage(w http.ResponseWriter, r *http.Request)
GetProviderUsage serves usage/quota information for a provider from the read-through snapshot store (cold-filling on first view).
func (*Handler) GetPublicConfig ¶ added in v0.9.66
func (h *Handler) GetPublicConfig(w http.ResponseWriter, _ *http.Request)
GetPublicConfig returns the feature flags that are safe to expose without authentication. Currently just read-only (demo) mode.
func (*Handler) GetSettings ¶
func (h *Handler) GetSettings(w http.ResponseWriter, r *http.Request)
GetSettings returns all settings as a key-value map.
func (*Handler) GetVirtualKey ¶
func (h *Handler) GetVirtualKey(w http.ResponseWriter, r *http.Request)
GetVirtualKey retrieves a virtual API key by ID.
func (*Handler) ListAudit ¶ added in v0.9.84
func (h *Handler) ListAudit(w http.ResponseWriter, r *http.Request)
ListAudit returns audit entries newest-first. Query params: cursor (base64 of {created_at,id}) for infinite scroll, or offset for the page-numbered view; limit (default 50, max 200), actor, method, from, to (RFC3339).
func (*Handler) ListAuthSessions ¶ added in v0.9.97
func (h *Handler) ListAuthSessions(w http.ResponseWriter, r *http.Request)
ListAuthSessions returns the caller's live sessions for the settings panel: device metadata, timestamps, and which row is the calling session. Identity scoping happens in the manager off the middleware-resolved identity, so a caller can only ever see their own sessions.
func (*Handler) ListGrantCatalog ¶ added in v0.9.84
func (h *Handler) ListGrantCatalog(w http.ResponseWriter, _ *http.Request)
ListGrantCatalog returns the valid grant keys so the edit modal renders its checkboxes from the backend catalog instead of a hardcoded copy.
func (*Handler) ListLogs ¶
func (h *Handler) ListLogs(w http.ResponseWriter, r *http.Request)
ListLogs returns paginated request logs with filtering and sorting.
func (*Handler) ListLogsCursor ¶ added in v0.9.1
func (h *Handler) ListLogsCursor(w http.ResponseWriter, r *http.Request)
ListLogsCursor returns request logs using keyset (cursor) pagination.
Query parameters:
- cursor: encoded cursor from a previous response (base64 JSON of {created_at, id})
- direction: "after" (default) or "before" — which way to scroll from cursor
- limit: page size (default 20, max 200)
- model_id, provider_id, virtual_key_id, client_ip, status_code, from, to: same filters as ListLogs
- sort_by: only "time" is supported for cursor pagination (default "time")
- sort_dir: "desc" (default, newest first) or "asc"
The first request omits cursor to get the newest entries. Subsequent requests pass the cursor from the response boundary and direction to scroll older ("before") or newer ("after").
func (*Handler) ListModels ¶
func (h *Handler) ListModels(w http.ResponseWriter, r *http.Request)
ListModels returns all models with optional provider filtering.
func (*Handler) ListModelsCursor ¶ added in v0.9.2
func (h *Handler) ListModelsCursor(w http.ResponseWriter, r *http.Request)
ListModelsCursor returns models using keyset (cursor) pagination.
Query parameters:
- cursor: encoded cursor from a previous response
- direction: "after" (default) or "before"
- limit: page size (default 50, max 200)
- sort_by: "name" (default), "discovered", "context", "output", "provider", "status"
- sort_dir: "asc" (default) or "desc"
- search: text search on model_id, name, display_name
- provider_id: filter by provider UUID
- capabilities: comma-separated capability keys (e.g. "vision,reasoning")
- outputs: comma-separated output modalities (e.g. "image,embedding")
- provider_enabled: "true" or "false" to filter on the owning provider's flag
- enabled: "true" or "false" to filter on the model's own enabled flag
func (*Handler) ListProviders ¶
func (h *Handler) ListProviders(w http.ResponseWriter, r *http.Request)
ListProviders returns all configured providers.
func (*Handler) ListUsers ¶ added in v0.9.84
func (h *Handler) ListUsers(w http.ResponseWriter, r *http.Request)
ListUsers returns all users (password hashes never serialize).
func (*Handler) ListVirtualKeys ¶
func (h *Handler) ListVirtualKeys(w http.ResponseWriter, r *http.Request)
ListVirtualKeys returns virtual API keys: all of them for admins, only the caller's own for grant-holding users.
func (*Handler) Me ¶ added in v0.9.84
func (h *Handler) Me(w http.ResponseWriter, r *http.Request)
Me reports the caller's resolved identity. Mounted inside the authenticated group, so an identity is always present.
func (*Handler) MetricsHandler ¶ added in v0.9.55
MetricsHandler returns the authenticated Prometheus /metrics handler and registers the live circuit-breaker-state collector. Authentication: when METRICS_TOKEN is set it must match, presented as an Authorization: Bearer header; otherwise the admin token / passkey session is required. The endpoint is never served unauthenticated.
func (*Handler) NudgeQuotaPoll ¶ added in v0.9.95
NudgeQuotaPoll refreshes one provider's quota snapshot out of band and rebuilds the advice from it. The background pass polls every few minutes, so a circuit that opens because the provider's window is spent can be most of a cycle away from the reading that would pin its cooldown to the real reset time, and every probe it lets through until then is a guaranteed 429.
The refresh this ends with retargets circuits that are already open, so the reading reaches the very circuit whose opening asked for it rather than only governing the next one to open.
The upstream call runs on its own goroutine under a fresh context, so it never adds latency to whatever opened the circuit and never inherits that caller's cancellation. Providers that serve no traffic or expose no quota endpoint are rejected before any of that.
func (*Handler) PollQuotasOnce ¶ added in v0.9.91
PollQuotasOnce refreshes the snapshot for every enabled quota-capable provider. Called by the background quota loop. Each provider fetch is bounded by its own timeout so one slow upstream cannot stall the pass, and failures are recorded (via RecordFailure) without discarding the last good snapshot.
func (*Handler) ProbeAlert ¶ added in v0.9.99
func (h *Handler) ProbeAlert(w http.ResponseWriter, r *http.Request)
ProbeAlert (POST /alert/probe) checks an apprise-api URL the operator typed but has not saved; the setup wizard gates its first step on it. Admin-only, same netguard client as every outbound call; an admin could already save any URL and hit /alert/status, so this adds no capability.
func (*Handler) PurgeAudit ¶ added in v0.9.84
func (h *Handler) PurgeAudit(w http.ResponseWriter, r *http.Request)
PurgeAudit deletes old audit entries using the same older_than vocabulary as the request-log purge. The purge itself is a mutating request, so it is recorded by the audit middleware - a wiped trail always shows who wiped it.
func (*Handler) PurgeLogs ¶
func (h *Handler) PurgeLogs(w http.ResponseWriter, r *http.Request)
PurgeLogs deletes old request logs based on the specified time range.
func (*Handler) RefreshAllQuotas ¶
func (h *Handler) RefreshAllQuotas(w http.ResponseWriter, r *http.Request)
RefreshAllQuotas refreshes quota information for all providers that support it.
func (*Handler) RefreshQuotaAdvice ¶ added in v0.9.91
RefreshQuotaAdvice rebuilds the in-memory quota advice from stored snapshots. It reads the table rather than the poll's own fetches so fleet-distributed snapshots (which PollQuotasOnce skips) are included.
A snapshot older than three refresh intervals is ignored. If quota polling is disabled (quota_refresh_interval_min <= 0) the resolved maxAge is <= 0, and buildQuotaAdvice treats that as "advise nothing": we cannot trust the age of any stored snapshot without a live poll cadence to bound it, so never pin the breaker on data that could predate a plan change or a manual top-up.
func (*Handler) RefreshTotpEnabled ¶ added in v0.9.71
RefreshTotpEnabled re-reads IsEnabled from the DB and updates the cache. Called by the TOTP enroll-verify and disable handlers AFTER their DB mutations succeed. On DB error it fails closed (sets true).
func (*Handler) RegisterAlerts ¶ added in v0.9.68
RegisterAlerts mounts the alerting API routes:
GET /alert/events — the alertable-event catalog that feeds the picker.
GET /alert/status — reachability of the saved apprise-api configuration.
POST /alert/probe — reachability of an apprise-api URL not yet saved.
POST /alert/test — send a test notification through the saved or an
explicit configuration.
GET /alert/targets — the saved destinations, decrypted for the admin UI.
func (*Handler) RegisterAppLogs ¶
RegisterAppLogs registers the app logs endpoint on the given router.
func (*Handler) RegisterAudit ¶ added in v0.9.84
RegisterAudit mounts the admin-only audit-trail routes.
func (*Handler) RegisterAuthExchange ¶ added in v0.9.91
RegisterAuthExchange mounts the admin-token exchange endpoint and the always-available logout endpoint. Both must be registered in the auth-exempt group: the exchange runs before any session exists, and logout must work even for an already-expired or otherwise invalid session. Mounted under /api, this resolves to POST /api/auth/admin-exchange and POST /api/auth/logout.
func (*Handler) RegisterDemoLogin ¶ added in v0.9.69
RegisterDemoLogin mounts the unauthenticated demo-login route. It is registered outside the admin-auth group (see cmd/server) so the SPA can read it on the login screen, exactly like RegisterPublicConfig.
func (*Handler) RegisterEvents ¶
RegisterEvents registers the SSE endpoint on a route group that is exempt from the chi Timeout middleware. SSE connections are long-lived and must not be killed by a 60-second request deadline; the handler detects client disconnect via r.Context().Done() instead.
func (*Handler) RegisterLogs ¶
RegisterLogs mounts log management routes.
func (*Handler) RegisterModels ¶
RegisterModels mounts model management routes.
func (*Handler) RegisterProviderDiscovery ¶
RegisterProviderDiscovery mounts provider discovery and usage routes.
func (*Handler) RegisterPublicConfig ¶ added in v0.9.66
RegisterPublicConfig mounts the unauthenticated public-config route. It is deliberately registered outside the admin-auth group (see cmd/server) so the frontend can read it on the login screen as well as inside the dashboard.
func (*Handler) RegisterSettings ¶
RegisterSettings mounts settings API routes.
func (*Handler) RegisterUserTotp ¶ added in v0.9.84
RegisterUserTotp mounts the self-service TOTP endpoints for users-row identities. Mounted inside the authenticated group; every handler resolves the caller's own user id from the request identity, so no cross-user access is expressible. The env-token admin has no users row and is pointed at the existing single-admin /api/totp machinery instead. The user_totp tables are instance-local (not fleet-synced), so there is no managedWriteGuard here.
func (*Handler) RegisterUsers ¶ added in v0.9.84
RegisterUsers mounts the user management API. Admin-only: users cannot see or edit each other, and grants never unlock this surface.
Reads (the roster, the grant catalog) stay open on a managed fleet member: the list matches the primary's, and the operator may browse it. Writes are guarded by managedWriteGuard because the user roster is synced config — applyUsers deletes any account absent from the primary's export on the next sync, so a local create/edit/delete would "succeed" and then be undone.
func (*Handler) RegisterVersion ¶ added in v0.9.6
RegisterVersion mounts the version check route.
func (*Handler) RegisterVirtualKeys ¶
RegisterVirtualKeys mounts virtual key management routes.
func (*Handler) RequireGrant ¶ added in v0.9.84
RequireGrant is the exported guard for routes mounted outside Register (the admin chat group in main.go). Method on Handler only so callers reach it through the wired API handler.
func (*Handler) ResetSettings ¶ added in v0.9.46
func (h *Handler) ResetSettings(w http.ResponseWriter, r *http.Request)
ResetSettings deletes specified settings keys from the database so they fall through to their Go-side defaults. An empty keys list resets all settings. Returns the full updated settings map.
func (*Handler) ResetUserTotp ¶ added in v0.9.84
func (h *Handler) ResetUserTotp(w http.ResponseWriter, r *http.Request)
ResetUserTotp is the admin lockout-recovery path: it unconditionally disables TOTP and deletes the recovery codes for the target user. Admin-only (mounted under /users). Not managed-write-guarded: user_totp is instance-local state that a fleet sync never touches.
func (*Handler) RevokeAuthSessionByID ¶ added in v0.9.97
func (h *Handler) RevokeAuthSessionByID(w http.ResponseWriter, r *http.Request)
RevokeAuthSessionByID signs one of the caller's sessions out by id: the per-row action of the active-sessions list. Missing and foreign ids both read as 404 (a distinct answer would confirm the id exists); the session the request rides on is a 409, since ending it is what logout is for.
func (*Handler) RevokeOtherSessions ¶ added in v0.9.94
func (h *Handler) RevokeOtherSessions(w http.ResponseWriter, r *http.Request)
RevokeOtherSessions signs the caller's other sessions out, keeping the one this request was made from.
This is the operator's lever for the gap the session TTL only bounds: logging in does not revoke an existing session, so a stolen token stays usable until it expires. Making it an explicit action rather than an automatic revoke-on-login is deliberate. Three admin login front-ends mint sessions under one shared identity, so automatic revocation would evict the operator's other devices on every routine login, training them to ignore it. Here they ask for it, and it happens when they mean it.
Whose sessions are ended comes from the identity the auth middleware resolved, never from a credential read back off the request. Those two can disagree: resolveCredentials falls through to the bearer when the session cookie is invalid, so a caller presenting a valid bearer alongside a junk cookie authenticates as themselves while the junk cookie would have decided the target. Reading the identity from the request is how any authenticated account could have aimed this at the admin handle and signed every admin session out.
func (*Handler) SendAlertTest ¶ added in v0.9.68
func (h *Handler) SendAlertTest(w http.ResponseWriter, r *http.Request)
SendAlertTest (POST /alert/test) fires a test notification. With no body it uses the saved configuration (the card's Send test). The wizard sends {api_url, targets} to test explicit values before either is saved; either field may be omitted to fall back to the saved value.
func (*Handler) SetAudit ¶ added in v0.9.84
SetAudit wires the audit recorder: its middleware records every mutating request on the authenticated API, and the admin-only /audit routes read the trail. Nil (tests without a recorder) disables both.
func (*Handler) SetCircuitBreaker ¶ added in v0.9.38
func (h *Handler) SetCircuitBreaker(cb CircuitBreakerControl)
SetCircuitBreaker wires the proxy's circuit breaker so the API can publish its status (failover page, sidebar badge, /metrics) and reset it: there is exactly one breaker, so status and the operator reset lever come from the same object.
func (*Handler) SetClientIPSource ¶ added in v0.9.97
func (h *Handler) SetClientIPSource(ips webauthn.ClientIPSource)
SetClientIPSource wires the trusted-proxy-aware client-IP resolver (the IP limiter) used for session device metadata at the admin-token exchange. Left nil, forwarded headers are never trusted and the peer address is stored.
func (*Handler) SetDockerStatsCollector ¶
func (h *Handler) SetDockerStatsCollector(fn dockerStatsCollector)
SetDockerStatsCollector overrides the system Docker stats collector (for testing).
func (*Handler) SetPwnedChecker ¶ added in v0.9.91
func (h *Handler) SetPwnedChecker(c PwnedChecker)
SetPwnedChecker wires the breached-password checker used by the user create/reset/change flows. Leaving it unset disables the check regardless of config (the check is skipped, never blocking).
func (*Handler) SetQuotaAdvisor ¶ added in v0.9.91
func (h *Handler) SetQuotaAdvisor(a *QuotaAdvisor)
SetQuotaAdvisor wires the in-memory quota advisor that RefreshQuotaAdvice populates from stored snapshots on every poll. Call during startup wiring; leaving it unset makes RefreshQuotaAdvice a no-op.
func (*Handler) SetTotpStatus ¶ added in v0.9.71
func (h *Handler) SetTotpStatus(src TotpStatus)
SetTotpStatus wires the TOTP status source and best-effort seeds the cache. On seed error it fails closed (treats as enabled) so a DB blip at startup cannot silently disable 2FA if it was previously enabled.
func (*Handler) SetUserAuth ¶ added in v0.9.84
func (h *Handler) SetUserAuth(users UserStore, revoker SessionRevoker)
SetUserAuth wires the multi-user store and session revoker into the auth middleware and users admin API. Without it, sessions carrying user UUIDs fail closed (401) and the Users API is not mounted usefully.
func (*Handler) SetUserPassword ¶ added in v0.9.84
func (h *Handler) SetUserPassword(w http.ResponseWriter, r *http.Request)
SetUserPassword resets a user's password and revokes their sessions, so a reset always forces a fresh login (compromised-credential hygiene).
func (*Handler) SetUserTotp ¶ added in v0.9.84
func (h *Handler) SetUserTotp(factory UserTotpFactory)
SetUserTotp wires the per-user TOTP factory into the self-service and admin-reset endpoints.
func (*Handler) SetWebAuthnSessionManager ¶ added in v0.9.34
func (h *Handler) SetWebAuthnSessionManager(mgr WebAuthnSessionManager)
SetWebAuthnSessionManager sets the optional webAuthn session manager for token-based authentication fallback in AuthMiddleware.
func (*Handler) StartBackupScheduler ¶ added in v0.9.47
StartBackupScheduler starts the periodic backup scheduler if backup_enabled is true. Call this only after Register, which constructs the BackupHandler and assigns it as h.backupScheduler; calling earlier leaves the scheduler nil and no backups ever run.
func (*Handler) StopBackupScheduler ¶ added in v0.9.47
func (h *Handler) StopBackupScheduler()
StopBackupScheduler stops the periodic backup scheduler.
func (*Handler) StreamEvents ¶
func (h *Handler) StreamEvents(w http.ResponseWriter, r *http.Request)
StreamEvents handles server-sent events for real-time dashboard updates.
The caller's identity is re-checked on every heartbeat rather than pinned at connect. AuthMiddleware only runs once, so a stream opened before a session was revoked, or before a user was disabled or had a grant taken away, would otherwise keep delivering events under its connect-time permissions for as long as the client held the socket open - unbounded, since the heartbeat keeps it alive. Re-resolving costs one session lookup per stream per heartbeat interval; once the credential stops resolving the stream closes and the client's normal SSE reconnect then fails at the middleware.
func (*Handler) TestModel ¶
func (h *Handler) TestModel(w http.ResponseWriter, r *http.Request)
TestModel tests a model by making a test request and returning latency metrics.
func (*Handler) TotpEnabled ¶ added in v0.9.71
TotpEnabled reports the cached TOTP-enabled state. Per-request hot path: no DB hit. Returns false when the feature is not wired (nil source).
func (*Handler) UnpinDiscoveryClaims ¶ added in v0.9.99
func (h *Handler) UnpinDiscoveryClaims(w http.ResponseWriter, r *http.Request)
UnpinDiscoveryClaims drops the operator pin from models on one provider, returning them to discovery's listing-based auto-disable with a clean miss-streak. setModelsUnpinned only touches rows that actually carry a pin, so an already-unpinned model affects zero rows and falls through the 404 path below like any other unmatched model ID.
No model cache invalidation: the pin and the miss-streak live only in the database. model.Model carries neither, so no cached entry can go stale on this write, and the dismiss endpoint beside it invalidates nothing for the same reason. What does change on the next scan — the model being disabled — flows through the same path any auto-disable does.
Deliberately NOT added to httpx.IsReadOnlyExemptPost: it hands a model back to automatic management, which is a genuine state change.
func (*Handler) UpdateModel ¶
func (h *Handler) UpdateModel(w http.ResponseWriter, r *http.Request)
UpdateModel updates model configuration (enabled status, pricing overrides).
func (*Handler) UpdateProvider ¶
func (h *Handler) UpdateProvider(w http.ResponseWriter, r *http.Request)
UpdateProvider updates an existing provider by ID.
func (*Handler) UpdateSettings ¶
func (h *Handler) UpdateSettings(w http.ResponseWriter, r *http.Request)
UpdateSettings updates user settings in the database.
func (*Handler) UpdateUser ¶ added in v0.9.84
func (h *Handler) UpdateUser(w http.ResponseWriter, r *http.Request)
UpdateUser rewrites profile fields. Disabling a user revokes their live sessions immediately; self-disable is refused so an admin editing their own row cannot saw off the branch they sit on (the env token would still work, but the footgun is cheap to remove).
func (*Handler) UpdateVirtualKey ¶
func (h *Handler) UpdateVirtualKey(w http.ResponseWriter, r *http.Request)
UpdateVirtualKey updates a virtual API key.
func (*Handler) UserTotpDisable ¶ added in v0.9.84
func (h *Handler) UserTotpDisable(w http.ResponseWriter, r *http.Request)
UserTotpDisable turns the caller's second factor off. Requires a valid current TOTP or recovery code (401 on mismatch), consumed atomically with the disable so a transient failure cannot burn the code and leave 2FA on. Failed codes back off per user (shared with the password-change throttle): a hijacked session must not be a free brute-force oracle for the 6-digit window, matching the login and password-change paths.
func (*Handler) UserTotpEnrollStart ¶ added in v0.9.84
func (h *Handler) UserTotpEnrollStart(w http.ResponseWriter, r *http.Request)
UserTotpEnrollStart generates a provisional secret for the caller and returns the otpauth URI (QR) + base32 secret. Refused while TOTP is active, mirroring the admin flow: rotating requires an authorized disable first, so the enforcement gate never moves under a live second factor.
func (*Handler) UserTotpEnrollVerify ¶ added in v0.9.84
func (h *Handler) UserTotpEnrollVerify(w http.ResponseWriter, r *http.Request)
UserTotpEnrollVerify confirms the provisional secret with a live code, enables the second factor, and returns the single-use recovery codes (generated BEFORE enable so a failure can never leave 2FA on without them). The caller's session stays valid: unlike the admin flow, enabling a user's TOTP changes login requirements only, not the bearer they already hold.
func (*Handler) UserTotpStatus ¶ added in v0.9.84
func (h *Handler) UserTotpStatus(w http.ResponseWriter, r *http.Request)
UserTotpStatus reports the caller's own TOTP state for the Security UI.
type HealthHandler ¶ added in v0.9.79
type HealthHandler struct {
// contains filtered or unexported fields
}
HealthHandler answers GET /health. It reports 200 OK with body "OK" when the database is reachable and 503 with body "DEGRADED" when it is not, so a load balancer stops routing to an instance whose Postgres is down. The result is cached for healthCacheTTL and only one probe runs at a time.
func NewHealthHandler ¶ added in v0.9.79
func NewHealthHandler(pinger healthPinger) *HealthHandler
NewHealthHandler builds a HealthHandler probing the given pool.
func (*HealthHandler) ServeHTTP ¶ added in v0.9.79
func (h *HealthHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
ServeHTTP implements http.Handler.
type LogEntry ¶
type LogEntry struct {
ID string `json:"id"`
ProviderID string `json:"provider_id"`
ProviderName string `json:"provider_name"`
ModelID string `json:"model_id"`
RequestHash string `json:"request_hash"`
StatusCode int `json:"status_code"`
LatencyMs float64 `json:"latency_ms"`
DurationMs float64 `json:"duration_ms"`
TTFTMs float64 `json:"ttft_ms"`
ResponseHeaderMs float64 `json:"response_header_ms"`
ProxyOverheadMs float64 `json:"proxy_overhead_ms"`
ParseMs float64 `json:"parse_ms"`
FailoverLookupMs float64 `json:"failover_lookup_ms"`
ModelLookupMs float64 `json:"model_lookup_ms"`
ProviderLookupMs float64 `json:"provider_lookup_ms"`
KeyDecryptMs float64 `json:"key_decrypt_ms"`
DialMs float64 `json:"dial_ms"`
SettingsReadMs float64 `json:"settings_read_ms"`
CacheHits *CacheHits `json:"cache_hits,omitempty"`
TokensPerSecond float64 `json:"tokens_per_second"`
TokensPrompt int `json:"tokens_prompt"`
TokensCompletion int `json:"tokens_completion"`
TokensCompletionReasoning int `json:"tokens_completion_reasoning"`
TokensPromptCacheHit int `json:"tokens_prompt_cache_hit"`
TokensPromptCacheMiss int `json:"tokens_prompt_cache_miss"`
Streaming bool `json:"streaming"`
VirtualKeyName string `json:"virtual_key_name"`
VirtualKeyDeleted bool `json:"virtual_key_deleted"`
VirtualKeyID string `json:"virtual_key_id"`
ClientIP string `json:"client_ip"` // "" for rows predating migration 073 or address-less ingest paths
ErrorMessage string `json:"error_message"`
ErrorKind string `json:"error_kind"` // "" when unclassified (legacy rows); frontend falls back to substring matching
FailoverAttempt int `json:"failover_attempt"`
State string `json:"state"`
CreatedAt time.Time `json:"created_at"`
ResolvedModelID string `json:"resolved_model_id"`
EndpointType string `json:"endpoint_type"`
}
LogEntry represents a single request log entry.
type LogsCursorResponse ¶ added in v0.9.1
type LogsCursorResponse struct {
Entries []LogEntry `json:"entries"`
Total int `json:"total"`
HasBefore bool `json:"has_before"`
HasAfter bool `json:"has_after"`
}
LogsCursorResponse is the cursor-based paginated response for request logs.
type LogsResponse ¶
type LogsResponse struct {
Entries []LogEntry `json:"entries"`
Total int `json:"total"`
Page int `json:"page"`
PerPage int `json:"per_page"`
}
LogsResponse is the paginated response for request logs.
type ModelChange ¶ added in v0.9.52
type ModelChange struct {
ModelID string `json:"model_id"`
Reason string `json:"reason"` // machine-readable: new_model | reappeared | not_listed
}
ModelChange describes one model affected by a discovery scan.
type ModelClaim ¶ added in v0.9.92
type ModelClaim struct {
ModelID string `json:"model_id"`
State ClaimState `json:"state"`
// LastSeenAt is when the provider last listed the model, which for a gone
// model is when it went missing.
LastSeenAt time.Time `json:"last_seen_at"`
MissingScans int `json:"missing_scans"`
// FlapWindow counts membership transitions over ClaimWindow;
// FlapSinceReview counts them since the operator last opened the modal.
FlapWindow int `json:"flap_window"`
FlapSinceReview int `json:"flap_since_review"`
// RetiredAt is when the proxy retired it from traffic, set only on a retired
// claim. LastSeenAt cannot serve here: the provider still lists the model, so
// it keeps being refreshed and would read as "last seen a minute ago" beside
// a row saying the model is unavailable.
RetiredAt *time.Time `json:"retired_at,omitempty"`
// PinnedAt is when the operator enabled the model by hand, set only on a
// pinned claim. It dates the decision the pin records, which LastSeenAt
// cannot: that is when the provider last listed the model, i.e. the fact the
// operator overrode.
PinnedAt *time.Time `json:"pinned_at,omitempty"`
}
ModelClaim is one model's current standing.
type ModelResponse ¶
type ModelResponse struct {
ID string `json:"id"`
ModelID string `json:"model_id"`
Name string `json:"name"`
Description string `json:"description"`
DisplayName string `json:"display_name"`
ProviderID string `json:"provider_id"`
ProviderName string `json:"provider_name"`
ProviderEnabled bool `json:"provider_enabled"`
Capabilities string `json:"capabilities"`
Params string `json:"params"`
Modality string `json:"modality"`
InputModalities string `json:"input_modalities"`
OutputModalities string `json:"output_modalities"`
ContextLength *int `json:"context_length"`
MaxOutputTokens *int `json:"max_output_tokens"`
InputPricePerMillion *float64 `json:"input_price_per_million"`
InputPricePerMillionCacheHit *float64 `json:"input_price_per_million_cache_hit"`
OutputPricePerMillion *float64 `json:"output_price_per_million"`
OwnedBy string `json:"owned_by"`
Enabled bool `json:"enabled"`
DisabledManually bool `json:"disabled_manually"`
PriceCustomized bool `json:"price_customized"`
CreatedAt string `json:"created_at"`
LastSeenAt string `json:"last_seen_at"`
}
ModelResponse is the JSON response format for model API endpoints.
type ModelSnapshot ¶ added in v0.9.68
type ModelSnapshot struct {
// contains filtered or unexported fields
}
ModelSnapshot captures a model's pre-scan state — whether it was routable and whether the operator pinned it, plus the pricing/context fields compared to detect metadata changes. Why it carries no disabled_manually or auto_retired_at: what the scan did about either is read off the row Upsert returned, not re-derived here. The type is exported so the scheduled discovery loop (package main) can hold the snapshot returned by SnapshotProviderModels and pass it to BuildDiscoveryDiff; its fields stay package-private.
type ModelUpdate ¶ added in v0.9.68
type ModelUpdate struct {
ModelID string `json:"model_id"`
Changes []FieldChange `json:"changes"`
}
ModelUpdate groups the metadata field changes detected for one existing model.
type ModelsCursorResponse ¶ added in v0.9.2
type ModelsCursorResponse struct {
Entries []ModelResponse `json:"entries"`
// Total counts every row matching the filters. EnabledTotal counts the
// subset the proxy can serve (model enabled AND provider enabled), the same
// rule /v1/models applies, so the page title can report usable models even
// though only one page of rows is loaded.
Total int `json:"total"`
EnabledTotal int `json:"enabled_total"`
// ParkedTotal counts rows whose provider is disabled: listed, kept, but not
// served until the provider is enabled again.
ParkedTotal int `json:"parked_total"`
// DisabledTotal counts rows whose own enabled flag is off, parked or not:
// exactly the rows the Models page's "delete disabled" removes for these
// filters, so the button and the delete agree without loading every row.
DisabledTotal int `json:"disabled_total"`
HasBefore bool `json:"has_before"`
HasAfter bool `json:"has_after"`
}
ModelsCursorResponse is the cursor-based paginated response for models.
type ProviderClaims ¶ added in v0.9.92
type ProviderClaims struct {
ProviderID string `json:"provider_id"`
ProviderName string `json:"provider_name"`
Gone []ModelClaim `json:"gone"`
Stale []ModelClaim `json:"stale"`
Suspect []ModelClaim `json:"suspect"`
Retired []ModelClaim `json:"retired"`
Pinned []ModelClaim `json:"pinned"`
}
ProviderClaims groups one provider's claims by state.
type ProviderDistributionItem ¶
type ProviderDistributionItem struct {
Name string `json:"name"`
Count int `json:"count"`
Tokens int `json:"tokens"`
}
ProviderDistributionItem holds a single slice of the provider breakdown.
type ProviderDistributionStats ¶
type ProviderDistributionStats struct {
Items []ProviderDistributionItem `json:"items"`
}
ProviderDistributionStats holds the provider share pie data.
type ProviderLatencyEntry ¶ added in v0.9.43
type ProviderLatencyEntry struct {
ProviderName string `json:"provider_name"`
TotalMs float64 `json:"total_ms"`
OverheadMs float64 `json:"overhead_ms"`
ProviderMs float64 `json:"provider_ms"`
RequestCount int `json:"request_count"`
}
ProviderLatencyEntry holds per-provider latency breakdown for the dashboard.
type ProviderStore ¶
type ProviderStore interface {
Create(ctx context.Context, req provider.CreateProviderRequest, encryptedKey, keyNonce, keySalt []byte) (*provider.Provider, error)
List(ctx context.Context) ([]*provider.Provider, error)
Get(ctx context.Context, id uuid.UUID) (*provider.Provider, error)
GetByName(ctx context.Context, name string) (*provider.Provider, error)
Update(ctx context.Context, id uuid.UUID, req provider.UpdateProviderRequest, encryptedKey, keyNonce, keySalt []byte) (*provider.Provider, error)
Delete(ctx context.Context, id uuid.UUID) error
}
ProviderStore defines the provider repository methods used by the API.
type PublicConfigResponse ¶ added in v0.9.66
type PublicConfigResponse struct {
ReadOnly bool `json:"read_only"`
}
PublicConfigResponse is the unauthenticated subset of server configuration the SPA needs to render correctly before/independent of admin auth. It must only ever carry non-sensitive feature flags — never tokens, URLs, or secrets.
type PurgeLogsRequest ¶
type PurgeLogsRequest struct {
OlderThan string `json:"older_than"`
}
PurgeLogsRequest is the request body for purging logs.
type PwnedChecker ¶ added in v0.9.91
PwnedChecker reports whether a password appears in a known breach corpus. Satisfied by *pwned.Checker in production and by a stub in tests; nil when the feature is not wired.
type QuotaAdvisor ¶ added in v0.9.91
type QuotaAdvisor struct {
// contains filtered or unexported fields
}
QuotaAdvisor holds the latest per-provider quota reset deadlines in memory so the circuit breaker can consult them under its write lock without touching the database. Refreshed by the quota poller; satisfies failover.QuotaAdvisor.
func NewQuotaAdvisor ¶ added in v0.9.91
func NewQuotaAdvisor() *QuotaAdvisor
NewQuotaAdvisor returns an empty advisor. Until the first refresh it declines every lookup, so the breaker uses its configured cooldown.
func (*QuotaAdvisor) Replace ¶ added in v0.9.91
func (a *QuotaAdvisor) Replace(m map[uuid.UUID]time.Time)
Replace swaps the whole map so providers that recovered stop being advised. Replace takes ownership of m: the caller must not read or mutate it after the call, since ResetsAt may be reading it concurrently under RLock with no synchronization against a caller-side write.
type QuotaFleetHandler ¶ added in v0.9.91
type QuotaFleetHandler struct {
// contains filtered or unexported fields
}
QuotaFleetHandler serves and receives fleet quota snapshots. It mounts on the same fleet-authed router as config-sync (see ConfigSyncHandler.Register), so it inherits that router's fleet auth. Quota snapshots carry no key material, so unlike config import there is no MASTER_KEY canary.
func NewQuotaFleetHandler ¶ added in v0.9.91
func NewQuotaFleetHandler(quotaRepo *quota.Repository, providerRepo ProviderStore) *QuotaFleetHandler
NewQuotaFleetHandler builds a QuotaFleetHandler.
func (*QuotaFleetHandler) ExportSnapshots ¶ added in v0.9.91
func (h *QuotaFleetHandler) ExportSnapshots(w http.ResponseWriter, r *http.Request)
ExportSnapshots serves this node's quota snapshots keyed by provider name so a consumer maps them onto its own provider IDs.
func (*QuotaFleetHandler) ReceiveSnapshots ¶ added in v0.9.91
func (h *QuotaFleetHandler) ReceiveSnapshots(w http.ResponseWriter, r *http.Request)
ReceiveSnapshots stores fleet-distributed snapshots, mapping each by provider name onto this member's own provider IDs and writing with UpsertIfNewer so an older fleet write never clobbers a fresher local (e.g. manual) snapshot. A name with no local provider is skipped. Written with source='fleet'.
func (*QuotaFleetHandler) Register ¶ added in v0.9.91
func (h *QuotaFleetHandler) Register(r chi.Router)
Register mounts the fleet quota routes on the given (fleet-authed) router.
type QuotaRefreshResult ¶
type QuotaRefreshResult struct {
ProviderName string `json:"provider_name"`
ProviderType string `json:"provider_type"`
Refreshed bool `json:"refreshed"`
Error string `json:"error,omitempty"`
}
QuotaRefreshResult holds the result of refreshing quotas for a single provider.
type QuotaSnapshotWire ¶ added in v0.9.91
type QuotaSnapshotWire struct {
ProviderName string `json:"provider_name"`
Type string `json:"type"` // the provider's stored provider_type; chooses the badge
Kind string `json:"kind"`
Payload json.RawMessage `json:"payload"`
HTTPStatus int `json:"http_status"`
FetchedAt time.Time `json:"fetched_at"`
// LastError carries the sending node's failure marker so a receiving member
// classifies the snapshot exactly as the sender would. RecordFailure keeps
// the last good payload, http_status and fetched_at and sets only
// last_error, so without this field a row whose latest refresh failed
// arrives looking fresh and healthy and counts as affirmative recovery
// evidence — releasing the quota pin on a provider whose window is still
// spent. Omitted when empty, so an export from a node that has nothing to
// report stays byte-identical to the pre-existing shape.
LastError string `json:"last_error,omitempty"`
}
QuotaSnapshotWire is a quota snapshot keyed by provider NAME (not UUID) so a receiving member maps it onto its own provider IDs, matching the name-keyed contract config-sync already uses.
type SessionRevoker ¶ added in v0.9.84
type SessionRevoker interface {
DeleteSessionsByUserID(ctx context.Context, userID []byte) (int64, error)
}
SessionRevoker revokes every session of a user (disable/delete/password reset). Implemented by *webauthn.Repository; nil-safe wiring for tests.
type SettingsStore ¶
type SettingsStore interface {
GetAll(ctx context.Context) (map[string]string, error)
GetWithDefault(ctx context.Context, key string, defaultValue string) string
GetChecked(ctx context.Context, key string) (value string, found bool, err error)
GetBool(ctx context.Context, key string, defaultValue bool) bool
GetDuration(ctx context.Context, key string, defaultValue time.Duration) time.Duration
GetInt(ctx context.Context, key string, defaultValue int) int
Set(ctx context.Context, key string, value string) error
SetMany(ctx context.Context, kvs [][2]string) error
SetTx(ctx context.Context, tx pgx.Tx, key string, value string) error
DeleteKeysTx(ctx context.Context, tx pgx.Tx, keys []string) error
DeleteKey(ctx context.Context, key string) error
InvalidateCache(key string)
NotifyDeleted(key string)
}
SettingsStore defines the settings repository methods used by the API.
type StatsHandler ¶
type StatsHandler struct {
// contains filtered or unexported fields
}
StatsHandler provides statistics and analytics API endpoints.
func NewStatsHandler ¶
func NewStatsHandler(dbPool *pgxpool.Pool, adminMgr interface { Validate(token string) bool }) *StatsHandler
NewStatsHandler creates a new statistics handler.
func (*StatsHandler) GetProviderDistribution ¶
func (h *StatsHandler) GetProviderDistribution(w http.ResponseWriter, r *http.Request)
GetProviderDistribution returns request/token distribution by provider.
func (*StatsHandler) GetStats ¶
func (h *StatsHandler) GetStats(w http.ResponseWriter, r *http.Request)
GetStats returns aggregated statistics for the specified period.
func (*StatsHandler) GetTimeSeries ¶
func (h *StatsHandler) GetTimeSeries(w http.ResponseWriter, r *http.Request)
GetTimeSeries returns time-series statistics with hourly or daily buckets.
func (*StatsHandler) Register ¶
func (h *StatsHandler) Register(r chi.Router)
Register mounts statistics API routes.
type StatsResponse ¶
type StatsResponse struct {
TotalRequestsLast24h int `json:"total_requests_last_24h"`
TotalRequestsLast7d int `json:"total_requests_last_7d"`
ByModel map[string]int64 `json:"by_model"`
ByProvider map[string]int64 `json:"by_provider"`
ByVirtualKey map[string]int64 `json:"by_virtual_key"`
AvgLatencyMs float64 `json:"avg_latency_ms"`
ErrorRate float64 `json:"error_rate"`
AvgOverheadMs float64 `json:"avg_overhead_ms"`
TotalTokensPrompt int `json:"total_tokens_prompt"`
TotalTokensCompletion int `json:"total_tokens_completion"`
TotalTokensCacheHit int `json:"total_tokens_cache_hit"`
AvgTokensPerRequest float64 `json:"avg_tokens_per_request"`
RateLimitHits int `json:"rate_limit_hits"`
AvgTTFTMs float64 `json:"avg_ttft_ms"`
RequestsLast1h int `json:"requests_last_1h"`
ByProviderLatency []ProviderLatencyEntry `json:"by_provider_latency"`
}
StatsResponse contains aggregated statistics for the dashboard.
type SuspectStreak ¶ added in v0.9.84
type SuspectStreak struct {
// contains filtered or unexported fields
}
SuspectStreak persists the per-provider consecutive-mass-vanish counter. It is nil on paths that must not touch the counter (unit tests, and any future caller without a pool); ConfirmMissingModels guards every use.
func NewSuspectStreak ¶ added in v0.9.84
func NewSuspectStreak(pool *pgxpool.Pool) *SuspectStreak
NewSuspectStreak builds a pool-backed SuspectStreak for the discovery sweep.
type SystemHandler ¶
type SystemHandler struct {
// contains filtered or unexported fields
}
SystemHandler provides system health and stats API endpoints.
func NewSystemHandler ¶
func NewSystemHandler(pool *pgxpool.Pool, settings fleetSettings) *SystemHandler
NewSystemHandler creates a new system handler. settings is read to surface HA fleet membership on the payload; it may be nil (fleet state is then omitted).
func (*SystemHandler) GetSystem ¶
func (h *SystemHandler) GetSystem(w http.ResponseWriter, r *http.Request)
GetSystem returns system health metrics (app, database, Docker).
func (*SystemHandler) Register ¶
func (h *SystemHandler) Register(r chi.Router)
Register mounts system API routes.
func (*SystemHandler) SetDockerStatsCollector ¶
func (h *SystemHandler) SetDockerStatsCollector(fn dockerStatsCollector)
SetDockerStatsCollector overrides the Docker stats collector (for testing).
type SystemStats ¶
type SystemStats struct {
App AppStats `json:"app"`
DB DBStats `json:"db"`
Docker util.AggregatedDockerStats `json:"docker"`
// Fleet is this instance's HA fleet membership, or nil/omitted for a
// standalone instance Front Desk has never contacted (see fleet.go).
Fleet *FleetStatus `json:"fleet,omitempty"`
// InstanceID is this instance's stable identity (migration 056). Front Desk
// uses it to recognise the same instance reached under a different URL, so it
// can refuse to add a host that is already the primary or already a member.
// Omitted only on a pre-056 build that never generated one.
InstanceID string `json:"instance_id,omitempty"`
}
SystemStats contains system-wide health metrics.
type TestModelResponse ¶
type TestModelResponse struct {
Success bool `json:"success"`
Streaming bool `json:"streaming"`
TTFTMs *int64 `json:"ttft_ms,omitempty"`
ResponseHeaderMs *int64 `json:"response_header_ms,omitempty"`
DurationMs int64 `json:"duration_ms"`
Response string `json:"response"`
Error string `json:"error,omitempty"`
}
TestModelResponse is the JSON response for model test requests.
type TimeSeriesPoint ¶
type TimeSeriesPoint struct {
Bucket string `json:"bucket"`
Count int `json:"count"`
Tokens int `json:"tokens"`
TokensCacheHit int `json:"tokens_cache_hit"`
TokensCacheMiss int `json:"tokens_cache_miss"`
Errors int `json:"errors"`
Latency float64 `json:"latency_ms"`
OverheadMs float64 `json:"overhead_ms"`
ProviderLatencyMs float64 `json:"provider_latency_ms"`
RateLimitHits int `json:"rate_limit_hits"`
AvgTTFTMs float64 `json:"avg_ttft_ms"`
}
TimeSeriesPoint holds a single bucket of time-series data.
type TimeSeriesStats ¶
type TimeSeriesStats struct {
Points []TimeSeriesPoint `json:"points"`
}
TimeSeriesStats groups hourly aggregates returned by /api/stats/timeseries.
type TotpStatus ¶ added in v0.9.71
TotpStatus reports whether TOTP 2FA is active, used by AuthMiddleware gating. Implemented by *totp.Repository.
type UnpinDiscoveryClaimsRequest ¶ added in v0.9.99
type UnpinDiscoveryClaimsRequest struct {
ProviderID string `json:"provider_id"`
ModelIDs []string `json:"model_ids"`
}
UnpinDiscoveryClaimsRequest carries the models to unpin on one provider. It is shaped exactly like DismissDiscoveryClaimsRequest because it is the same kind of operation from the modal's side: a bulk verdict on a provider's rows.
Unpin-only, like dismiss, and for a matching reason: the pin direction is not an endpoint. A pin is armed by the operator enabling the model (models.SetEnabled and models.Update stamp manually_enabled_at), and cleared automatically by the next sighting, since a listed model needs no exemption from the listing. This endpoint covers the one case neither of those reaches: the operator changing their mind about a model the provider still does not list, where there is nothing to enable and no sighting coming.
type UpdateFailoverGroupRequest ¶
type UpdateFailoverGroupRequest struct {
DisplayName *string `json:"display_name"`
Description *string `json:"description"`
DisplayModel *string `json:"display_model"`
GroupEnabled *bool `json:"group_enabled"`
PriorityOrder []string `json:"priority_order"`
EntryEnabled map[string]bool `json:"entry_enabled"`
}
UpdateFailoverGroupRequest is the request body for updating a failover group.
type UpdateVirtualKeyRequest ¶
type UpdateVirtualKeyRequest struct {
Name string `json:"name"`
RateLimitRPS *float64 `json:"rate_limit_rps"`
RateLimitBurst *int `json:"rate_limit_burst"`
RateLimitTPM *int `json:"rate_limit_tpm"`
AllowedProviders *[]string `json:"allowed_providers,omitempty"`
StripReasoning *bool `json:"strip_reasoning,omitempty"`
OwnerUserID *string `json:"owner_user_id,omitempty"`
// contains filtered or unexported fields
}
UpdateVirtualKeyRequest is the request body for updating a virtual key.
func (*UpdateVirtualKeyRequest) UnmarshalJSON ¶ added in v0.9.28
func (r *UpdateVirtualKeyRequest) UnmarshalJSON(data []byte) error
UnmarshalJSON detects whether allowed_providers was present in the JSON.
type UserStore ¶ added in v0.9.84
type UserStore interface {
Get(ctx context.Context, id uuid.UUID) (*user.User, error)
List(ctx context.Context) ([]*user.User, error)
Create(ctx context.Context, username, displayName string, email *string, passwordHash string, role user.Role, grants []string, limits user.Limits, allowedProviders *[]string) (*user.User, error)
Update(ctx context.Context, id uuid.UUID, username, displayName string, email *string, role user.Role, grants []string, enabled bool, limits user.Limits, allowedProviders *[]string) (*user.User, error)
SetPassword(ctx context.Context, id uuid.UUID, passwordHash string) error
Delete(ctx context.Context, id uuid.UUID) error
}
UserStore is the slice of the user repository the auth middleware and the users admin API need. Implemented by *user.Repository.
type UserTotpFactory ¶ added in v0.9.84
type UserTotpFactory = totp.UserFactory
UserTotpFactory builds a TOTP repository bound to one user's rows (user_totp tables). Wired from main.go; nil disables the whole surface. Aliased to totp.UserFactory so the login handler and this surface share one signature (a change to the factory shape touches a single declaration).
type VirtualKeyStore ¶
type VirtualKeyStore interface {
Create(ctx context.Context, name, keyHash, keyPreview string, rps *float64, burst, tpm *int, allowedProviders *[]string, stripReasoning *bool, ownerUserID *uuid.UUID) (*virtualkey.VirtualKey, error)
List(ctx context.Context) ([]*virtualkey.VirtualKey, error)
ListByOwner(ctx context.Context, ownerUserID uuid.UUID) ([]*virtualkey.VirtualKey, error)
Get(ctx context.Context, id uuid.UUID) (*virtualkey.VirtualKey, error)
Delete(ctx context.Context, id uuid.UUID) error
Update(ctx context.Context, id uuid.UUID, name string, rps *float64, burst, tpm *int, allowedProviders *[]string, stripReasoning *bool, ownerUserID *uuid.UUID) (*virtualkey.VirtualKey, error)
}
VirtualKeyStore defines the virtual key repository methods used by the API.
type WebAuthnSessionManager ¶ added in v0.9.34
type WebAuthnSessionManager interface {
Validate(ctx context.Context, token string) bool
// TokenUser validates like Validate and returns the session's user handle
// ([]byte("admin") for legacy admin logins, a user UUID string for
// multi-user password logins).
TokenUser(ctx context.Context, token string) ([]byte, bool)
// Authenticate validates like TokenUser and additionally reports the
// session's expiry and whether this call slid it forward, so a cookie
// caller can re-issue the cookie pair with the new lifetime.
Authenticate(ctx context.Context, token string) (webauthn.AuthResult, bool)
// Verify validates like Authenticate but writes nothing (no last-seen
// stamp, no slide): for server-driven re-checks that are not use.
Verify(ctx context.Context, token string) (webauthn.AuthResult, bool)
RevokeAuthToken(ctx context.Context, token string) bool
// RevokeOtherSessions signs out every session belonging to identity except
// the one the request was made from. identity must come from the
// authentication layer, never from a token the caller supplied; the
// candidate tokens only decide which session is spared, and one that does
// not belong to identity spares nothing.
RevokeOtherSessions(ctx context.Context, identity []byte, candidateTokens ...string) (int64, error)
// CreateAuthToken mints a new session token for the given user handle. The
// admin-token exchange trades a valid admin token for a session cookie via
// this method (userID is []byte("admin") for the legacy admin login). meta
// carries the login request's device metadata onto the stored session.
CreateAuthToken(ctx context.Context, userID, credentialID []byte, meta webauthn.SessionMeta) (string, error)
// ListAuthSessions returns identity's live sessions for the active-sessions
// list, marking as current the one whose token the request carried. The
// same identity/candidate contract as RevokeOtherSessions applies.
ListAuthSessions(ctx context.Context, identity []byte, candidateTokens ...string) ([]webauthn.AuthSessionInfo, error)
// RevokeSessionByID deletes one of identity's sessions. It returns
// webauthn.ErrNotFound for a session that is missing or not identity's, and
// webauthn.ErrCurrentSession when the target is the session the request
// itself rides on.
RevokeSessionByID(ctx context.Context, identity []byte, id uuid.UUID, candidateTokens ...string) error
}
WebAuthnSessionManager defines webAuthn session token validation. It is implemented by the internal/webauthn.SessionManager.
Source Files
¶
- admin.go
- admin_token_exchange.go
- alerts.go
- applogs.go
- applogs_buffer.go
- applogs_slog.go
- audit.go
- auth_logout.go
- auth_sessions.go
- authz.go
- backup.go
- backup_restore.go
- backup_retention.go
- backup_scheduler.go
- backup_sign.go
- chatusercontext.go
- configsync.go
- configsync_apply.go
- configsync_apply_models.go
- configsync_apply_upserts.go
- configsync_export.go
- configsync_import.go
- demologin.go
- discovery.go
- discovery_changes.go
- discovery_claim_alert.go
- discovery_claims.go
- discovery_confirm.go
- discovery_diff.go
- discovery_dismiss.go
- discovery_quota.go
- events.go
- failover.go
- failover_circuitbreaker.go
- fleet.go
- health.go
- helpers.go
- logs.go
- logs_query.go
- logscache.go
- managedwrite.go
- metrics.go
- models.go
- models_cursor.go
- passwordpolicy.go
- provider_typegate.go
- providers.go
- publicconfig.go
- quota_advisor.go
- quota_drift.go
- quota_fleet.go
- quota_snapshot.go
- readonly.go
- settings.go
- stats.go
- stats_queries.go
- stats_timeseries.go
- system.go
- userpassword.go
- users.go
- usertotp.go
- validate.go
- version.go
- virtualkeys.go