Enable the global workflow and require_action in org setting

Signed-off-by: Alex Lau(AvengerMoJo) <avengermojo@gmail.com>
This commit is contained in:
Alex Lau(AvengerMoJo) 2024-04-09 01:09:17 +08:00
parent d547b53cca
commit 5243490071
No known key found for this signature in database
GPG Key ID: E924333A268354EA
17 changed files with 551 additions and 1 deletions

View File

@ -0,0 +1,82 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
// WIP RequireAction
package actions
import (
"context"
"code.gitea.io/gitea/models/db"
//"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/timeutil"
"xorm.io/builder"
)
type RequireAction struct {
ID int64 `xorm:"pk autoincr"`
OrgID int64 `xorm:"index"`
RepoName string `xorm:"VARCHAR(255)"`
WorkflowName string `xorm:"VARCHAR(255) UNIQUE(require_action) NOT NULL"`
//Description string `xorm:"LONGTEXT NOT NULL"`
CreatedUnix timeutil.TimeStamp `xorm:"created NOT NULL"`
UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
// RepoRange string // glob match which repositories could use this runner
}
type GlobalWorkflow struct {
RepoName string
Filename string
}
func init() {
db.RegisterModel(new(RequireAction))
}
type FindRequireActionOptions struct {
db.ListOptions
RequireActionID int64
OrgID int64
RepoName string
}
func (opts FindRequireActionOptions) ToConds() builder.Cond {
cond := builder.NewCond()
if opts.OrgID > 0 {
cond = cond.And(builder.Eq{"org_id": opts.OrgID})
}
if opts.RequireActionID > 0 {
cond = cond.And(builder.Eq{"id": opts.RequireActionID})
}
if opts.RepoName != "" {
cond = cond.And(builder.Eq{"repo_name": opts.RepoName})
}
return cond
}
// LoadAttributes loads the attributes of the require action
func (r *RequireAction) LoadAttributes(ctx context.Context) error {
// place holder for now.
return nil
}
// if the workflow is removable
func (r *RequireAction) Removable(orgID int64) bool {
// everyone can remove for now
if r.OrgID == orgID {
return true
}
return false
}
func AddRequireAction(ctx context.Context, orgID int64, repoName string, workflowName string) (*RequireAction, error) {
ra := &RequireAction{
OrgID: orgID,
RepoName: repoName,
WorkflowName: workflowName,
}
return ra, db.Insert(ctx, ra)
}

View File

@ -576,7 +576,10 @@ var migrations = []Migration{
// Gitea 1.22.0 ends at 294
// v294 -> v295
NewMigration("Add unique index for project issue table", v1_23.AddUniqueIndexForProjectIssue),
// v295 -> v296
NewMigration("Add RequireAction Global Workflow", v1_23.AddRequireActionTable),
}
// GetCurrentDBVersion returns the current db version

View File

@ -0,0 +1,21 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package v1_23 //nolint
import (
"code.gitea.io/gitea/modules/timeutil"
"xorm.io/xorm"
)
func AddRequireActionTable(x *xorm.Engine) error {
type RequireAction struct {
ID int64 `xorm:"pk autoincr"`
OrgID int64 `xorm:"index"`
RepoName string `xorm:"VARCHAR(255)"`
WorkflowName string `xorm:"VARCHAR(255) UNIQUE(require_action) NOT NULL"`
CreatedUnix timeutil.TimeStamp `xorm:"created NOT NULL"`
UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
}
return x.Sync(new(RequireAction))
}

View File

@ -168,10 +168,27 @@ func (cfg *PullRequestsConfig) GetDefaultMergeStyle() MergeStyle {
type ActionsConfig struct {
DisabledWorkflows []string
EnabledGlobalWorkflows []string
}
func (cfg *ActionsConfig) EnableWorkflow(file string) {
cfg.DisabledWorkflows = util.SliceRemoveAll(cfg.DisabledWorkflows, file)
}
func (cfg *ActionsConfig) DisableGlobalWorkflow(file string) {
cfg.EnabledGlobalWorkflows = util.SliceRemoveAll(cfg.EnabledGlobalWorkflows, file)
}
func (cfg *ActionsConfig) IsGlobalWorkflowEnabled(file string) bool {
return slices.Contains(cfg.EnabledGlobalWorkflows, file)
}
func (cfg *ActionsConfig) EnableGlobalWorkflow(file string) {
cfg.EnabledGlobalWorkflows = append(cfg.EnabledGlobalWorkflows, file)
}
func (cfg *ActionsConfig) GetGlobalWorkflow() []string {
return cfg.EnabledGlobalWorkflows
}
func (cfg *ActionsConfig) ToString() string {

View File

@ -3640,11 +3640,34 @@ runs.no_workflows.documentation = For more information on Gitea Actions, see <a
runs.no_runs = The workflow has no runs yet.
runs.empty_commit_message = (empty commit message)
require_action = Require Actions
require_action.require_action_manage_panel = Require Actions Management Panel
require_action.enable_global_workflow = How to Enable Global Workflow
require_action.id = ID
require_action.add = Add Global Workflow
require_action.add_require_action = Enable selected Workflow
require_action.new = Create New
require_action.status = Status
require_action.version = Version
require_action.repo = Repo Name
require_action.workflow = Workflow Filename
require_action.link = Link
require_action.remove = Remove
require_action.none = No Require Actions Available.
require_action.creation.failed = Create Global Require Action %s Failed.
require_action.creation.success = Create Global Require Action %s successfully.
workflow.disable = Disable Workflow
workflow.disable_success = Workflow '%s' disabled successfully.
workflow.enable = Enable Workflow
workflow.enable_success = Workflow '%s' enabled successfully.
workflow.disabled = Workflow is disabled.
workflow.global = Global
workflow.global_disable = Disable Global Require
workflow.global_disable_success = Global Require '%s' disabled successfully.
workflow.global_enable = Enable Global Require
workflow.global_enable_success = Global Require '%s' enabled successfully.
workflow.global_enabled = Global Require is disabled.
need_approval_desc = Need approval to run workflows for fork pull request.

View File

@ -0,0 +1,14 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
// WIP RequireAction
package setting
import (
"code.gitea.io/gitea/services/context"
)
func RedirectToRepoSetting(ctx *context.Context) {
ctx.Redirect(ctx.Org.OrgLink + "/settings/actions/require_action")
}

View File

@ -145,6 +145,7 @@ func List(ctx *context.Context) {
workflow := ctx.FormString("workflow")
actorID := ctx.FormInt64("actor")
status := ctx.FormInt("status")
isGlobal := false
ctx.Data["CurWorkflow"] = workflow
actionsConfig := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypeActions).ActionsConfig()
@ -153,6 +154,8 @@ func List(ctx *context.Context) {
if len(workflow) > 0 && ctx.Repo.IsAdmin() {
ctx.Data["AllowDisableOrEnableWorkflow"] = true
ctx.Data["CurWorkflowDisabled"] = actionsConfig.IsWorkflowDisabled(workflow)
ctx.Data["CurGlobalWorkflowEnable"] = actionsConfig.IsGlobalWorkflowEnabled(workflow)
isGlobal = actionsConfig.IsGlobalWorkflowEnabled(workflow)
}
// if status or actor query param is not given to frontend href, (href="/<repoLink>/actions")
@ -209,6 +212,9 @@ func List(ctx *context.Context) {
pager.AddParamString("workflow", workflow)
pager.AddParamString("actor", fmt.Sprint(actorID))
pager.AddParamString("status", fmt.Sprint(status))
if isGlobal {
pager.AddParamString("global", fmt.Sprint(isGlobal))
}
ctx.Data["Page"] = pager
ctx.Data["HasWorkflowsOrRuns"] = len(workflows) > 0 || len(runs) > 0

View File

@ -714,3 +714,38 @@ func disableOrEnableWorkflowFile(ctx *context_module.Context, isEnable bool) {
url.QueryEscape(ctx.FormString("actor")), url.QueryEscape(ctx.FormString("status")))
ctx.JSONRedirect(redirectURL)
}
func DisableGlobalWorkflowFile(ctx *context_module.Context) {
disableOrEnableGlobalWorkflowFile(ctx, true)
}
func EnableGlobalWorkflowFile(ctx *context_module.Context) {
disableOrEnableGlobalWorkflowFile(ctx, false)
}
func disableOrEnableGlobalWorkflowFile(ctx *context_module.Context, isGlobalEnable bool) {
workflow := ctx.FormString("workflow")
if len(workflow) == 0 {
ctx.ServerError("workflow", nil)
return
}
cfgUnit := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypeActions)
cfg := cfgUnit.ActionsConfig()
if isGlobalEnable {
cfg.DisableGlobalWorkflow(workflow)
} else {
cfg.EnableGlobalWorkflow(workflow)
}
if err := repo_model.UpdateRepoUnit(ctx, cfgUnit); err != nil {
ctx.ServerError("UpdateRepoUnit", err)
return
}
if isGlobalEnable {
ctx.Flash.Success(ctx.Tr("actions.workflow.global_disable_success", workflow))
} else {
ctx.Flash.Success(ctx.Tr("actions.workflow.global_enable_success", workflow))
}
redirectURL := fmt.Sprintf("%s/actions?workflow=%s&actor=%s&status=%s", ctx.Repo.RepoLink, url.QueryEscape(workflow),
url.QueryEscape(ctx.FormString("actor")), url.QueryEscape(ctx.FormString("status")))
ctx.JSONRedirect(redirectURL)
}

View File

@ -0,0 +1,80 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
// WIP RequireAction
package setting
import (
"errors"
"net/http"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/base"
// "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/services/context"
//"code.gitea.io/gitea/modules/setting"
shared "code.gitea.io/gitea/routers/web/shared/actions"
actions_model "code.gitea.io/gitea/models/actions"
)
const (
// let start with org WIP
tplOrgRequireAction base.TplName = "org/settings/actions"
)
type requireActionsCtx struct {
OrgID int64
IsOrg bool
RequireActionTemplate base.TplName
RedirectLink string
}
func getRequireActionCtx(ctx *context.Context) (*requireActionsCtx, error) {
if ctx.Data["PageIsOrgSettings"] == true {
return &requireActionsCtx{
OrgID: ctx.Org.Organization.ID,
IsOrg: true,
RequireActionTemplate: tplOrgRequireAction,
RedirectLink: ctx.Org.OrgLink + "/settings/actions/require_action",
}, nil
}
return nil, errors.New("unable to set Require Actions context")
}
// Listing all RequireAction
func RequireAction(ctx *context.Context) {
ctx.Data["ActionsTitle"] = ctx.Tr("actions.requires")
ctx.Data["PageType"] = "require_action"
ctx.Data["PageIsSharedSettingsRequireAction"] = true
vCtx, err := getRequireActionCtx(ctx)
if err != nil {
ctx.ServerError("getRequireActionCtx", err)
return
}
page := ctx.FormInt("page")
if page <= 1 { page = 1 }
opts := actions_model.FindRequireActionOptions{
ListOptions: db.ListOptions{
Page: page,
PageSize: 10,
},
}
shared.SetRequireActionContext(ctx, opts)
ctx.Data["Link"] = vCtx.RedirectLink
shared.GlobalEnableWorkflow(ctx, ctx.Org.Organization.ID)
ctx.HTML(http.StatusOK, vCtx.RequireActionTemplate)
}
func RequireActionCreate(ctx *context.Context) {
vCtx, err := getRequireActionCtx(ctx)
if err != nil {
ctx.ServerError("getRequireActionCtx", err)
return
}
shared.CreateRequireAction(ctx, vCtx.OrgID, vCtx.RedirectLink)
}

View File

@ -0,0 +1,74 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
// WIP RequireAction
package actions
import (
actions_model "code.gitea.io/gitea/models/actions"
org_model "code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/log"
//"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/forms"
actions_service "code.gitea.io/gitea/services/actions"
"code.gitea.io/gitea/services/context"
)
// SetRequireActionDeletePost response for deleting a require action workflow
func SetRequireActionContext(ctx *context.Context, opts actions_model.FindRequireActionOptions) {
require_actions, count, err := db.FindAndCount[actions_model.RequireAction](ctx, opts)
if err != nil {
ctx.ServerError("CountRequireActions", err)
return
}
ctx.Data["RequireActions"] = require_actions
ctx.Data["Total"] = count
ctx.Data["OrgID"] = ctx.Org.Organization.ID
ctx.Data["OrgName"] = ctx.Org.Organization.Name
pager := context.NewPagination(int(count), opts.PageSize, opts.Page, 5)
ctx.Data["Page"] = pager
}
// get all the available enable global workflow in the org's repo
func GlobalEnableWorkflow(ctx *context.Context, orgID int64){
var gwfList []actions_model.GlobalWorkflow
orgRepos, err := org_model.GetOrgRepositories(ctx, orgID)
if err != nil {
ctx.ServerError("GlobalEnableWorkflows get org repos: ", err)
return
}
for _, repo := range orgRepos {
repo.LoadUnits(ctx)
actionsConfig := repo.MustGetUnit(ctx, unit.TypeActions).ActionsConfig()
enabledWorkflows := actionsConfig.GetGlobalWorkflow()
for _, workflow := range enabledWorkflows {
gwf := actions_model.GlobalWorkflow{
RepoName: repo.Name,
Filename: workflow,
}
gwfList = append(gwfList, gwf)
}
}
ctx.Data["GlobalEnableWorkflows"] = gwfList
}
func CreateRequireAction(ctx *context.Context, orgID int64, redirectURL string){
ctx.Data["OrgID"] = ctx.Org.Organization.ID
form := web.GetForm(ctx).(*forms.RequireActionForm)
// log.Error("org %d, repo_name: %s, workflow_name %s", orgID, form.RepoName, form.WorkflowName)
log.Error("org %d, repo_name: %+v", orgID, form)
v, err := actions_service.CreateRequireAction(ctx, orgID, form.RepoName, form.WorkflowName)
if err != nil {
log.Error("CreateRequireAction: %v", err)
ctx.JSONError(ctx.Tr("actions.require_action.creation.failed"))
return
}
ctx.Flash.Success(ctx.Tr("actions.require_action.creation.success", v.WorkflowName))
ctx.JSONRedirect(redirectURL)
}

View File

@ -458,6 +458,14 @@ func registerRoutes(m *web.Route) {
})
}
// WIP RequireAction
addSettingsRequireActionRoutes := func() {
m.Group("/require_action", func() {
m.Get("", repo_setting.RequireAction)
m.Post("/add", web.Bind(forms.RequireActionForm{}), repo_setting.RequireActionCreate)
})
}
// FIXME: not all routes need go through same middleware.
// Especially some AJAX requests, we can reduce middleware number to improve performance.
@ -628,6 +636,7 @@ func registerRoutes(m *web.Route) {
m.Group("/actions", func() {
m.Get("", user_setting.RedirectToDefaultSetting)
addSettingsRequireActionRoutes()
addSettingsRunnersRoutes()
addSettingsSecretsRoutes()
addSettingsVariablesRoutes()
@ -925,6 +934,7 @@ func registerRoutes(m *web.Route) {
m.Group("/actions", func() {
m.Get("", org_setting.RedirectToDefaultSetting)
addSettingsRequireActionRoutes()
addSettingsRunnersRoutes()
addSettingsSecretsRoutes()
addSettingsVariablesRoutes()
@ -1358,6 +1368,8 @@ func registerRoutes(m *web.Route) {
m.Get("", actions.List)
m.Post("/disable", reqRepoAdmin, actions.DisableWorkflowFile)
m.Post("/enable", reqRepoAdmin, actions.EnableWorkflowFile)
m.Post("/global_disable", reqRepoAdmin, actions.DisableGlobalWorkflowFile)
m.Post("/global_enable", reqRepoAdmin, actions.EnableGlobalWorkflowFile)
m.Group("/runs/{run}", func() {
m.Combo("").

View File

@ -0,0 +1,18 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package actions
import (
"context"
actions_model "code.gitea.io/gitea/models/actions"
)
func CreateRequireAction(ctx context.Context, orgID int64, repoName string, workflowName string) (*actions_model.RequireAction, error) {
v, err := actions_model.AddRequireAction(ctx, orgID, repoName, workflowName)
if err != nil {
return nil, err
}
return v, nil
}

View File

@ -359,6 +359,13 @@ func (f *EditVariableForm) Validate(req *http.Request, errs binding.Errors) bind
return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
}
//WIP RequireAction create form
type RequireActionForm struct {
RepoName string `binding:"Required;MaxSize(255)"`
WorkflowName string `binding:"Required;MaxSize(255)"`
}
// NewAccessTokenForm form for creating access token
type NewAccessTokenForm struct {
Name string `binding:"Required;MaxSize(255)" locale:"settings.token_name"`

View File

@ -1,6 +1,8 @@
{{template "org/settings/layout_head" (dict "ctxData" . "pageClass" "organization settings actions")}}
<div class="org-setting-content">
{{if eq .PageType "runners"}}
{{if eq .PageType "require_action"}}
{{template "shared/actions/require_action_list" .}}
{{else if eq .PageType "runners"}}
{{template "shared/actions/runner_list" .}}
{{else if eq .PageType "secrets"}}
{{template "shared/secrets/add_list" .}}

View File

@ -29,6 +29,9 @@
<details class="item toggleable-item" {{if or .PageIsSharedSettingsRunners .PageIsSharedSettingsSecrets .PageIsSharedSettingsVariables}}open{{end}}>
<summary>{{ctx.Locale.Tr "actions.actions"}}</summary>
<div class="menu">
<a class="{{if .PageIsSharedSettingsRequireAction}}active {{end}}item" href="{{.OrgLink}}/settings/actions/require_action">
{{ctx.Locale.Tr "actions.require_action"}}
</a>
<a class="{{if .PageIsSharedSettingsRunners}}active {{end}}item" href="{{.OrgLink}}/settings/actions/runners">
{{ctx.Locale.Tr "actions.runners"}}
</a>

View File

@ -20,6 +20,9 @@
{{if $.ActionsConfig.IsWorkflowDisabled .Entry.Name}}
<div class="ui red label">{{ctx.Locale.Tr "disabled"}}</div>
{{end}}
{{if $.ActionsConfig.IsGlobalWorkflowEnabled .Entry.Name}}
<div class="ui red label">{{ctx.Locale.Tr "Global Enabled"}}</div>
{{end}}
</a>
{{end}}
</div>
@ -65,6 +68,10 @@
</div>
</div>
<!-- IsGlobalWorkflowEnabled -->
<div class="ui dropdown jump item">
<span class="text">{{ctx.Locale.Tr "actions.workflow.global"}}</span>
</div>
{{if .AllowDisableOrEnableWorkflow}}
<button class="ui jump dropdown btn interact-bg tw-p-2">
{{svg "octicon-kebab-horizontal"}}
@ -72,6 +79,9 @@
<a class="item link-action" data-url="{{$.Link}}/{{if .CurWorkflowDisabled}}enable{{else}}disable{{end}}?workflow={{$.CurWorkflow}}&actor={{.CurActor}}&status={{$.CurStatus}}">
{{if .CurWorkflowDisabled}}{{ctx.Locale.Tr "actions.workflow.enable"}}{{else}}{{ctx.Locale.Tr "actions.workflow.disable"}}{{end}}
</a>
<a class="item link-action" data-url="{{$.Link}}/{{if .CurGlobalWorkflowEnable}}global_disable{{else}}global_enable{{end}}?workflow={{$.CurWorkflow}}&actor={{.CurActor}}&status={{$.CurStatus}}">
{{if .CurGlobalWorkflowEnable}}{{ctx.Locale.Tr "actions.workflow.global_disable"}}{{else}}{{ctx.Locale.Tr "actions.workflow.global_enable"}}{{end}}
</a>
</div>
</button>
{{end}}

View File

@ -0,0 +1,143 @@
<div class="require-actions-container">
<h4 class="ui top attached header">
{{ctx.Locale.Tr "actions.require_action.require_action_manage_panel"}} ({{ctx.Locale.Tr "admin.total" .Total}})
<div class="ui right">
<div class="ui top right">
<button class="ui primary tiny button show-modal"
data-modal="#add-require-actions-modal"
data-modal-form.action="{{.Link}}/add"
data-modal-header="{{ctx.Locale.Tr "actions.require_action.add"}}"
>
{{ctx.Locale.Tr "actions.require_action.add"}}
</button>
</div>
</div>
</h4>
<div class="ui attached segment">
<form class="ui form ignore-dirty" id="require-action-list-search-form" action="{{$.Link}}">
<!-- Search Text -->
<div class="ui fluid action input">
{{template "shared/search/combo" dict "Value" .Keyword}}
<button class="ui primary button">{{ctx.Locale.Tr "actions.require_action.search"}}</button>
</div>
</form>
</div>
<div class="ui attached table segment">
<table class="ui very basic striped table unstackable">
<thead>
<tr>
<th data-sortt-asc="newest" data-sortt-desc="oldest">
{{ctx.Locale.Tr "actions.require_action.id"}}
</th>
<th data-sortt-asc="alphabetically" data-sortt-desc="reversealphabetically">
{{ctx.Locale.Tr "actions.require_action.workflow"}}
</th>
<th>{{ctx.Locale.Tr "actions.require_action.repo"}}</th>
<th>{{ctx.Locale.Tr "actions.require_action.link"}}</th>
<th>{{ctx.Locale.Tr "actions.require_action.remove"}}</th>
</tr>
</thead>
<tbody>
{{if .RequireActions}}
{{range .RequireActions}}
<tr>
<td>{{.ID}}</td>
<td><p data-tooltip-content="{{.RepoName}}">{{.WorkflowName}}</p></td>
<td>{{.RepoName}}</td>
<td><a href="/{{$.OrgName}}/{{.RepoName}}">Workflow Link</a></td>
<td class="require_action-ops">
{{if .Removable $.OrgID }}
<a href="{{$.Link}}/{{.ID}}">{{svg "octicon-x-circle-fill"}}</a>
{{end}}
</td>
</tr>
{{end}}
{{else}}
<tr>
<td class="center aligned" colspan="8">{{ctx.Locale.Tr "actions.require_action.none"}}</td>
</tr>
{{end}}
</tbody>
</table>
</div>
{{template "base/paginate" .}}
</div>
{{/* Add RequireAction dialog */}}
<div class="ui small modal" id="add-require-actions-modal">
<div class="header">
<span id="actions-modal-header">Availble Enable Workflows</span>
</div>
<form class="ui form form-fetch-action" method="post">
<div class="content">
<div class="item">
<a href="https://docs.gitea.com/usage/actions/require-action">{{ctx.Locale.Tr "actions.require_action.enable_global_workflow"}}</a>
</div>
<div class="divider"></div>
<!-- <div class="ui input"> -->
<table class="ui very basic striped table unstackable">
<thead>
<tr>
<th data-sortt-asc="alphabetically" data-sortt-desc="reversealphabetically">
{{ctx.Locale.Tr "actions.require_action.workflow"}}
</th>
<th>
{{ctx.Locale.Tr "actions.require_action.repo"}}
</th>
</tr>
</thead>
<tbody>
{{if .GlobalEnableWorkflows}}
{{range .GlobalEnableWorkflows}}
<tr>
<td><div class="field">
<div class="ui radio checkbox">
<input class="select-org-radio" name="workflow_name" type="radio" value="{{.Filename}}">
<label>{{.Filename}}</label>
</div>
<input name="repo_name" type="hidden" value="{{.RepoName}}">
</div></td>
<td><div class="field">
<a href="/{{$.OrgName}}/{{.RepoName}}">
<label>{{.RepoName}}</label>
</a>
</div></td>
</tr>
{{ end }}
{{else}}
<tr>
<td class="center aligned" colspan="8">{{ctx.Locale.Tr "actions.require_action.none"}}</td>
</tr>
{{ end }}
</tbody>
</table>
<div class="divider"></div>
<div class="item">
<a href="{{$.Link}}/add">{{ctx.Locale.Tr "actions.require_action.add_require_action"}}</a>
</div>
</div>
{{template "base/modal_actions_confirm" (dict "ModalButtonTypes" "confirm")}}
</form>
</div>
<script>
window.addEventListener('DOMContentLoaded', function() {
var checkboxes = document.querySelectorAll('.ui.radio.checkbox');
checkboxes.forEach(function(checkbox) {
checkbox.addEventListener('change', function() {
var hiddenInput = this.nextElementSibling;
var isChecked = this.querySelector('input[type="radio"]').checked;
hiddenInput.disabled = !isChecked;
// Disable other hidden inputs
checkboxes.forEach(function(otherCheckbox) {
var otherHiddenInput = otherCheckbox.nextElementSibling;
if (otherCheckbox !== checkbox) {
otherHiddenInput.disabled = isChecked;
}
});
});
});
});
</script>