mirror of
https://github.com/go-gitea/gitea.git
synced 2025-10-27 12:45:25 +01: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:
parent
9a73a1fb83
commit
0d740a6a72
@ -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,28 +172,48 @@ 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 {
|
func prepareWorkflowDispatchTemplate(ctx *context.Context, workflowInfos []WorkflowInfo, curWorkflowID string) {
|
||||||
workflowDispatchConfig := workflowDispatchConfig(curWorkflow)
|
actionsConfig := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypeActions).ActionsConfig()
|
||||||
if workflowDispatchConfig != nil {
|
if curWorkflowID == "" || !ctx.Repo.CanWrite(unit.TypeActions) || actionsConfig.IsWorkflowDisabled(curWorkflowID) {
|
||||||
ctx.Data["WorkflowDispatchConfig"] = workflowDispatchConfig
|
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{
|
branchOpts := git_model.FindBranchOptions{
|
||||||
RepoID: ctx.Repo.Repository.ID,
|
RepoID: ctx.Repo.Repository.ID,
|
||||||
@ -230,28 +225,22 @@ func prepareWorkflowDispatchTemplate(ctx *context.Context, commit *git.Commit) (
|
|||||||
branches, err := git_model.FindBranchNames(ctx, branchOpts)
|
branches, err := git_model.FindBranchNames(ctx, branchOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("FindBranchNames", err)
|
ctx.ServerError("FindBranchNames", err)
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
// always put default branch on the top if it exists
|
// always put default branch on the top
|
||||||
if slices.Contains(branches, ctx.Repo.Repository.DefaultBranch) {
|
|
||||||
branches = util.SliceRemoveAll(branches, ctx.Repo.Repository.DefaultBranch)
|
branches = util.SliceRemoveAll(branches, ctx.Repo.Repository.DefaultBranch)
|
||||||
branches = append([]string{ctx.Repo.Repository.DefaultBranch}, branches...)
|
branches = append([]string{ctx.Repo.Repository.DefaultBranch}, branches...)
|
||||||
}
|
|
||||||
ctx.Data["Branches"] = branches
|
ctx.Data["Branches"] = branches
|
||||||
|
|
||||||
tags, err := repo_model.GetTagNamesByRepoID(ctx, ctx.Repo.Repository.ID)
|
tags, err := repo_model.GetTagNamesByRepoID(ctx, ctx.Repo.Repository.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("GetTagNamesByRepoID", err)
|
ctx.ServerError("GetTagNamesByRepoID", err)
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
ctx.Data["Tags"] = tags
|
ctx.Data["Tags"] = tags
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return workflows
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func prepareWorkflowList(ctx *context.Context, workflows []Workflow) {
|
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 {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user