Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
79563c2
feat: add admin dashboard and API behind ADMIN_ENABLED feature flag
claude Feb 16, 2026
c865234
feat(dashboard): add light/dark theme toggle and improve model filtering
claude Feb 16, 2026
c48b56b
fix(dashboard): resolve chart infinite resize crash and update CDN libs
claude Feb 16, 2026
5e1a0d5
fix(dashboard): start chart Y-axis from zero
claude Feb 16, 2026
50b0a18
feat(dashboard): fill missing days on X-axis with zero values
claude Feb 16, 2026
7a78bcb
feat(dashboard): rebrand to match gomodel.enterpilot.io theme
claude Feb 16, 2026
a0a5b24
feat(admin): split into ADMIN_ENDPOINTS_ENABLED and ADMIN_UI_ENABLED …
claude Feb 16, 2026
b79bff6
Apply suggestion from @SantiagoDePolonia
SantiagoDePolonia Feb 16, 2026
6549537
fix(dashboard): icon-only sidebar on mobile with title attributes
claude Feb 16, 2026
ad75993
fix(dashboard): keep theme toggle accessible on mobile
SantiagoDePolonia Feb 16, 2026
570f565
fix(dashboard): fix chart rendering, add favicon, improve sidebar spa…
SantiagoDePolonia Feb 16, 2026
2525cdc
feat: made menu entries linkable
SantiagoDePolonia Feb 16, 2026
ff63250
fix(dashboard): prevent overview page from expanding beyond viewport
SantiagoDePolonia Feb 17, 2026
2bcfff3
feat(dashboard): add collapsible sidebar toggle on desktop
SantiagoDePolonia Feb 17, 2026
4ef58de
fix(dashboard): use mousedown for snappier sidebar toggle
SantiagoDePolonia Feb 17, 2026
679dd4f
fix(dashboard): use semi-transparent background for sidebar toggle hover
SantiagoDePolonia Feb 17, 2026
71f5175
fix(dashboard,usage): address code review feedback
SantiagoDePolonia Feb 17, 2026
b6cced8
docs: updated the documentation + claude.md file
SantiagoDePolonia Feb 17, 2026
e60c096
fix(dashboard): replace static hint with mouse-following cursor tooltip
SantiagoDePolonia Feb 17, 2026
c92e8d8
fix(admin): use typed GatewayError handling in admin API endpoints
SantiagoDePolonia Feb 17, 2026
d53ba4f
docs: update admin endpoint docs with query params and roadmap status
SantiagoDePolonia Feb 17, 2026
c57c7ff
fix(dashboard): fix mobile calendar navigation and disable next-month…
SantiagoDePolonia Feb 17, 2026
72e644d
fix(app): log full clickable URL for admin dashboard at startup
SantiagoDePolonia Feb 17, 2026
6a73999
test(admin): add comprehensive tests for admin dashboard feature
SantiagoDePolonia Feb 17, 2026
54b584b
fix(usage): fix date filtering and ISO week grouping in usage readers
SantiagoDePolonia Feb 17, 2026
f594475
fix(dashboard): reset needsAuth flag in fetchAll() for re-auth recovery
SantiagoDePolonia Feb 17, 2026
8028e91
fix(admin): return 400 for malformed date query params instead of sil…
SantiagoDePolonia Feb 17, 2026
7b28e1a
fix: nil-slice JSON null, MongoDB summary filter, and admin startup l…
SantiagoDePolonia Feb 17, 2026
fda7d7b
chore: added more permissions to the claude code
SantiagoDePolonia Feb 17, 2026
59dd55a
fix(dashboard): close date picker on Escape and skip theme buttons in…
SantiagoDePolonia Feb 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .claude/settings.local.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@
"WebFetch(domain:docs.anthropic.com)",
"mcp__linear-server__list_projects",
"mcp__linear-server__list_issues",
"mcp__linear-server__get_issue"
"mcp__linear-server__get_issue",
"Bash(make build:*)",
"Bash(git add:*)",
"Bash(git commit:*)",
"Bash(git push)",
"Bash(git -C:*)"
],
"deny": [],
"ask": []
Comment thread
SantiagoDePolonia marked this conversation as resolved.
Expand Down
14 changes: 14 additions & 0 deletions .env.template
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,20 @@
# Optional: Custom cache directory for local file cache
# GOMODEL_CACHE_DIR=.cache

# =============================================================================
# Admin API & Dashboard Configuration
# =============================================================================

# Enable/disable admin REST API endpoints (default: true)
# When enabled, provides /admin/api/v1/* REST endpoints
# ADMIN_ENDPOINTS_ENABLED=true

# Enable/disable admin dashboard UI (default: true)
# When enabled, provides /admin/dashboard UI
# Requires ADMIN_ENDPOINTS_ENABLED=true — if endpoints are disabled
# and UI is enabled, a warning is logged and UI is forced to disabled
# ADMIN_UI_ENABLED=true

# =============================================================================
# Storage Configuration (used by audit logging, usage tracking, future IAM, etc.)
# =============================================================================
Expand Down
17 changes: 17 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,23 @@ Full reference: `.env.template` and `config/config.yaml`
- **Guardrails:** Configured via `config/config.yaml` only (except `GUARDRAILS_ENABLED` env var)
- **Providers:** `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GEMINI_API_KEY`, `XAI_API_KEY`, `GROQ_API_KEY`, `OLLAMA_BASE_URL`

## Documentation Maintenance

After completing any code change, routinely check whether documentation needs updating. This applies to all three documentation layers:

1. **README files** (`README.md`, `helm/README.md`, `tests/contract/README.md`) — Update when adding/removing features, changing setup steps, modifying CLI flags, or altering configuration options.
2. **In-code documentation** (Go doc comments on exported types, functions, interfaces) — Update when changing public APIs, adding new exported symbols, or modifying function signatures/behavior.
3. **Mintlify / technical docs** (`docs/` directory) — Update `docs/advanced/*.mdx` pages when changing configuration options or guardrails behavior. Update `docs/adr/` when making significant architectural decisions. Update `docs/plans/` if implementation diverges from existing plans. Check `docs.json` if new pages need to be added to the navigation.

**When to update:**
- Adding a new provider, endpoint, config option, or feature
- Changing existing behavior, defaults, or API contracts
- Renaming or removing configuration variables
- Adding or modifying middleware, guardrails, or storage backends
- Changing build/test commands or requirements

**How to check:** After making changes, scan the affected documentation layers for stale or missing information. Do not add speculative documentation for unimplemented features — only document what exists.

## Key Details

1. Providers are registered explicitly via `factory.Register()` in main.go — order matters, first registered wins for duplicate model names
Expand Down
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,10 @@ docker run --rm -p 8080:8080 --env-file .env gomodel
| `/v1/models` | GET | List available models |
| `/health` | GET | Health check |
| `/metrics` | GET | Prometheus metrics (when enabled) |
| `/admin/api/v1/usage/summary` | GET | Aggregate token usage statistics |
| `/admin/api/v1/usage/daily` | GET | Per-period token usage breakdown |
| `/admin/api/v1/models` | GET | List models with provider type |
| `/admin/dashboard` | GET | Admin dashboard UI |

---

Expand Down Expand Up @@ -194,10 +198,10 @@ See [DEVELOPMENT.md](DEVELOPMENT.md) for testing, linting, and pre-commit setup.
| Full-observability | 🚧 | 🚧 |
| Budget management | 🚧 | 🚧 |
| Many keys support | 🚧 | 🚧 |
| Administrative endpoints | 🚧 | 🚧 |
| Guardrails | 🚧 | 🚧 |
| Administrative endpoints | | 🚧 |
| Guardrails | | 🚧 |
| SSO | 🚧 | 🚧 |
| System Prompt (GuardRails) | 🚧 | 🚧 |
| System Prompt (GuardRails) | | 🚧 |

## Integrations

Expand Down
19 changes: 17 additions & 2 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,24 @@ type Config struct {
Usage UsageConfig `yaml:"usage"`
Metrics MetricsConfig `yaml:"metrics"`
HTTP HTTPConfig `yaml:"http"`
Admin AdminConfig `yaml:"admin"`
Guardrails GuardrailsConfig `yaml:"guardrails"`
Providers map[string]ProviderConfig `yaml:"providers"`
}

// AdminConfig holds configuration for the admin API and dashboard UI.
type AdminConfig struct {
// EndpointsEnabled controls whether the admin REST API is active
// Default: true
EndpointsEnabled bool `yaml:"endpoints_enabled" env:"ADMIN_ENDPOINTS_ENABLED"`

// UIEnabled controls whether the admin dashboard UI is active
// Requires EndpointsEnabled — if endpoints are disabled and UI is enabled,
// a warning is logged and UI is forced to false.
// Default: true
UIEnabled bool `yaml:"ui_enabled" env:"ADMIN_UI_ENABLED"`
}

// GuardrailsConfig holds configuration for the request guardrails pipeline.
type GuardrailsConfig struct {
// Enabled controls whether guardrails are active
Expand Down Expand Up @@ -215,7 +229,7 @@ type RedisConfig struct {
// ServerConfig holds HTTP server configuration
type ServerConfig struct {
Port string `yaml:"port" env:"PORT"`
MasterKey string `yaml:"master_key" env:"GOMODEL_MASTER_KEY"` // Optional: Master key for authentication
MasterKey string `yaml:"master_key" env:"GOMODEL_MASTER_KEY"` // Optional: Master key for authentication
BodySizeLimit string `yaml:"body_size_limit" env:"BODY_SIZE_LIMIT"` // Max request body size (e.g., "10M", "1024K")
}

Expand Down Expand Up @@ -284,8 +298,9 @@ func defaultConfig() Config {
Timeout: 600,
ResponseHeaderTimeout: 600,
},
Admin: AdminConfig{EndpointsEnabled: true, UIEnabled: true},
Guardrails: GuardrailsConfig{},
Providers: make(map[string]ProviderConfig),
Providers: make(map[string]ProviderConfig),
}
}

Expand Down
185 changes: 185 additions & 0 deletions docs/advanced/admin-endpoints.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
---
title: "Admin Endpoints"
description: "Built-in REST API and dashboard for monitoring usage, models, and gateway health."
---

## Philosophy

GOModel ships with admin endpoints **enabled by default**. The goal is simple: you should be able to deploy GOModel and immediately have visibility into what's happening — no extra services, no separate monitoring stack, no configuration.

The admin layer is split into two independently controllable pieces:

1. **Admin REST API** (`/admin/api/v1/*`) — machine-readable JSON endpoints for usage data and model inventory. Protected by `GOMODEL_MASTER_KEY` like all other API routes.
2. **Admin Dashboard UI** (`/admin/dashboard`) — a lightweight, embedded HTML dashboard that visualizes the same data. No external dependencies, no JavaScript frameworks to install — it's compiled into the binary.

Both are on by default because observability shouldn't be opt-in. If you don't need them, turn them off with a single environment variable.

## Configuration

| Variable | Description | Default |
| ------------------------- | ------------------------------------ | ------- |
| `ADMIN_ENDPOINTS_ENABLED` | Enable the admin REST API | `true` |
| `ADMIN_UI_ENABLED` | Enable the admin dashboard UI | `true` |

Or in YAML:

```yaml
admin:
endpoints_enabled: true
ui_enabled: true
```

<Note>
The dashboard UI requires the REST API to be enabled. If you set
`ADMIN_ENDPOINTS_ENABLED=false` but leave `ADMIN_UI_ENABLED=true`, the UI
will be automatically disabled with a warning in the logs.
</Note>

## Authentication

The admin REST API endpoints (`/admin/api/v1/*`) are protected by the same `GOMODEL_MASTER_KEY` authentication as the main API routes. Include the key as a Bearer token:

```bash
curl -H "Authorization: Bearer $GOMODEL_MASTER_KEY" \
http://localhost:8080/admin/api/v1/usage/summary
```

The dashboard UI pages (`/admin/dashboard`) and static assets (`/admin/static/*`) **skip authentication** so the dashboard is accessible without configuring API keys in the browser.

<Warning>
If your GOModel instance is publicly accessible, be aware that the dashboard
UI is unauthenticated. Disable it with `ADMIN_UI_ENABLED=false` or restrict
access at the network level.
</Warning>

## REST API Endpoints

All admin API endpoints are mounted under `/admin/api/v1`.

### GET /admin/api/v1/usage/summary

Returns aggregate token usage statistics over a configurable time window.

**Query parameters:**

| Parameter | Type | Description | Default |
| ------------ | ------ | -------------------------------------------------------- | -------------------- |
| `start_date` | string | Range start in `YYYY-MM-DD` format | 29 days before end |
| `end_date` | string | Range end in `YYYY-MM-DD` format | Today |
| `days` | int | Shorthand for look-back window (ignored if dates are set) | `30` |

Use `start_date`/`end_date` for explicit ranges or `days` as a shorthand. When both are provided, `start_date`/`end_date` take priority.
Comment on lines +65 to +71
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Documentation inaccuracy: The documentation states that the default for start_date is "29 days before end" (line 67), but the actual implementation in handler.go line 68 uses AddDate(0, 0, -29) which creates a 30-day window (29 days ago + today = 30 days). Similarly, line 69 says the default for days is 30, but line 82 in handler.go uses AddDate(0, 0, -(days - 1)) which for days=30 gives 30 days (0-29 days back). The documentation should clarify that the range is inclusive of both start and end dates.

Suggested change
| Parameter | Type | Description | Default |
| ------------ | ------ | -------------------------------------------------------- | -------------------- |
| `start_date` | string | Range start in `YYYY-MM-DD` format | 29 days before end |
| `end_date` | string | Range end in `YYYY-MM-DD` format | Today |
| `days` | int | Shorthand for look-back window (ignored if dates are set) | `30` |
Use `start_date`/`end_date` for explicit ranges or `days` as a shorthand. When both are provided, `start_date`/`end_date` take priority.
| Parameter | Type | Description | Default |
| ------------ | ------ | -------------------------------------------------------- | -------------------------------------------- |
| `start_date` | string | Range start in `YYYY-MM-DD` format | 29 days before end (30-day inclusive window) |
| `end_date` | string | Range end in `YYYY-MM-DD` format | Today |
| `days` | int | Shorthand for look-back window (ignored if dates are set) | `30` (inclusive of today; 0–29 days back) |
Use `start_date`/`end_date` for explicit ranges or `days` as a shorthand. When both are provided, `start_date`/`end_date` take priority. Ranges are inclusive of both start and end dates; for example, a 30-day window includes today plus the previous 29 days.

Copilot uses AI. Check for mistakes.

**Response:**

```json
{
"total_requests": 1542,
"total_input_tokens": 2450000,
"total_output_tokens": 890000,
"total_tokens": 3340000
}
```

If usage tracking is disabled, returns zeroed values.

### GET /admin/api/v1/usage/daily

Returns per-period token usage breakdown over a configurable time window, grouped by the specified interval.

**Query parameters:**

| Parameter | Type | Description | Default |
| ------------ | ------ | -------------------------------------------------------- | -------------------- |
| `start_date` | string | Range start in `YYYY-MM-DD` format | 29 days before end |
| `end_date` | string | Range end in `YYYY-MM-DD` format | Today |
| `days` | int | Shorthand for look-back window (ignored if dates are set) | `30` |
| `interval` | string | Grouping: `daily`, `weekly`, `monthly`, `yearly` | `daily` |

The `date` field in the response changes format based on the interval: `YYYY-MM-DD` (daily), `YYYY-Www` (weekly), `YYYY-MM` (monthly), or `YYYY` (yearly).

**Response:**

```json
[
{
"date": "2026-02-17",
"requests": 84,
"input_tokens": 120000,
"output_tokens": 45000,
"total_tokens": 165000
},
{
"date": "2026-02-16",
"requests": 102,
"input_tokens": 155000,
"output_tokens": 58000,
"total_tokens": 213000
}
]
```

Returns an empty array if usage tracking is disabled or no data exists for the period.

### GET /admin/api/v1/models

Returns all registered models with their provider type.

**Response:**

```json
[
{
"model": {
"id": "gpt-4o",
"object": "model",
"created": 1715367049,
"owned_by": "system"
},
"provider_type": "openai"
},
{
"model": {
"id": "claude-sonnet-4-5-20250929",
"object": "model",
"created": 1727568000,
"owned_by": "system"
},
"provider_type": "anthropic"
}
]
```

This differs from the standard `/v1/models` endpoint: the admin version includes `provider_type` for each model, making it useful for understanding which provider serves which model.

## Admin Dashboard

The dashboard is a server-rendered HTML page embedded in the GOModel binary. Access it at:

```
http://localhost:8080/admin/dashboard
```

It provides a visual overview of usage statistics and registered models using the same data as the REST API endpoints above.

## Disabling Admin Features

To disable all admin features:

```bash
export ADMIN_ENDPOINTS_ENABLED=false
```

This disables both the REST API and the dashboard UI. To keep the API but hide the dashboard:

```bash
export ADMIN_ENDPOINTS_ENABLED=true
export ADMIN_UI_ENABLED=false
```

<Tip>
The admin layer is designed to degrade gracefully. If usage tracking is off,
the usage endpoints return empty results instead of errors. If the model
registry isn't ready, the models endpoint returns an empty array. The gateway
keeps running regardless.
</Tip>
7 changes: 7 additions & 0 deletions docs/advanced/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,13 @@ Storage is shared by audit logging, usage tracking, and future features like IAM
| `METRICS_ENABLED` | Enable Prometheus metrics | `false` |
| `METRICS_ENDPOINT` | HTTP path for metrics | `/metrics` |

#### Admin

| Variable | Description | Default |
| ------------------------- | ----------------------------- | ------- |
| `ADMIN_ENDPOINTS_ENABLED` | Enable the admin REST API | `true` |
| `ADMIN_UI_ENABLED` | Enable the admin dashboard UI | `true` |

#### HTTP Client

These control timeouts for upstream API requests to LLM providers.
Expand Down
2 changes: 1 addition & 1 deletion docs/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"pages": [
{
"group": "Advanced",
"pages": ["advanced/configuration", "advanced/guardrails"]
"pages": ["advanced/configuration", "advanced/guardrails", "advanced/admin-endpoints"]
}
]
}
Expand Down
57 changes: 57 additions & 0 deletions internal/admin/dashboard/dashboard.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Package dashboard provides the embedded admin dashboard UI for GOModel.
package dashboard

import (
"bytes"
"embed"
"html/template"
"io/fs"
"net/http"

"github.com/labstack/echo/v4"
)

//go:embed templates/*.html static/css/*.css static/js/*.js static/*.svg
var content embed.FS

// Handler serves the admin dashboard UI.
type Handler struct {
indexTmpl *template.Template
staticFS http.Handler
}

// New creates a new dashboard handler with parsed templates and static file server.
func New() (*Handler, error) {
tmpl, err := template.ParseFS(content, "templates/layout.html", "templates/index.html")
if err != nil {
return nil, err
}

staticSub, err := fs.Sub(content, "static")
if err != nil {
return nil, err
}

return &Handler{
indexTmpl: tmpl,
staticFS: http.StripPrefix("/admin/static/", http.FileServer(http.FS(staticSub))),
}, nil
}

// Index serves GET /admin/dashboard — the main dashboard page.
func (h *Handler) Index(c echo.Context) error {
var buf bytes.Buffer
if err := h.indexTmpl.ExecuteTemplate(&buf, "layout", nil); err != nil {
return err
}
c.Response().Header().Set("Content-Type", "text/html; charset=utf-8")
c.Response().WriteHeader(http.StatusOK)
_, err := buf.WriteTo(c.Response().Writer)
return err
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// Static serves GET /admin/static/* — embedded CSS/JS assets.
func (h *Handler) Static(c echo.Context) error {
h.staticFS.ServeHTTP(c.Response().Writer, c.Request())
return nil
}
Comment on lines +41 to +57
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing test coverage for dashboard handler. Consider adding tests for the Index and Static handler methods to verify template rendering, error handling, and static file serving.

Copilot uses AI. Check for mistakes.
Loading