@@ -7,22 +7,28 @@ import (
77 "bytes"
88 "fmt"
99 "net/http"
10+ "slices"
1011 "strings"
1112
1213 actions_model "code.gitea.io/gitea/models/actions"
1314 "code.gitea.io/gitea/models/db"
15+ git_model "code.gitea.io/gitea/models/git"
16+ repo_model "code.gitea.io/gitea/models/repo"
1417 "code.gitea.io/gitea/models/unit"
1518 "code.gitea.io/gitea/modules/actions"
1619 "code.gitea.io/gitea/modules/base"
1720 "code.gitea.io/gitea/modules/container"
1821 "code.gitea.io/gitea/modules/git"
22+ "code.gitea.io/gitea/modules/log"
1923 "code.gitea.io/gitea/modules/optional"
2024 "code.gitea.io/gitea/modules/setting"
25+ "code.gitea.io/gitea/modules/util"
2126 "code.gitea.io/gitea/routers/web/repo"
2227 "code.gitea.io/gitea/services/context"
2328 "code.gitea.io/gitea/services/convert"
2429
2530 "github.com/nektos/act/pkg/model"
31+ "gopkg.in/yaml.v3"
2632)
2733
2834const (
@@ -58,8 +64,13 @@ func MustEnableActions(ctx *context.Context) {
5864func List (ctx * context.Context ) {
5965 ctx .Data ["Title" ] = ctx .Tr ("actions.actions" )
6066 ctx .Data ["PageIsActions" ] = true
67+ workflowID := ctx .FormString ("workflow" )
68+ actorID := ctx .FormInt64 ("actor" )
69+ status := ctx .FormInt ("status" )
70+ ctx .Data ["CurWorkflow" ] = workflowID
6171
6272 var workflows []Workflow
73+ var curWorkflow * model.Workflow
6374 if empty , err := ctx .Repo .GitRepo .IsEmpty (); err != nil {
6475 ctx .ServerError ("IsEmpty" , err )
6576 return
@@ -140,6 +151,10 @@ func List(ctx *context.Context) {
140151 workflow .ErrMsg = ctx .Locale .TrString ("actions.runs.no_job" )
141152 }
142153 workflows = append (workflows , workflow )
154+
155+ if workflow .Entry .Name () == workflowID {
156+ curWorkflow = wf
157+ }
143158 }
144159 }
145160 ctx .Data ["workflows" ] = workflows
@@ -150,17 +165,46 @@ func List(ctx *context.Context) {
150165 page = 1
151166 }
152167
153- workflow := ctx .FormString ("workflow" )
154- actorID := ctx .FormInt64 ("actor" )
155- status := ctx .FormInt ("status" )
156- ctx .Data ["CurWorkflow" ] = workflow
157-
158168 actionsConfig := ctx .Repo .Repository .MustGetUnit (ctx , unit .TypeActions ).ActionsConfig ()
159169 ctx .Data ["ActionsConfig" ] = actionsConfig
160170
161- if len (workflow ) > 0 && ctx .Repo .IsAdmin () {
171+ if len (workflowID ) > 0 && ctx .Repo .IsAdmin () {
162172 ctx .Data ["AllowDisableOrEnableWorkflow" ] = true
163- ctx .Data ["CurWorkflowDisabled" ] = actionsConfig .IsWorkflowDisabled (workflow )
173+ isWorkflowDisabled := actionsConfig .IsWorkflowDisabled (workflowID )
174+ ctx .Data ["CurWorkflowDisabled" ] = isWorkflowDisabled
175+
176+ if ! isWorkflowDisabled && curWorkflow != nil {
177+ workflowDispatchConfig := workflowDispatchConfig (curWorkflow )
178+ if workflowDispatchConfig != nil {
179+ ctx .Data ["WorkflowDispatchConfig" ] = workflowDispatchConfig
180+
181+ branchOpts := git_model.FindBranchOptions {
182+ RepoID : ctx .Repo .Repository .ID ,
183+ IsDeletedBranch : optional .Some (false ),
184+ ListOptions : db.ListOptions {
185+ ListAll : true ,
186+ },
187+ }
188+ branches , err := git_model .FindBranchNames (ctx , branchOpts )
189+ if err != nil {
190+ ctx .ServerError ("FindBranchNames" , err )
191+ return
192+ }
193+ // always put default branch on the top if it exists
194+ if slices .Contains (branches , ctx .Repo .Repository .DefaultBranch ) {
195+ branches = util .SliceRemoveAll (branches , ctx .Repo .Repository .DefaultBranch )
196+ branches = append ([]string {ctx .Repo .Repository .DefaultBranch }, branches ... )
197+ }
198+ ctx .Data ["Branches" ] = branches
199+
200+ tags , err := repo_model .GetTagNamesByRepoID (ctx , ctx .Repo .Repository .ID )
201+ if err != nil {
202+ ctx .ServerError ("GetTagNamesByRepoID" , err )
203+ return
204+ }
205+ ctx .Data ["Tags" ] = tags
206+ }
207+ }
164208 }
165209
166210 // if status or actor query param is not given to frontend href, (href="/<repoLink>/actions")
@@ -177,7 +221,7 @@ func List(ctx *context.Context) {
177221 PageSize : convert .ToCorrectPageSize (ctx .FormInt ("limit" )),
178222 },
179223 RepoID : ctx .Repo .Repository .ID ,
180- WorkflowID : workflow ,
224+ WorkflowID : workflowID ,
181225 TriggerUserID : actorID ,
182226 }
183227
@@ -214,11 +258,94 @@ func List(ctx *context.Context) {
214258
215259 pager := context .NewPagination (int (total ), opts .PageSize , opts .Page , 5 )
216260 pager .SetDefaultParams (ctx )
217- pager .AddParamString ("workflow" , workflow )
261+ pager .AddParamString ("workflow" , workflowID )
218262 pager .AddParamString ("actor" , fmt .Sprint (actorID ))
219263 pager .AddParamString ("status" , fmt .Sprint (status ))
220264 ctx .Data ["Page" ] = pager
221265 ctx .Data ["HasWorkflowsOrRuns" ] = len (workflows ) > 0 || len (runs ) > 0
222266
223267 ctx .HTML (http .StatusOK , tplListActions )
224268}
269+
270+ type WorkflowDispatchInput struct {
271+ Name string `yaml:"name"`
272+ Description string `yaml:"description"`
273+ Required bool `yaml:"required"`
274+ Default string `yaml:"default"`
275+ Type string `yaml:"type"`
276+ Options []string `yaml:"options"`
277+ }
278+
279+ type WorkflowDispatch struct {
280+ Inputs []WorkflowDispatchInput
281+ }
282+
283+ func workflowDispatchConfig (w * model.Workflow ) * WorkflowDispatch {
284+ switch w .RawOn .Kind {
285+ case yaml .ScalarNode :
286+ var val string
287+ if ! decodeNode (w .RawOn , & val ) {
288+ return nil
289+ }
290+ if val == "workflow_dispatch" {
291+ return & WorkflowDispatch {}
292+ }
293+ case yaml .SequenceNode :
294+ var val []string
295+ if ! decodeNode (w .RawOn , & val ) {
296+ return nil
297+ }
298+ for _ , v := range val {
299+ if v == "workflow_dispatch" {
300+ return & WorkflowDispatch {}
301+ }
302+ }
303+ case yaml .MappingNode :
304+ var val map [string ]yaml.Node
305+ if ! decodeNode (w .RawOn , & val ) {
306+ return nil
307+ }
308+
309+ workflowDispatchNode , found := val ["workflow_dispatch" ]
310+ if ! found {
311+ return nil
312+ }
313+
314+ var workflowDispatch WorkflowDispatch
315+ var workflowDispatchVal map [string ]yaml.Node
316+ if ! decodeNode (workflowDispatchNode , & workflowDispatchVal ) {
317+ return & workflowDispatch
318+ }
319+
320+ inputsNode , found := workflowDispatchVal ["inputs" ]
321+ if ! found || inputsNode .Kind != yaml .MappingNode {
322+ return & workflowDispatch
323+ }
324+
325+ i := 0
326+ for {
327+ if i + 1 >= len (inputsNode .Content ) {
328+ break
329+ }
330+ var input WorkflowDispatchInput
331+ if decodeNode (* inputsNode .Content [i + 1 ], & input ) {
332+ input .Name = inputsNode .Content [i ].Value
333+ workflowDispatch .Inputs = append (workflowDispatch .Inputs , input )
334+ }
335+ i += 2
336+ }
337+ return & workflowDispatch
338+
339+ default :
340+ return nil
341+ }
342+ return nil
343+ }
344+
345+ func decodeNode (node yaml.Node , out any ) bool {
346+ if err := node .Decode (out ); err != nil {
347+ log .Warn ("Failed to decode node %v into %T: %v" , node , out , err )
348+ return false
349+ }
350+ return true
351+ }
0 commit comments