diff --git a/modules/structs/actions_capabilities.go b/modules/structs/actions_capabilities.go index 2e258047ba..ce579550e1 100644 --- a/modules/structs/actions_capabilities.go +++ b/modules/structs/actions_capabilities.go @@ -21,10 +21,18 @@ type DiskInfo struct { UsedPercent float64 `json:"used_percent"` } +// DistroInfo holds Linux distribution information +type DistroInfo struct { + ID string `json:"id,omitempty"` // e.g., "ubuntu", "debian", "fedora" + VersionID string `json:"version_id,omitempty"` // e.g., "24.04", "12" + PrettyName string `json:"pretty_name,omitempty"` // e.g., "Ubuntu 24.04 LTS" +} + // RunnerCapability represents the detailed capabilities of a runner type RunnerCapability struct { OS string `json:"os"` Arch string `json:"arch"` + Distro *DistroInfo `json:"distro,omitempty"` Docker bool `json:"docker"` DockerCompose bool `json:"docker_compose"` ContainerRuntime string `json:"container_runtime,omitempty"` @@ -34,6 +42,7 @@ type RunnerCapability struct { Limitations []string `json:"limitations,omitempty"` Disk *DiskInfo `json:"disk,omitempty"` Bandwidth *BandwidthInfo `json:"bandwidth,omitempty"` + SuggestedLabels []string `json:"suggested_labels,omitempty"` } // CapabilityFeatures represents feature support flags diff --git a/options/locale/locale_en-US.json b/options/locale/locale_en-US.json index 839ebfba5c..f9c26b7327 100644 --- a/options/locale/locale_en-US.json +++ b/options/locale/locale_en-US.json @@ -3716,7 +3716,7 @@ "actions.runners.capabilities.bandwidth": "Network Bandwidth", "actions.runners.bandwidth_test_requested": "Bandwidth test requested. Results will appear on next poll.", "actions.runners.bandwidth_test_request_failed": "Failed to request bandwidth test.", - "actions.runners.check_bandwidth_now": "Check Now", + "actions.runners.check_bandwidth_now": "Check Bandwidth", "actions.runs.all_workflows": "All Workflows", "actions.runs.commit": "Commit", "actions.runs.scheduled": "Scheduled", diff --git a/routers/web/shared/actions/runners.go b/routers/web/shared/actions/runners.go index fa637ac5c8..0b3ad93ebe 100644 --- a/routers/web/shared/actions/runners.go +++ b/routers/web/shared/actions/runners.go @@ -5,6 +5,7 @@ package actions import ( "errors" + "strings" "net/http" "net/url" @@ -408,3 +409,174 @@ func findActionsRunner(ctx *context.Context, rCtx *runnersCtx) *actions_model.Ac return got[0] } +// RunnerAddLabel adds a single label to a runner +func RunnerAddLabel(ctx *context.Context) { + rCtx, err := getRunnersCtx(ctx) + if err != nil { + ctx.ServerError("getRunnersCtx", err) + return + } + + runner := findActionsRunner(ctx, rCtx) + if runner == nil { + return + } + + label := ctx.FormString("label") + if label == "" { + ctx.Flash.Warning("No label specified") + ctx.Redirect(rCtx.RedirectLink + ctx.PathParam("runnerid")) + return + } + + // Check if label already exists + for _, existing := range runner.AgentLabels { + if existing == label { + ctx.Flash.Info("Label already exists") + ctx.Redirect(rCtx.RedirectLink + ctx.PathParam("runnerid")) + return + } + } + + // Add the label + runner.AgentLabels = append(runner.AgentLabels, label) + + err = actions_model.UpdateRunner(ctx, runner, "agent_labels") + if err != nil { + log.Warn("RunnerAddLabel.UpdateRunner failed: %v", err) + ctx.Flash.Warning("Failed to add label") + ctx.Redirect(rCtx.RedirectLink + ctx.PathParam("runnerid")) + return + } + + ctx.Flash.Success("Label added: " + label) + ctx.Redirect(rCtx.RedirectLink + ctx.PathParam("runnerid")) +} + +// RunnerRemoveLabel removes a single label from a runner +func RunnerRemoveLabel(ctx *context.Context) { + rCtx, err := getRunnersCtx(ctx) + if err != nil { + ctx.ServerError("getRunnersCtx", err) + return + } + + runner := findActionsRunner(ctx, rCtx) + if runner == nil { + return + } + + label := ctx.FormString("label") + if label == "" { + ctx.Flash.Warning("No label specified") + ctx.Redirect(rCtx.RedirectLink + ctx.PathParam("runnerid")) + return + } + + // Remove the label + newLabels := make([]string, 0, len(runner.AgentLabels)) + found := false + for _, existing := range runner.AgentLabels { + if existing == label { + found = true + } else { + newLabels = append(newLabels, existing) + } + } + + if !found { + ctx.Flash.Info("Label not found") + ctx.Redirect(rCtx.RedirectLink + ctx.PathParam("runnerid")) + return + } + + runner.AgentLabels = newLabels + + err = actions_model.UpdateRunner(ctx, runner, "agent_labels") + if err != nil { + log.Warn("RunnerRemoveLabel.UpdateRunner failed: %v", err) + ctx.Flash.Warning("Failed to remove label") + ctx.Redirect(rCtx.RedirectLink + ctx.PathParam("runnerid")) + return + } + + ctx.Flash.Success("Label removed: " + label) + ctx.Redirect(rCtx.RedirectLink + ctx.PathParam("runnerid")) +} + +// RunnerUseSuggestedLabels adds all suggested labels based on capabilities +func RunnerUseSuggestedLabels(ctx *context.Context) { + rCtx, err := getRunnersCtx(ctx) + if err != nil { + ctx.ServerError("getRunnersCtx", err) + return + } + + runner := findActionsRunner(ctx, rCtx) + if runner == nil { + return + } + + // Parse capabilities to get suggested labels + if runner.CapabilitiesJSON == "" { + ctx.Flash.Warning("No capabilities data available") + ctx.Redirect(rCtx.RedirectLink + ctx.PathParam("runnerid")) + return + } + + var caps structs.RunnerCapability + if err := json.Unmarshal([]byte(runner.CapabilitiesJSON), &caps); err != nil { + ctx.Flash.Warning("Failed to parse capabilities") + ctx.Redirect(rCtx.RedirectLink + ctx.PathParam("runnerid")) + return + } + + // Build suggested labels + suggestedLabels := []string{} + existingSet := make(map[string]bool) + for _, label := range runner.AgentLabels { + existingSet[label] = true + } + + // OS-based labels + switch caps.OS { + case "linux": + suggestedLabels = append(suggestedLabels, "linux", "linux-latest") + case "windows": + suggestedLabels = append(suggestedLabels, "windows", "windows-latest") + case "darwin": + suggestedLabels = append(suggestedLabels, "macos", "macos-latest") + } + + // Distro-based labels + if caps.Distro != nil && caps.Distro.ID != "" { + suggestedLabels = append(suggestedLabels, caps.Distro.ID, caps.Distro.ID+"-latest") + } + + // Add only new labels + added := []string{} + for _, label := range suggestedLabels { + if !existingSet[label] { + runner.AgentLabels = append(runner.AgentLabels, label) + added = append(added, label) + existingSet[label] = true + } + } + + if len(added) == 0 { + ctx.Flash.Info("All suggested labels already exist") + ctx.Redirect(rCtx.RedirectLink + ctx.PathParam("runnerid")) + return + } + + err = actions_model.UpdateRunner(ctx, runner, "agent_labels") + if err != nil { + log.Warn("RunnerUseSuggestedLabels.UpdateRunner failed: %v", err) + ctx.Flash.Warning("Failed to add labels") + ctx.Redirect(rCtx.RedirectLink + ctx.PathParam("runnerid")) + return + } + + ctx.Flash.Success("Added labels: " + strings.Join(added, ", ")) + ctx.Redirect(rCtx.RedirectLink + ctx.PathParam("runnerid")) +} diff --git a/routers/web/web.go b/routers/web/web.go index 1cc2f6d2a4..5195810fc9 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -511,6 +511,9 @@ func registerWebRoutes(m *web.Router) { Post(web.Bind(forms.EditRunnerForm{}), shared_actions.RunnersEditPost) m.Post("/{runnerid}/delete", shared_actions.RunnerDeletePost) m.Post("/{runnerid}/bandwidth-test", shared_actions.RunnerRequestBandwidthTest) + m.Post("/{runnerid}/add-label", shared_actions.RunnerAddLabel) + m.Post("/{runnerid}/remove-label", shared_actions.RunnerRemoveLabel) + m.Post("/{runnerid}/use-suggested-labels", shared_actions.RunnerUseSuggestedLabels) m.Post("/reset_registration_token", shared_actions.ResetRunnerRegistrationToken) }) } diff --git a/templates/shared/actions/runner_edit.tmpl b/templates/shared/actions/runner_edit.tmpl index dabac76bf8..9412153b5f 100644 --- a/templates/shared/actions/runner_edit.tmpl +++ b/templates/shared/actions/runner_edit.tmpl @@ -3,47 +3,142 @@ {{ctx.Locale.Tr "actions.runners.runner_title"}} {{.Runner.ID}} {{.Runner.Name}}
| Version | +{{.Runner.Version}} | +
| Owner | +{{.Runner.BelongsToOwnerType.LocaleString ctx.Locale}} | +
| Labels | ++ {{range .Runner.AgentLabels}} + + {{end}} + {{if not .Runner.AgentLabels}}No labels{{end}} + | +
Based on detected capabilities. Click + to add individually.
+No capabilities reported
+No capabilities reported