mirror of
				https://github.com/go-gitea/gitea
				synced 2025-10-31 11:28:24 +00:00 
			
		
		
		
	Improve online runner check (#35722)
This PR moves "no online runner" warning to the runs list. 
A job's `runs-on` may contain expressions like `runs-on: [self-hosted,
"${{ inputs.chosen-os }}"]` so the value of `runs-on` may be different
in each run. We cannot check it through the workflow file.
<details>
  <summary>Screenshots</summary>
Before:
<img width="960" alt="3d2a91746271d8b1f12c8f7d20eba550"
src="https://github.com/user-attachments/assets/7a972c50-db97-49d2-b12b-c1a439732a11"
/>
After:
<img width="960" alt="image"
src="https://github.com/user-attachments/assets/fc076e0e-bd08-4afe-99b9-c0eb0fd2c7e7"
/>
</details>
This PR also splits `prepareWorkflowDispatchTemplate` function into 2
functions:
- `prepareWorkflowTemplate` get and check all of the workflows
- `prepareWorkflowDispatchTemplate` only prepare workflow dispatch
config for `workflow_dispatch` workflows.
---------
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
			
			
This commit is contained in:
		| @@ -14,6 +14,7 @@ import ( | |||||||
| 	repo_model "code.gitea.io/gitea/models/repo" | 	repo_model "code.gitea.io/gitea/models/repo" | ||||||
| 	"code.gitea.io/gitea/models/shared/types" | 	"code.gitea.io/gitea/models/shared/types" | ||||||
| 	user_model "code.gitea.io/gitea/models/user" | 	user_model "code.gitea.io/gitea/models/user" | ||||||
|  | 	"code.gitea.io/gitea/modules/container" | ||||||
| 	"code.gitea.io/gitea/modules/optional" | 	"code.gitea.io/gitea/modules/optional" | ||||||
| 	"code.gitea.io/gitea/modules/setting" | 	"code.gitea.io/gitea/modules/setting" | ||||||
| 	"code.gitea.io/gitea/modules/timeutil" | 	"code.gitea.io/gitea/modules/timeutil" | ||||||
| @@ -173,6 +174,13 @@ func (r *ActionRunner) GenerateToken() (err error) { | |||||||
| 	return err | 	return err | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // CanMatchLabels checks whether the runner's labels can match a job's "runs-on" | ||||||
|  | // See https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#jobsjob_idruns-on | ||||||
|  | func (r *ActionRunner) CanMatchLabels(jobRunsOn []string) bool { | ||||||
|  | 	runnerLabelSet := container.SetOf(r.AgentLabels...) | ||||||
|  | 	return runnerLabelSet.Contains(jobRunsOn...) // match all labels | ||||||
|  | } | ||||||
|  |  | ||||||
| func init() { | func init() { | ||||||
| 	db.RegisterModel(&ActionRunner{}) | 	db.RegisterModel(&ActionRunner{}) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -13,7 +13,6 @@ import ( | |||||||
| 	auth_model "code.gitea.io/gitea/models/auth" | 	auth_model "code.gitea.io/gitea/models/auth" | ||||||
| 	"code.gitea.io/gitea/models/db" | 	"code.gitea.io/gitea/models/db" | ||||||
| 	"code.gitea.io/gitea/models/unit" | 	"code.gitea.io/gitea/models/unit" | ||||||
| 	"code.gitea.io/gitea/modules/container" |  | ||||||
| 	"code.gitea.io/gitea/modules/log" | 	"code.gitea.io/gitea/modules/log" | ||||||
| 	"code.gitea.io/gitea/modules/setting" | 	"code.gitea.io/gitea/modules/setting" | ||||||
| 	"code.gitea.io/gitea/modules/timeutil" | 	"code.gitea.io/gitea/modules/timeutil" | ||||||
| @@ -245,7 +244,7 @@ func CreateTaskForRunner(ctx context.Context, runner *ActionRunner) (*ActionTask | |||||||
| 	var job *ActionRunJob | 	var job *ActionRunJob | ||||||
| 	log.Trace("runner labels: %v", runner.AgentLabels) | 	log.Trace("runner labels: %v", runner.AgentLabels) | ||||||
| 	for _, v := range jobs { | 	for _, v := range jobs { | ||||||
| 		if isSubset(runner.AgentLabels, v.RunsOn) { | 		if runner.CanMatchLabels(v.RunsOn) { | ||||||
| 			job = v | 			job = v | ||||||
| 			break | 			break | ||||||
| 		} | 		} | ||||||
| @@ -475,20 +474,6 @@ func FindOldTasksToExpire(ctx context.Context, olderThan timeutil.TimeStamp, lim | |||||||
| 		Find(&tasks) | 		Find(&tasks) | ||||||
| } | } | ||||||
|  |  | ||||||
| func isSubset(set, subset []string) bool { |  | ||||||
| 	m := make(container.Set[string], len(set)) |  | ||||||
| 	for _, v := range set { |  | ||||||
| 		m.Add(v) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	for _, v := range subset { |  | ||||||
| 		if !m.Contains(v) { |  | ||||||
| 			return false |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return true |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func convertTimestamp(timestamp *timestamppb.Timestamp) timeutil.TimeStamp { | func convertTimestamp(timestamp *timestamppb.Timestamp) timeutil.TimeStamp { | ||||||
| 	if timestamp.GetSeconds() == 0 && timestamp.GetNanos() == 0 { | 	if timestamp.GetSeconds() == 0 && timestamp.GetNanos() == 0 { | ||||||
| 		return timeutil.TimeStamp(0) | 		return timeutil.TimeStamp(0) | ||||||
|   | |||||||
| @@ -28,7 +28,7 @@ import ( | |||||||
| 	"code.gitea.io/gitea/services/context" | 	"code.gitea.io/gitea/services/context" | ||||||
| 	"code.gitea.io/gitea/services/convert" | 	"code.gitea.io/gitea/services/convert" | ||||||
|  |  | ||||||
| 	"github.com/nektos/act/pkg/model" | 	act_model "github.com/nektos/act/pkg/model" | ||||||
| 	"gopkg.in/yaml.v3" | 	"gopkg.in/yaml.v3" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -38,9 +38,10 @@ const ( | |||||||
| 	tplViewActions           templates.TplName = "repo/actions/view" | 	tplViewActions           templates.TplName = "repo/actions/view" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type Workflow struct { | type WorkflowInfo struct { | ||||||
| 	Entry  git.TreeEntry | 	Entry    git.TreeEntry | ||||||
| 	ErrMsg string | 	ErrMsg   string | ||||||
|  | 	Workflow *act_model.Workflow | ||||||
| } | } | ||||||
|  |  | ||||||
| // MustEnableActions check if actions are enabled in settings | // MustEnableActions check if actions are enabled in settings | ||||||
| @@ -77,7 +78,11 @@ func List(ctx *context.Context) { | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	workflows := prepareWorkflowDispatchTemplate(ctx, commit) | 	workflows, curWorkflowID := prepareWorkflowTemplate(ctx, commit) | ||||||
|  | 	if ctx.Written() { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	prepareWorkflowDispatchTemplate(ctx, workflows, curWorkflowID) | ||||||
| 	if ctx.Written() { | 	if ctx.Written() { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| @@ -112,55 +117,41 @@ func WorkflowDispatchInputs(ctx *context.Context) { | |||||||
| 		ctx.ServerError("GetTagCommit/GetBranchCommit", err) | 		ctx.ServerError("GetTagCommit/GetBranchCommit", err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	prepareWorkflowDispatchTemplate(ctx, commit) | 	workflows, curWorkflowID := prepareWorkflowTemplate(ctx, commit) | ||||||
|  | 	if ctx.Written() { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	prepareWorkflowDispatchTemplate(ctx, workflows, curWorkflowID) | ||||||
| 	if ctx.Written() { | 	if ctx.Written() { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	ctx.HTML(http.StatusOK, tplDispatchInputsActions) | 	ctx.HTML(http.StatusOK, tplDispatchInputsActions) | ||||||
| } | } | ||||||
|  |  | ||||||
| func prepareWorkflowDispatchTemplate(ctx *context.Context, commit *git.Commit) (workflows []Workflow) { | func prepareWorkflowTemplate(ctx *context.Context, commit *git.Commit) (workflows []WorkflowInfo, curWorkflowID string) { | ||||||
| 	workflowID := ctx.FormString("workflow") | 	curWorkflowID = ctx.FormString("workflow") | ||||||
| 	ctx.Data["CurWorkflow"] = workflowID |  | ||||||
| 	ctx.Data["CurWorkflowExists"] = false |  | ||||||
|  |  | ||||||
| 	var curWorkflow *model.Workflow |  | ||||||
|  |  | ||||||
| 	_, entries, err := actions.ListWorkflows(commit) | 	_, entries, err := actions.ListWorkflows(commit) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		ctx.ServerError("ListWorkflows", err) | 		ctx.ServerError("ListWorkflows", err) | ||||||
| 		return nil | 		return nil, "" | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Get all runner labels | 	workflows = make([]WorkflowInfo, 0, len(entries)) | ||||||
| 	runners, err := db.Find[actions_model.ActionRunner](ctx, actions_model.FindRunnerOptions{ |  | ||||||
| 		RepoID:        ctx.Repo.Repository.ID, |  | ||||||
| 		IsOnline:      optional.Some(true), |  | ||||||
| 		WithAvailable: true, |  | ||||||
| 	}) |  | ||||||
| 	if err != nil { |  | ||||||
| 		ctx.ServerError("FindRunners", err) |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| 	allRunnerLabels := make(container.Set[string]) |  | ||||||
| 	for _, r := range runners { |  | ||||||
| 		allRunnerLabels.AddMultiple(r.AgentLabels...) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	workflows = make([]Workflow, 0, len(entries)) |  | ||||||
| 	for _, entry := range entries { | 	for _, entry := range entries { | ||||||
| 		workflow := Workflow{Entry: *entry} | 		workflow := WorkflowInfo{Entry: *entry} | ||||||
| 		content, err := actions.GetContentFromEntry(entry) | 		content, err := actions.GetContentFromEntry(entry) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			ctx.ServerError("GetContentFromEntry", err) | 			ctx.ServerError("GetContentFromEntry", err) | ||||||
| 			return nil | 			return nil, "" | ||||||
| 		} | 		} | ||||||
| 		wf, err := model.ReadWorkflow(bytes.NewReader(content)) | 		wf, err := act_model.ReadWorkflow(bytes.NewReader(content)) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			workflow.ErrMsg = ctx.Locale.TrString("actions.runs.invalid_workflow_helper", err.Error()) | 			workflow.ErrMsg = ctx.Locale.TrString("actions.runs.invalid_workflow_helper", err.Error()) | ||||||
| 			workflows = append(workflows, workflow) | 			workflows = append(workflows, workflow) | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
|  | 		workflow.Workflow = wf | ||||||
| 		// The workflow must contain at least one job without "needs". Otherwise, a deadlock will occur and no jobs will be able to run. | 		// The workflow must contain at least one job without "needs". Otherwise, a deadlock will occur and no jobs will be able to run. | ||||||
| 		hasJobWithoutNeeds := false | 		hasJobWithoutNeeds := false | ||||||
| 		// Check whether you have matching runner and a job without "needs" | 		// Check whether you have matching runner and a job without "needs" | ||||||
| @@ -173,22 +164,6 @@ func prepareWorkflowDispatchTemplate(ctx *context.Context, commit *git.Commit) ( | |||||||
| 			if !hasJobWithoutNeeds && len(j.Needs()) == 0 { | 			if !hasJobWithoutNeeds && len(j.Needs()) == 0 { | ||||||
| 				hasJobWithoutNeeds = true | 				hasJobWithoutNeeds = true | ||||||
| 			} | 			} | ||||||
| 			runsOnList := j.RunsOn() |  | ||||||
| 			for _, ro := range runsOnList { |  | ||||||
| 				if strings.Contains(ro, "${{") { |  | ||||||
| 					// Skip if it contains expressions. |  | ||||||
| 					// The expressions could be very complex and could not be evaluated here, |  | ||||||
| 					// so just skip it, it's OK since it's just a tooltip message. |  | ||||||
| 					continue |  | ||||||
| 				} |  | ||||||
| 				if !allRunnerLabels.Contains(ro) { |  | ||||||
| 					workflow.ErrMsg = ctx.Locale.TrString("actions.runs.no_matching_online_runner_helper", ro) |  | ||||||
| 					break |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 			if workflow.ErrMsg != "" { |  | ||||||
| 				break |  | ||||||
| 			} |  | ||||||
| 		} | 		} | ||||||
| 		if !hasJobWithoutNeeds { | 		if !hasJobWithoutNeeds { | ||||||
| 			workflow.ErrMsg = ctx.Locale.TrString("actions.runs.no_job_without_needs") | 			workflow.ErrMsg = ctx.Locale.TrString("actions.runs.no_job_without_needs") | ||||||
| @@ -197,61 +172,75 @@ func prepareWorkflowDispatchTemplate(ctx *context.Context, commit *git.Commit) ( | |||||||
| 			workflow.ErrMsg = ctx.Locale.TrString("actions.runs.no_job") | 			workflow.ErrMsg = ctx.Locale.TrString("actions.runs.no_job") | ||||||
| 		} | 		} | ||||||
| 		workflows = append(workflows, workflow) | 		workflows = append(workflows, workflow) | ||||||
|  |  | ||||||
| 		if workflow.Entry.Name() == workflowID { |  | ||||||
| 			curWorkflow = wf |  | ||||||
| 			ctx.Data["CurWorkflowExists"] = true |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	ctx.Data["workflows"] = workflows | 	ctx.Data["workflows"] = workflows | ||||||
| 	ctx.Data["RepoLink"] = ctx.Repo.Repository.Link() | 	ctx.Data["RepoLink"] = ctx.Repo.Repository.Link() | ||||||
|  | 	ctx.Data["AllowDisableOrEnableWorkflow"] = ctx.Repo.IsAdmin() | ||||||
| 	actionsConfig := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypeActions).ActionsConfig() | 	actionsConfig := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypeActions).ActionsConfig() | ||||||
| 	ctx.Data["ActionsConfig"] = actionsConfig | 	ctx.Data["ActionsConfig"] = actionsConfig | ||||||
|  | 	ctx.Data["CurWorkflow"] = curWorkflowID | ||||||
|  | 	ctx.Data["CurWorkflowDisabled"] = actionsConfig.IsWorkflowDisabled(curWorkflowID) | ||||||
|  |  | ||||||
| 	if len(workflowID) > 0 && ctx.Repo.CanWrite(unit.TypeActions) { | 	return workflows, curWorkflowID | ||||||
| 		ctx.Data["AllowDisableOrEnableWorkflow"] = ctx.Repo.IsAdmin() |  | ||||||
| 		isWorkflowDisabled := actionsConfig.IsWorkflowDisabled(workflowID) |  | ||||||
| 		ctx.Data["CurWorkflowDisabled"] = isWorkflowDisabled |  | ||||||
|  |  | ||||||
| 		if !isWorkflowDisabled && curWorkflow != nil { |  | ||||||
| 			workflowDispatchConfig := workflowDispatchConfig(curWorkflow) |  | ||||||
| 			if workflowDispatchConfig != nil { |  | ||||||
| 				ctx.Data["WorkflowDispatchConfig"] = workflowDispatchConfig |  | ||||||
|  |  | ||||||
| 				branchOpts := git_model.FindBranchOptions{ |  | ||||||
| 					RepoID:          ctx.Repo.Repository.ID, |  | ||||||
| 					IsDeletedBranch: optional.Some(false), |  | ||||||
| 					ListOptions: db.ListOptions{ |  | ||||||
| 						ListAll: true, |  | ||||||
| 					}, |  | ||||||
| 				} |  | ||||||
| 				branches, err := git_model.FindBranchNames(ctx, branchOpts) |  | ||||||
| 				if err != nil { |  | ||||||
| 					ctx.ServerError("FindBranchNames", err) |  | ||||||
| 					return nil |  | ||||||
| 				} |  | ||||||
| 				// always put default branch on the top if it exists |  | ||||||
| 				if slices.Contains(branches, ctx.Repo.Repository.DefaultBranch) { |  | ||||||
| 					branches = util.SliceRemoveAll(branches, ctx.Repo.Repository.DefaultBranch) |  | ||||||
| 					branches = append([]string{ctx.Repo.Repository.DefaultBranch}, branches...) |  | ||||||
| 				} |  | ||||||
| 				ctx.Data["Branches"] = branches |  | ||||||
|  |  | ||||||
| 				tags, err := repo_model.GetTagNamesByRepoID(ctx, ctx.Repo.Repository.ID) |  | ||||||
| 				if err != nil { |  | ||||||
| 					ctx.ServerError("GetTagNamesByRepoID", err) |  | ||||||
| 					return nil |  | ||||||
| 				} |  | ||||||
| 				ctx.Data["Tags"] = tags |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return workflows |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func prepareWorkflowList(ctx *context.Context, workflows []Workflow) { | func prepareWorkflowDispatchTemplate(ctx *context.Context, workflowInfos []WorkflowInfo, curWorkflowID string) { | ||||||
|  | 	actionsConfig := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypeActions).ActionsConfig() | ||||||
|  | 	if curWorkflowID == "" || !ctx.Repo.CanWrite(unit.TypeActions) || actionsConfig.IsWorkflowDisabled(curWorkflowID) { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var curWorkflow *act_model.Workflow | ||||||
|  | 	for _, workflowInfo := range workflowInfos { | ||||||
|  | 		if workflowInfo.Entry.Name() == curWorkflowID { | ||||||
|  | 			if workflowInfo.Workflow == nil { | ||||||
|  | 				log.Debug("CurWorkflowID %s is found but its workflowInfo.Workflow is nil", curWorkflowID) | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 			curWorkflow = workflowInfo.Workflow | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if curWorkflow == nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	ctx.Data["CurWorkflowExists"] = true | ||||||
|  | 	curWfDispatchCfg := workflowDispatchConfig(curWorkflow) | ||||||
|  | 	if curWfDispatchCfg == nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	ctx.Data["WorkflowDispatchConfig"] = curWfDispatchCfg | ||||||
|  |  | ||||||
|  | 	branchOpts := git_model.FindBranchOptions{ | ||||||
|  | 		RepoID:          ctx.Repo.Repository.ID, | ||||||
|  | 		IsDeletedBranch: optional.Some(false), | ||||||
|  | 		ListOptions: db.ListOptions{ | ||||||
|  | 			ListAll: true, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 	branches, err := git_model.FindBranchNames(ctx, branchOpts) | ||||||
|  | 	if err != nil { | ||||||
|  | 		ctx.ServerError("FindBranchNames", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	// always put default branch on the top | ||||||
|  | 	branches = util.SliceRemoveAll(branches, ctx.Repo.Repository.DefaultBranch) | ||||||
|  | 	branches = append([]string{ctx.Repo.Repository.DefaultBranch}, branches...) | ||||||
|  | 	ctx.Data["Branches"] = branches | ||||||
|  |  | ||||||
|  | 	tags, err := repo_model.GetTagNamesByRepoID(ctx, ctx.Repo.Repository.ID) | ||||||
|  | 	if err != nil { | ||||||
|  | 		ctx.ServerError("GetTagNamesByRepoID", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	ctx.Data["Tags"] = tags | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func prepareWorkflowList(ctx *context.Context, workflows []WorkflowInfo) { | ||||||
| 	actorID := ctx.FormInt64("actor") | 	actorID := ctx.FormInt64("actor") | ||||||
| 	status := ctx.FormInt("status") | 	status := ctx.FormInt("status") | ||||||
| 	workflowID := ctx.FormString("workflow") | 	workflowID := ctx.FormString("workflow") | ||||||
| @@ -302,6 +291,45 @@ func prepareWorkflowList(ctx *context.Context, workflows []Workflow) { | |||||||
| 		log.Error("LoadIsRefDeleted", err) | 		log.Error("LoadIsRefDeleted", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	// Check for each run if there is at least one online runner that can run its jobs | ||||||
|  | 	runErrors := make(map[int64]string) | ||||||
|  | 	runners, err := db.Find[actions_model.ActionRunner](ctx, actions_model.FindRunnerOptions{ | ||||||
|  | 		RepoID:        ctx.Repo.Repository.ID, | ||||||
|  | 		IsOnline:      optional.Some(true), | ||||||
|  | 		WithAvailable: true, | ||||||
|  | 	}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		ctx.ServerError("FindRunners", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	for _, run := range runs { | ||||||
|  | 		if !run.Status.In(actions_model.StatusWaiting, actions_model.StatusRunning) { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		jobs, err := actions_model.GetRunJobsByRunID(ctx, run.ID) | ||||||
|  | 		if err != nil { | ||||||
|  | 			ctx.ServerError("GetRunJobsByRunID", err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		for _, job := range jobs { | ||||||
|  | 			if !job.Status.IsWaiting() { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 			hasOnlineRunner := false | ||||||
|  | 			for _, runner := range runners { | ||||||
|  | 				if runner.CanMatchLabels(job.RunsOn) { | ||||||
|  | 					hasOnlineRunner = true | ||||||
|  | 					break | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			if !hasOnlineRunner { | ||||||
|  | 				runErrors[run.ID] = ctx.Locale.TrString("actions.runs.no_matching_online_runner_helper", strings.Join(job.RunsOn, ",")) | ||||||
|  | 				break | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	ctx.Data["RunErrors"] = runErrors | ||||||
|  |  | ||||||
| 	ctx.Data["Runs"] = runs | 	ctx.Data["Runs"] = runs | ||||||
|  |  | ||||||
| 	actors, err := actions_model.GetActors(ctx, ctx.Repo.Repository.ID) | 	actors, err := actions_model.GetActors(ctx, ctx.Repo.Repository.ID) | ||||||
| @@ -362,7 +390,7 @@ type WorkflowDispatch struct { | |||||||
| 	Inputs []WorkflowDispatchInput | 	Inputs []WorkflowDispatchInput | ||||||
| } | } | ||||||
|  |  | ||||||
| func workflowDispatchConfig(w *model.Workflow) *WorkflowDispatch { | func workflowDispatchConfig(w *act_model.Workflow) *WorkflowDispatch { | ||||||
| 	switch w.RawOn.Kind { | 	switch w.RawOn.Kind { | ||||||
| 	case yaml.ScalarNode: | 	case yaml.ScalarNode: | ||||||
| 		var val string | 		var val string | ||||||
|   | |||||||
| @@ -7,14 +7,14 @@ | |||||||
| 		{{if .HasWorkflowsOrRuns}} | 		{{if .HasWorkflowsOrRuns}} | ||||||
| 		<div class="ui stackable grid"> | 		<div class="ui stackable grid"> | ||||||
| 			<div class="four wide column"> | 			<div class="four wide column"> | ||||||
| 				<div class="ui fluid vertical menu"> | 				<div class="ui fluid vertical menu flex-items-block"> | ||||||
| 					<a class="item{{if not $.CurWorkflow}} active{{end}}" href="?actor={{$.CurActor}}&status={{$.CurStatus}}">{{ctx.Locale.Tr "actions.runs.all_workflows"}}</a> | 					<a class="item {{if not $.CurWorkflow}}active{{end}}" href="?actor={{$.CurActor}}&status={{$.CurStatus}}">{{ctx.Locale.Tr "actions.runs.all_workflows"}}</a> | ||||||
| 					{{range .workflows}} | 					{{range .workflows}} | ||||||
| 						<a class="item{{if eq .Entry.Name $.CurWorkflow}} active{{end}}" href="?workflow={{.Entry.Name}}&actor={{$.CurActor}}&status={{$.CurStatus}}">{{.Entry.Name}} | 						<a class="item {{if eq .Entry.Name $.CurWorkflow}}active{{end}}" href="?workflow={{.Entry.Name}}&actor={{$.CurActor}}&status={{$.CurStatus}}"> | ||||||
|  | 							<span class="gt-ellipsis">{{.Entry.Name}}</span> | ||||||
|  |  | ||||||
| 							{{if .ErrMsg}} | 							{{if .ErrMsg}} | ||||||
| 								<span data-tooltip-content="{{.ErrMsg}}"> | 								<span class="flex-text-inline" data-tooltip-content="{{.ErrMsg}}">{{svg "octicon-alert" 16 "text red"}}</span> | ||||||
| 									{{svg "octicon-alert" 16 "text red"}} |  | ||||||
| 								</span> |  | ||||||
| 							{{end}} | 							{{end}} | ||||||
|  |  | ||||||
| 							{{if $.ActionsConfig.IsWorkflowDisabled .Entry.Name}} | 							{{if $.ActionsConfig.IsWorkflowDisabled .Entry.Name}} | ||||||
|   | |||||||
| @@ -16,6 +16,7 @@ | |||||||
| 				</a> | 				</a> | ||||||
| 				<div class="flex-item-body"> | 				<div class="flex-item-body"> | ||||||
| 					<span><b>{{if not $.CurWorkflow}}{{$run.WorkflowID}} {{end}}#{{$run.Index}}</b>:</span> | 					<span><b>{{if not $.CurWorkflow}}{{$run.WorkflowID}} {{end}}#{{$run.Index}}</b>:</span> | ||||||
|  |  | ||||||
| 					{{- if $run.ScheduleID -}} | 					{{- if $run.ScheduleID -}} | ||||||
| 						{{ctx.Locale.Tr "actions.runs.scheduled"}} | 						{{ctx.Locale.Tr "actions.runs.scheduled"}} | ||||||
| 					{{- else -}} | 					{{- else -}} | ||||||
| @@ -24,6 +25,13 @@ | |||||||
| 						{{ctx.Locale.Tr "actions.runs.pushed_by"}} | 						{{ctx.Locale.Tr "actions.runs.pushed_by"}} | ||||||
| 						<a href="{{$run.TriggerUser.HomeLink}}">{{$run.TriggerUser.GetDisplayName}}</a> | 						<a href="{{$run.TriggerUser.HomeLink}}">{{$run.TriggerUser.GetDisplayName}}</a> | ||||||
| 					{{- end -}} | 					{{- end -}} | ||||||
|  |  | ||||||
|  | 					{{$errMsg := index $.RunErrors $run.ID}} | ||||||
|  | 					{{if $errMsg}} | ||||||
|  | 						<span class="flex-text-inline" data-tooltip-content="{{$errMsg}}"> | ||||||
|  | 							{{svg "octicon-alert" 16 "text red"}} | ||||||
|  | 						</span> | ||||||
|  | 					{{end}} | ||||||
| 				</div> | 				</div> | ||||||
| 			</div> | 			</div> | ||||||
| 			<div class="flex-item-trailing"> | 			<div class="flex-item-trailing"> | ||||||
|   | |||||||
| @@ -1104,6 +1104,7 @@ table th[data-sortt-desc] .svg { | |||||||
| } | } | ||||||
|  |  | ||||||
| .ui.list.flex-items-block > .item, | .ui.list.flex-items-block > .item, | ||||||
|  | .ui.vertical.menu.flex-items-block > .item, | ||||||
| .ui.form .field > label.flex-text-block, /* override fomantic "block" style */ | .ui.form .field > label.flex-text-block, /* override fomantic "block" style */ | ||||||
| .flex-items-block > .item, | .flex-items-block > .item, | ||||||
| .flex-text-block { | .flex-text-block { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user