Conversation
…nitoring - Add usage_scripts DB table (base_url_host + account_type as key) - Implement ScriptEngine: Starlark interpreter with http_get/http_post/json_parse builtins - Integrate with AccountUsageService: auto-match scripts by account base_url - Add scheduling integration: utilization >= 1.0 triggers temp_unschedulable - Add Admin CRUD API for usage scripts management - Wire injection for ScriptEngine and UsageScriptRepository
- Add Admin CRUD page for usage scripts management (UsageScriptsView.vue) - Add API client for usage scripts (api/admin/usageScripts.ts) - Add ScriptUsageWindow type to AccountUsageInfo - Display script_windows in AccountUsageCell for accounts with custom base_url - Add route /admin/usage-scripts and sidebar navigation entry - Add i18n translations (en/zh) for usage scripts
There was a problem hiding this comment.
Pull request overview
This PR adds a Starlark-based “usage collection script” system to support usage monitoring for third-party relay upstreams (non-official upstreams), including admin CRUD APIs/UI and usage display integration.
Changes:
- Backend: introduce a Starlark script engine + usage-script persistence (Ent schema + migration) + admin CRUD routes/handler, and integrate script execution into account usage lookup.
- Frontend: add an admin “Usage Scripts” management page, navigation entry, API client, and usage UI support for script-based usage windows.
- Types/i18n: add
script_windowstyping and translations for the new admin page.
Reviewed changes
Copilot reviewed 39 out of 40 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| frontend/src/views/admin/UsageScriptsView.vue | New admin CRUD page for managing usage scripts. |
| frontend/src/types/index.ts | Adds script_windows and ScriptUsageWindow type for usage display. |
| frontend/src/router/index.ts | Adds /admin/usage-scripts admin route. |
| frontend/src/i18n/locales/zh.ts | Adds nav + page translations for usage scripts (ZH). |
| frontend/src/i18n/locales/en.ts | Adds nav + page translations for usage scripts (EN). |
| frontend/src/components/layout/AppSidebar.vue | Adds sidebar nav item for usage scripts. |
| frontend/src/components/account/AccountUsageCell.vue | Adds rendering for script-based usage windows. |
| frontend/src/api/admin/usageScripts.ts | New admin API client for usage scripts. |
| backend/migrations/072_add_usage_scripts.sql | Adds usage_scripts table + partial unique index. |
| backend/internal/service/wire.go | Registers ScriptEngine provider in service DI. |
| backend/internal/service/usage_script.go | Adds service-layer UsageScript model. |
| backend/internal/service/script_usage_types.go | Adds script usage result/window DTO types. |
| backend/internal/service/script_engine.go | Implements Starlark execution engine + builtins. |
| backend/internal/service/account_usage_service.go | Integrates script lookup/execution into usage flow + adds repo interface. |
| backend/internal/server/routes/admin.go | Registers admin routes for usage script CRUD. |
| backend/internal/repository/wire.go | Registers UsageScriptRepository provider. |
| backend/internal/repository/usage_script_repo.go | Implements UsageScriptRepository via Ent. |
| backend/internal/handler/wire.go | Wires UsageScriptHandler into admin handlers set. |
| backend/internal/handler/handler.go | Adds UsageScript to AdminHandlers. |
| backend/internal/handler/admin/usage_script_handler.go | Implements admin CRUD HTTP handlers for usage scripts. |
| backend/go.mod | Adds go.starlark.net dependency. |
| backend/go.sum | Updates module sums for new dependency. |
| backend/ent/usagescript_update.go | Ent generated code for UsageScript updates. |
| backend/ent/usagescript_query.go | Ent generated code for UsageScript queries. |
| backend/ent/usagescript_delete.go | Ent generated code for UsageScript deletes. |
| backend/ent/usagescript_create.go | Ent generated code for UsageScript creates/upserts. |
| backend/ent/usagescript/where.go | Ent generated predicates for UsageScript fields. |
| backend/ent/usagescript/usagescript.go | Ent generated metadata for UsageScript schema. |
| backend/ent/usagescript.go | Ent generated model struct for UsageScript. |
| backend/ent/tx.go | Adds UsageScript client to Ent transactions. |
| backend/ent/schema/usage_script.go | Defines Ent schema for UsageScript entity. |
| backend/ent/runtime/runtime.go | Registers runtime defaults/validators/hooks for UsageScript. |
| backend/ent/predicate/predicate.go | Adds predicate type for UsageScript. |
| backend/ent/mutation.go | Adds Ent mutation plumbing for UsageScript. |
| backend/ent/migrate/schema.go | Adds UsageScripts table schema to Ent migration metadata. |
| backend/ent/intercept/intercept.go | Adds intercept scaffolding for UsageScript queries. |
| backend/ent/hook/hook.go | Adds hook scaffolding for UsageScript mutations. |
| backend/ent/ent.go | Updates Ent column validation map to include UsageScript. |
| backend/ent/client.go | Adds UsageScript client and wiring to Ent client. |
| backend/cmd/server/wire_gen.go | Updates generated DI wiring to include ScriptEngine + repo + handler. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| func (r *usageScriptRepository) Update(ctx context.Context, id int64, script *service.UsageScript) (*service.UsageScript, error) { | ||
| m, err := r.client.UsageScript.UpdateOneID(id). | ||
| SetBaseURLHost(script.BaseURLHost). | ||
| SetAccountType(script.AccountType). | ||
| SetScript(script.Script). | ||
| SetEnabled(script.Enabled). | ||
| Save(ctx) |
There was a problem hiding this comment.
Update uses UpdateOneID(id) without filtering out soft-deleted rows. In other repos (e.g. APIKey) updates typically add Where(DeletedAtIsNil()) to avoid mutating deleted records. Consider adding Where(usagescript.DeletedAtIsNil()) here (and translating NotFound appropriately) so soft-deleted scripts can't be updated by ID.
| body, err := io.ReadAll(io.LimitReader(resp.Body, scriptMaxResponseBody)) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("http_get: read body: %w", err) | ||
| } | ||
|
|
||
| return starlark.Tuple{starlark.MakeInt(resp.StatusCode), starlark.String(body), responseHeadersToStarlark(resp)}, nil | ||
| } |
There was a problem hiding this comment.
body is a []byte from io.ReadAll, but it's being converted directly to starlark.String(body), which won't compile (cannot convert []byte to string). Convert the response body to a Go string first (or return starlark.Bytes).
| body, err := io.ReadAll(io.LimitReader(resp.Body, scriptMaxResponseBody)) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("http_post: read body: %w", err) | ||
| } | ||
|
|
||
| return starlark.Tuple{starlark.MakeInt(resp.StatusCode), starlark.String(body), responseHeadersToStarlark(resp)}, nil | ||
| } |
There was a problem hiding this comment.
Same issue as http_get: body is []byte but returned as starlark.String(body), which will not compile. Convert to string(body) (or return bytes) before wrapping it as a Starlark value.
| script := &service.UsageScript{ | ||
| BaseURLHost: req.BaseURLHost, | ||
| AccountType: req.AccountType, | ||
| Script: req.Script, | ||
| } | ||
| if req.Enabled != nil { | ||
| script.Enabled = *req.Enabled | ||
| } else { | ||
| script.Enabled = true | ||
| } |
There was a problem hiding this comment.
Update currently treats missing fields as empty values and also forces Enabled=true when enabled is omitted. This makes partial updates unsafe (e.g., a client sending only {enabled:false} would blank out base_url_host/account_type/script, and sending an update without enabled will unintentionally re-enable the script). Either require all fields in the update request (add binding:"required") or implement patch semantics by loading the existing row and only updating fields that are explicitly set (use pointer fields / separate PATCH endpoint).
| script := &service.UsageScript{ | |
| BaseURLHost: req.BaseURLHost, | |
| AccountType: req.AccountType, | |
| Script: req.Script, | |
| } | |
| if req.Enabled != nil { | |
| script.Enabled = *req.Enabled | |
| } else { | |
| script.Enabled = true | |
| } | |
| // Require all fields to be present for an update to avoid unsafe partial updates. | |
| if req.BaseURLHost == "" || req.AccountType == "" || req.Script == "" || req.Enabled == nil { | |
| response.BadRequest(c, "base_url_host, account_type, script and enabled are required") | |
| return | |
| } | |
| script := &service.UsageScript{ | |
| BaseURLHost: req.BaseURLHost, | |
| AccountType: req.AccountType, | |
| Script: req.Script, | |
| Enabled: *req.Enabled, | |
| } |
| export async function toggleEnabled(id: number, enabled: boolean): Promise<UsageScript> { | ||
| return update(id, { enabled } as UpdateUsageScriptRequest) | ||
| } |
There was a problem hiding this comment.
toggleEnabled force-casts a partial payload ({ enabled }) to UpdateUsageScriptRequest, but update() expects the full object. Given the backend update overwrites fields, calling this will likely wipe base_url_host/account_type/script. Prefer a dedicated backend endpoint for toggling, or change UpdateUsageScriptRequest to support partial updates safely and remove the unsafe cast.
|
|
||
| func (UsageScript) Indexes() []ent.Index { | ||
| return []ent.Index{ | ||
| index.Fields("base_url_host", "account_type").Unique(), |
There was a problem hiding this comment.
This entity uses SoftDeleteMixin, but the schema defines a plain unique index on (base_url_host, account_type). With soft deletes, a non-partial unique constraint prevents recreating a script with the same keys after deletion. Other schemas document using a partial unique index via SQL migrations (e.g. ent/schema/group.go). Consider removing .Unique() here and enforcing uniqueness via a partial unique index WHERE deleted_at IS NULL (as the SQL migration already does), plus keep a deleted_at index for query performance if needed.
| index.Fields("base_url_host", "account_type").Unique(), | |
| index.Fields("base_url_host", "account_type"), |
Summary
Changes
Backend
internal/service/script_engine.go— Starlark 脚本执行引擎,支持 HTTP 请求、JSON 解析、时间函数等内置模块internal/service/account_usage_service.go— 在用量查询流程中集成脚本引擎,按 base_url host 匹配脚本internal/handler/admin/usage_script_handler.go— 管理 API:列表/创建/更新/删除用量脚本ent/schema/usage_script.go— 数据库 schema:host_pattern、account_type、script 等字段migrations/072_add_usage_scripts.sql— 数据库迁移Frontend
views/admin/UsageScriptsView.vue— 用量脚本管理页面components/account/AccountUsageCell.vue— 支持 script_windows 展示api/admin/usageScripts.ts— API 客户端Test plan
🤖 Generated with Claude Code