feat: CPU/RAM limits per stage, NPM access list (global + per-project)

Resource limits:
- Add cpu_limit (cores) and memory_limit (MB) fields to Stage model
- Pass limits to Docker container via NanoCPUs and Memory in HostConfig
- Add CPU/Memory fields to stage creation form in project detail
- 0 = unlimited (default)

NPM access list:
- Add npm_access_list_id to Settings (global default) and Project (per-project override)
- Per-project overrides global when > 0
- NPM provider passes access_list_id when configuring proxy hosts
- Add GET /api/settings/npm-access-lists endpoint to list NPM access lists
- Add access list picker on NPM settings page (global)
- Add access list ID field on project edit form (per-project)
- DB migrations for all new columns
This commit is contained in:
2026-04-05 12:44:26 +03:00
parent c6d20ca26e
commit 7550fe9e32
12 changed files with 217 additions and 38 deletions
+27 -8
View File
@@ -13,14 +13,16 @@ import (
// stageRequest is the expected JSON body for creating/updating a stage.
type stageRequest struct {
Name string `json:"name"`
TagPattern string `json:"tag_pattern"`
AutoDeploy *bool `json:"auto_deploy"`
MaxInstances *int `json:"max_instances"`
Confirm *bool `json:"confirm"`
PromoteFrom string `json:"promote_from"`
Subdomain string `json:"subdomain"`
NotificationURL string `json:"notification_url"`
Name string `json:"name"`
TagPattern string `json:"tag_pattern"`
AutoDeploy *bool `json:"auto_deploy"`
MaxInstances *int `json:"max_instances"`
Confirm *bool `json:"confirm"`
PromoteFrom string `json:"promote_from"`
Subdomain string `json:"subdomain"`
NotificationURL string `json:"notification_url"`
CpuLimit *float64 `json:"cpu_limit"`
MemoryLimit *int `json:"memory_limit"`
}
// createStage handles POST /api/projects/{id}/stages.
@@ -64,6 +66,15 @@ func (s *Server) createStage(w http.ResponseWriter, r *http.Request) {
confirm = *req.Confirm
}
var cpuLimit float64
if req.CpuLimit != nil {
cpuLimit = *req.CpuLimit
}
var memoryLimit int
if req.MemoryLimit != nil {
memoryLimit = *req.MemoryLimit
}
stage, err := s.store.CreateStage(store.Stage{
ProjectID: projectID,
Name: req.Name,
@@ -74,6 +85,8 @@ func (s *Server) createStage(w http.ResponseWriter, r *http.Request) {
PromoteFrom: req.PromoteFrom,
Subdomain: req.Subdomain,
NotificationURL: req.NotificationURL,
CpuLimit: cpuLimit,
MemoryLimit: memoryLimit,
})
if err != nil {
slog.Error("failed to create stage", "error", err)
@@ -131,6 +144,12 @@ func (s *Server) updateStage(w http.ResponseWriter, r *http.Request) {
updated.PromoteFrom = req.PromoteFrom
updated.Subdomain = req.Subdomain
updated.NotificationURL = req.NotificationURL
if req.CpuLimit != nil {
updated.CpuLimit = *req.CpuLimit
}
if req.MemoryLimit != nil {
updated.MemoryLimit = *req.MemoryLimit
}
if err := s.store.UpdateStage(updated); err != nil {
slog.Error("failed to update stage", "error", err)