diff --git a/cmd/generate.go b/cmd/generate.go index 2cff24458d3d3..5922617217af6 100644 --- a/cmd/generate.go +++ b/cmd/generate.go @@ -70,12 +70,12 @@ func runGenerateInternalToken(c *cli.Context) error { } func runGenerateLfsJwtSecret(c *cli.Context) error { - JWTSecretBase64, err := generate.NewJwtSecretBase64() + _, jwtSecretBase64, err := generate.NewJwtSecretBase64() if err != nil { return err } - fmt.Printf("%s", JWTSecretBase64) + fmt.Printf("%s", jwtSecretBase64) if isatty.IsTerminal(os.Stdout.Fd()) { fmt.Printf("\n") diff --git a/models/fixtures/label.yml b/models/fixtures/label.yml index ab4d5ef944045..2242b90dcdc23 100644 --- a/models/fixtures/label.yml +++ b/models/fixtures/label.yml @@ -7,6 +7,7 @@ exclusive: false num_issues: 2 num_closed_issues: 0 + archived_unix: 0 - id: 2 @@ -17,6 +18,7 @@ exclusive: false num_issues: 1 num_closed_issues: 1 + archived_unix: 0 - id: 3 @@ -27,6 +29,7 @@ exclusive: false num_issues: 0 num_closed_issues: 0 + archived_unix: 0 - id: 4 @@ -37,6 +40,7 @@ exclusive: false num_issues: 1 num_closed_issues: 0 + archived_unix: 0 - id: 5 @@ -47,6 +51,7 @@ exclusive: false num_issues: 0 num_closed_issues: 0 + archived_unix: 0 - id: 6 @@ -57,6 +62,7 @@ exclusive: false num_issues: 0 num_closed_issues: 0 + archived_unix: 0 - id: 7 @@ -67,6 +73,7 @@ exclusive: true num_issues: 0 num_closed_issues: 0 + archived_unix: 0 - id: 8 @@ -77,6 +84,7 @@ exclusive: true num_issues: 0 num_closed_issues: 0 + archived_unix: 0 - id: 9 @@ -87,3 +95,4 @@ exclusive: true num_issues: 0 num_closed_issues: 0 + archived_unix: 0 diff --git a/models/issues/label.go b/models/issues/label.go index 57a2e67f8cd51..70906efb47d75 100644 --- a/models/issues/label.go +++ b/models/issues/label.go @@ -97,6 +97,8 @@ type Label struct { QueryString string `xorm:"-"` IsSelected bool `xorm:"-"` IsExcluded bool `xorm:"-"` + + ArchivedUnix timeutil.TimeStamp `xorm:"DEFAULT NULL"` } func init() { @@ -109,6 +111,15 @@ func (l *Label) CalOpenIssues() { l.NumOpenIssues = l.NumIssues - l.NumClosedIssues } +// SetArchived set the label as archived +func (l *Label) SetArchived(isArchived bool) { + if isArchived && l.ArchivedUnix.IsZero() { + l.ArchivedUnix = timeutil.TimeStampNow() + } else { + l.ArchivedUnix = timeutil.TimeStamp(0) + } +} + // CalOpenOrgIssues calculates the open issues of a label for a specific repo func (l *Label) CalOpenOrgIssues(ctx context.Context, repoID, labelID int64) { counts, _ := CountIssuesByRepo(ctx, &IssuesOptions{ @@ -153,6 +164,11 @@ func (l *Label) BelongsToOrg() bool { return l.OrgID > 0 } +// IsArchived returns true if label is an archived +func (l *Label) IsArchived() bool { + return l.ArchivedUnix > 0 +} + // BelongsToRepo returns true if label is a repository label func (l *Label) BelongsToRepo() bool { return l.RepoID > 0 @@ -211,7 +227,7 @@ func UpdateLabel(l *Label) error { } l.Color = color - return updateLabelCols(db.DefaultContext, l, "name", "description", "color", "exclusive") + return updateLabelCols(db.DefaultContext, l, "name", "description", "color", "exclusive", "archived_unix") } // DeleteLabel delete a label diff --git a/models/issues/label_test.go b/models/issues/label_test.go index 1bc5a1a93544f..3f0e980b318be 100644 --- a/models/issues/label_test.go +++ b/models/issues/label_test.go @@ -11,6 +11,7 @@ import ( repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/timeutil" "github.com/stretchr/testify/assert" ) @@ -259,11 +260,12 @@ func TestUpdateLabel(t *testing.T) { label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1}) // make sure update wont overwrite it update := &issues_model.Label{ - ID: label.ID, - Color: "#ffff00", - Name: "newLabelName", - Description: label.Description, - Exclusive: false, + ID: label.ID, + Color: "#ffff00", + Name: "newLabelName", + Description: label.Description, + Exclusive: false, + ArchivedUnix: timeutil.TimeStamp(0), } label.Color = update.Color label.Name = update.Name @@ -273,6 +275,7 @@ func TestUpdateLabel(t *testing.T) { assert.EqualValues(t, label.Color, newLabel.Color) assert.EqualValues(t, label.Name, newLabel.Name) assert.EqualValues(t, label.Description, newLabel.Description) + assert.EqualValues(t, newLabel.ArchivedUnix, 0) unittest.CheckConsistencyFor(t, &issues_model.Label{}, &repo_model.Repository{}) } diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 55107439b0484..7a126593d1458 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -522,6 +522,8 @@ var migrations = []Migration{ NewMigration("Drop deleted branch table", v1_21.DropDeletedBranchTable), // v270 -> v271 NewMigration("Fix PackageProperty typo", v1_21.FixPackagePropertyTypo), + // v271 -> v272 + NewMigration("Allow archiving labels", v1_21.AddArchivedUnixColumInLabelTable), } // GetCurrentDBVersion returns the current db version diff --git a/models/migrations/v1_21/v271.go b/models/migrations/v1_21/v271.go new file mode 100644 index 0000000000000..098f6499d57e5 --- /dev/null +++ b/models/migrations/v1_21/v271.go @@ -0,0 +1,16 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_21 //nolint +import ( + "code.gitea.io/gitea/modules/timeutil" + + "xorm.io/xorm" +) + +func AddArchivedUnixColumInLabelTable(x *xorm.Engine) error { + type Label struct { + ArchivedUnix timeutil.TimeStamp `xorm:"DEFAULT NULL"` + } + return x.Sync(new(Label)) +} diff --git a/models/repo/repo.go b/models/repo/repo.go index 3d1f2dcfa8a67..b37948fea77a6 100644 --- a/models/repo/repo.go +++ b/models/repo/repo.go @@ -391,7 +391,13 @@ func (repo *Repository) MustGetUnit(ctx context.Context, tp unit.Type) *RepoUnit Type: tp, Config: new(IssuesConfig), } + } else if tp == unit.TypeActions { + return &RepoUnit{ + Type: tp, + Config: new(ActionsConfig), + } } + return &RepoUnit{ Type: tp, Config: new(UnitConfig), diff --git a/models/repo/repo_unit.go b/models/repo/repo_unit.go index 7c1af95bf0921..cf9ff93d32153 100644 --- a/models/repo/repo_unit.go +++ b/models/repo/repo_unit.go @@ -6,6 +6,7 @@ package repo import ( "context" "fmt" + "strings" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/unit" @@ -162,6 +163,42 @@ func (cfg *PullRequestsConfig) GetDefaultMergeStyle() MergeStyle { return MergeStyleMerge } +type ActionsConfig struct { + DisabledWorkflows []string +} + +func (cfg *ActionsConfig) EnableWorkflow(file string) { + cfg.DisabledWorkflows = util.SliceRemoveAll(cfg.DisabledWorkflows, file) +} + +func (cfg *ActionsConfig) ToString() string { + return strings.Join(cfg.DisabledWorkflows, ",") +} + +func (cfg *ActionsConfig) IsWorkflowDisabled(file string) bool { + return util.SliceContains(cfg.DisabledWorkflows, file) +} + +func (cfg *ActionsConfig) DisableWorkflow(file string) { + for _, workflow := range cfg.DisabledWorkflows { + if file == workflow { + return + } + } + + cfg.DisabledWorkflows = append(cfg.DisabledWorkflows, file) +} + +// FromDB fills up a ActionsConfig from serialized format. +func (cfg *ActionsConfig) FromDB(bs []byte) error { + return json.UnmarshalHandleDoubleEncode(bs, &cfg) +} + +// ToDB exports a ActionsConfig to a serialized format. +func (cfg *ActionsConfig) ToDB() ([]byte, error) { + return json.Marshal(cfg) +} + // BeforeSet is invoked from XORM before setting the value of a field of this object. func (r *RepoUnit) BeforeSet(colName string, val xorm.Cell) { switch colName { @@ -175,7 +212,9 @@ func (r *RepoUnit) BeforeSet(colName string, val xorm.Cell) { r.Config = new(PullRequestsConfig) case unit.TypeIssues: r.Config = new(IssuesConfig) - case unit.TypeCode, unit.TypeReleases, unit.TypeWiki, unit.TypeProjects, unit.TypePackages, unit.TypeActions: + case unit.TypeActions: + r.Config = new(ActionsConfig) + case unit.TypeCode, unit.TypeReleases, unit.TypeWiki, unit.TypeProjects, unit.TypePackages: fallthrough default: r.Config = new(UnitConfig) @@ -218,6 +257,11 @@ func (r *RepoUnit) ExternalTrackerConfig() *ExternalTrackerConfig { return r.Config.(*ExternalTrackerConfig) } +// ActionsConfig returns config for unit.ActionsConfig +func (r *RepoUnit) ActionsConfig() *ActionsConfig { + return r.Config.(*ActionsConfig) +} + func getUnitsByRepoID(ctx context.Context, repoID int64) (units []*RepoUnit, err error) { var tmpUnits []*RepoUnit if err := db.GetEngine(ctx).Where("repo_id = ?", repoID).Find(&tmpUnits); err != nil { diff --git a/models/repo/repo_unit_test.go b/models/repo/repo_unit_test.go new file mode 100644 index 0000000000000..a760594013859 --- /dev/null +++ b/models/repo/repo_unit_test.go @@ -0,0 +1,30 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package repo + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestActionsConfig(t *testing.T) { + cfg := &ActionsConfig{} + cfg.DisableWorkflow("test1.yaml") + assert.EqualValues(t, []string{"test1.yaml"}, cfg.DisabledWorkflows) + + cfg.DisableWorkflow("test1.yaml") + assert.EqualValues(t, []string{"test1.yaml"}, cfg.DisabledWorkflows) + + cfg.EnableWorkflow("test1.yaml") + assert.EqualValues(t, []string{}, cfg.DisabledWorkflows) + + cfg.EnableWorkflow("test1.yaml") + assert.EqualValues(t, []string{}, cfg.DisabledWorkflows) + + cfg.DisableWorkflow("test1.yaml") + cfg.DisableWorkflow("test2.yaml") + cfg.DisableWorkflow("test3.yaml") + assert.EqualValues(t, "test1.yaml,test2.yaml,test3.yaml", cfg.ToString()) +} diff --git a/modules/generate/generate.go b/modules/generate/generate.go index 8b82976e85f3c..ee3c76059bc89 100644 --- a/modules/generate/generate.go +++ b/modules/generate/generate.go @@ -49,12 +49,12 @@ func NewJwtSecret() ([]byte, error) { } // NewJwtSecretBase64 generates a new base64 encoded value intended to be used for JWT secrets. -func NewJwtSecretBase64() (string, error) { +func NewJwtSecretBase64() ([]byte, string, error) { bytes, err := NewJwtSecret() if err != nil { - return "", err + return nil, "", err } - return base64.RawURLEncoding.EncodeToString(bytes), nil + return bytes, base64.RawURLEncoding.EncodeToString(bytes), nil } // NewSecretKey generate a new value intended to be used by SECRET_KEY. diff --git a/modules/setting/lfs.go b/modules/setting/lfs.go index 21a46a8cce42b..a5ea537cef50d 100644 --- a/modules/setting/lfs.go +++ b/modules/setting/lfs.go @@ -9,6 +9,7 @@ import ( "time" "code.gitea.io/gitea/modules/generate" + "code.gitea.io/gitea/modules/util" ) // LFS represents the configuration for Git LFS @@ -56,17 +57,14 @@ func loadLFSFrom(rootCfg ConfigProvider) error { LFS.HTTPAuthExpiry = sec.Key("LFS_HTTP_AUTH_EXPIRY").MustDuration(24 * time.Hour) - if !LFS.StartServer { + if !LFS.StartServer || !InstallLock { return nil } LFS.JWTSecretBase64 = loadSecret(rootCfg.Section("server"), "LFS_JWT_SECRET_URI", "LFS_JWT_SECRET") - - LFS.JWTSecretBytes = make([]byte, 32) - n, err := base64.RawURLEncoding.Decode(LFS.JWTSecretBytes, []byte(LFS.JWTSecretBase64)) - - if (err != nil || n != 32) && InstallLock { - LFS.JWTSecretBase64, err = generate.NewJwtSecretBase64() + LFS.JWTSecretBytes, err = util.Base64FixedDecode(base64.RawURLEncoding, []byte(LFS.JWTSecretBase64), 32) + if err != nil { + LFS.JWTSecretBytes, LFS.JWTSecretBase64, err = generate.NewJwtSecretBase64() if err != nil { return fmt.Errorf("error generating JWT Secret for custom config: %v", err) } diff --git a/modules/setting/oauth2.go b/modules/setting/oauth2.go index b88070681a7d8..ab82393106fac 100644 --- a/modules/setting/oauth2.go +++ b/modules/setting/oauth2.go @@ -10,6 +10,7 @@ import ( "code.gitea.io/gitea/modules/generate" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/util" ) // OAuth2UsernameType is enum describing the way gitea 'name' should be generated from oauth2 data @@ -129,21 +130,19 @@ func loadOAuth2From(rootCfg ConfigProvider) { } if InstallLock { - key := make([]byte, 32) - n, err := base64.RawURLEncoding.Decode(key, []byte(OAuth2.JWTSecretBase64)) - if err != nil || n != 32 { - key, err = generate.NewJwtSecret() + if _, err := util.Base64FixedDecode(base64.RawURLEncoding, []byte(OAuth2.JWTSecretBase64), 32); err != nil { + key, err := generate.NewJwtSecret() if err != nil { log.Fatal("error generating JWT secret: %v", err) } - secretBase64 := base64.RawURLEncoding.EncodeToString(key) + OAuth2.JWTSecretBase64 = base64.RawURLEncoding.EncodeToString(key) saveCfg, err := rootCfg.PrepareSaving() if err != nil { log.Fatal("save oauth2.JWT_SECRET failed: %v", err) } - rootCfg.Section("oauth2").Key("JWT_SECRET").SetValue(secretBase64) - saveCfg.Section("oauth2").Key("JWT_SECRET").SetValue(secretBase64) + rootCfg.Section("oauth2").Key("JWT_SECRET").SetValue(OAuth2.JWTSecretBase64) + saveCfg.Section("oauth2").Key("JWT_SECRET").SetValue(OAuth2.JWTSecretBase64) if err := saveCfg.Save(); err != nil { log.Fatal("save oauth2.JWT_SECRET failed: %v", err) } diff --git a/modules/structs/issue_label.go b/modules/structs/issue_label.go index 2610d3e93f187..bf68726d79379 100644 --- a/modules/structs/issue_label.go +++ b/modules/structs/issue_label.go @@ -11,6 +11,8 @@ type Label struct { Name string `json:"name"` // example: false Exclusive bool `json:"exclusive"` + // example: false + IsArchived bool `json:"is_archived"` // example: 00aabb Color string `json:"color"` Description string `json:"description"` @@ -27,6 +29,8 @@ type CreateLabelOption struct { // example: #00aabb Color string `json:"color" binding:"Required"` Description string `json:"description"` + // example: false + IsArchived bool `json:"is_archived"` } // EditLabelOption options for editing a label @@ -37,6 +41,8 @@ type EditLabelOption struct { // example: #00aabb Color *string `json:"color"` Description *string `json:"description"` + // example: false + IsArchived *bool `json:"is_archived"` } // IssueLabelsOption a collection of labels diff --git a/modules/util/util.go b/modules/util/util.go index 9d5e6c1e89303..cc25539967537 100644 --- a/modules/util/util.go +++ b/modules/util/util.go @@ -6,6 +6,7 @@ package util import ( "bytes" "crypto/rand" + "encoding/base64" "fmt" "math/big" "strconv" @@ -261,3 +262,13 @@ func ToFloat64(number any) (float64, error) { func ToPointer[T any](val T) *T { return &val } + +func Base64FixedDecode(encoding *base64.Encoding, src []byte, length int) ([]byte, error) { + decoded := make([]byte, encoding.DecodedLen(len(src))+3) + if n, err := encoding.Decode(decoded, src); err != nil { + return nil, err + } else if n != length { + return nil, fmt.Errorf("invalid base64 decoded length: %d, expects: %d", n, length) + } + return decoded[:length], nil +} diff --git a/modules/util/util_test.go b/modules/util/util_test.go index c5830ce01cb22..8509d8acedc68 100644 --- a/modules/util/util_test.go +++ b/modules/util/util_test.go @@ -4,6 +4,7 @@ package util import ( + "encoding/base64" "regexp" "strings" "testing" @@ -233,3 +234,16 @@ func TestToPointer(t *testing.T) { val123 := 123 assert.False(t, &val123 == ToPointer(val123)) } + +func TestBase64FixedDecode(t *testing.T) { + _, err := Base64FixedDecode(base64.RawURLEncoding, []byte("abcd"), 32) + assert.ErrorContains(t, err, "invalid base64 decoded length") + _, err = Base64FixedDecode(base64.RawURLEncoding, []byte(strings.Repeat("a", 64)), 32) + assert.ErrorContains(t, err, "invalid base64 decoded length") + + str32 := strings.Repeat("x", 32) + encoded32 := base64.RawURLEncoding.EncodeToString([]byte(str32)) + decoded32, err := Base64FixedDecode(base64.RawURLEncoding, []byte(encoded32), 32) + assert.NoError(t, err) + assert.Equal(t, str32, string(decoded32)) +} diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 30fa899c9d85b..2f32a9df7000a 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1491,6 +1491,8 @@ issues.label_title = Name issues.label_description = Description issues.label_color = Color issues.label_exclusive = Exclusive +issues.label_archive = Archive Label +issues.label_archive_tooltip= Archived labels are excluded from the label search when applying labels to an issue. Existing labels on issues remain unaffected, allowing you to retire obsolete labels without losing information. issues.label_exclusive_desc = Name the label scope/item to make it mutually exclusive with other scope/ labels. issues.label_exclusive_warning = Any conflicting scoped labels will be removed when editing the labels of an issue or pull request. issues.label_count = %d labels @@ -3489,6 +3491,11 @@ runs.status_no_select = All status runs.no_results = No results matched. runs.no_runs = The workflow has no runs yet. +workflow.disable = Disable Workflow +workflow.disable_success = Workflow '%s' disabled successfully. +workflow.enable = Enable Workflow +workflow.enable_success = Workflow '%s' enabled successfully. + need_approval_desc = Need approval to run workflows for fork pull request. variables = Variables diff --git a/routers/api/v1/org/label.go b/routers/api/v1/org/label.go index 183c1e6cc8a8c..9ef28d4db9267 100644 --- a/routers/api/v1/org/label.go +++ b/routers/api/v1/org/label.go @@ -209,6 +209,7 @@ func EditLabel(ctx *context.APIContext) { if form.Description != nil { l.Description = *form.Description } + l.SetArchived(form.IsArchived != nil && *form.IsArchived) if err := issues_model.UpdateLabel(l); err != nil { ctx.Error(http.StatusInternalServerError, "UpdateLabel", err) return diff --git a/routers/api/v1/repo/label.go b/routers/api/v1/repo/label.go index 6cb231f596c8b..fc9a16b58aa89 100644 --- a/routers/api/v1/repo/label.go +++ b/routers/api/v1/repo/label.go @@ -151,7 +151,6 @@ func CreateLabel(ctx *context.APIContext) { return } form.Color = color - l := &issues_model.Label{ Name: form.Name, Exclusive: form.Exclusive, @@ -159,6 +158,7 @@ func CreateLabel(ctx *context.APIContext) { RepoID: ctx.Repo.Repository.ID, Description: form.Description, } + l.SetArchived(form.IsArchived) if err := issues_model.NewLabel(ctx, l); err != nil { ctx.Error(http.StatusInternalServerError, "NewLabel", err) return @@ -231,6 +231,7 @@ func EditLabel(ctx *context.APIContext) { if form.Description != nil { l.Description = *form.Description } + l.SetArchived(form.IsArchived != nil && *form.IsArchived) if err := issues_model.UpdateLabel(l); err != nil { ctx.Error(http.StatusInternalServerError, "UpdateLabel", err) return diff --git a/routers/install/install.go b/routers/install/install.go index aa9c0f498681b..99ea7b0738c93 100644 --- a/routers/install/install.go +++ b/routers/install/install.go @@ -415,7 +415,7 @@ func SubmitInstall(ctx *context.Context) { cfg.Section("server").Key("LFS_START_SERVER").SetValue("true") cfg.Section("lfs").Key("PATH").SetValue(form.LFSRootPath) var lfsJwtSecret string - if lfsJwtSecret, err = generate.NewJwtSecretBase64(); err != nil { + if _, lfsJwtSecret, err = generate.NewJwtSecretBase64(); err != nil { ctx.RenderWithErr(ctx.Tr("install.lfs_jwt_secret_failed", err), tplInstall, &form) return } diff --git a/routers/web/org/org_labels.go b/routers/web/org/org_labels.go index a9f9e963d4970..2c7725e38da5c 100644 --- a/routers/web/org/org_labels.go +++ b/routers/web/org/org_labels.go @@ -75,6 +75,7 @@ func UpdateLabel(ctx *context.Context) { l.Exclusive = form.Exclusive l.Description = form.Description l.Color = form.Color + l.SetArchived(form.IsArchived) if err := issues_model.UpdateLabel(l); err != nil { ctx.ServerError("UpdateLabel", err) return diff --git a/routers/web/repo/actions/actions.go b/routers/web/repo/actions/actions.go index 5a12f52dcd142..b0f4b6f8971da 100644 --- a/routers/web/repo/actions/actions.go +++ b/routers/web/repo/actions/actions.go @@ -137,6 +137,15 @@ func List(ctx *context.Context) { actorID := ctx.FormInt64("actor") status := ctx.FormInt("status") ctx.Data["CurWorkflow"] = workflow + + actionsConfig := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypeActions).ActionsConfig() + ctx.Data["ActionsConfig"] = actionsConfig + + if len(workflow) > 0 && ctx.Repo.IsAdmin() { + ctx.Data["AllowDisableOrEnableWorkflow"] = true + ctx.Data["CurWorkflowDisabled"] = actionsConfig.IsWorkflowDisabled(workflow) + } + // if status or actor query param is not given to frontend href, (href="//actions") // they will be 0 by default, which indicates get all status or actors ctx.Data["CurActor"] = actorID diff --git a/routers/web/repo/actions/view.go b/routers/web/repo/actions/view.go index abb1f6b66b979..af2ec21e4bdda 100644 --- a/routers/web/repo/actions/view.go +++ b/routers/web/repo/actions/view.go @@ -17,6 +17,7 @@ import ( actions_model "code.gitea.io/gitea/models/actions" "code.gitea.io/gitea/models/db" + repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/modules/actions" "code.gitea.io/gitea/modules/base" @@ -572,3 +573,43 @@ func ArtifactsDownloadView(ctx *context_module.Context) { } } } + +func DisableWorkflowFile(ctx *context_module.Context) { + disableOrEnableWorkflowFile(ctx, false) +} + +func EnableWorkflowFile(ctx *context_module.Context) { + disableOrEnableWorkflowFile(ctx, true) +} + +func disableOrEnableWorkflowFile(ctx *context_module.Context, isEnable 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 isEnable { + cfg.EnableWorkflow(workflow) + } else { + cfg.DisableWorkflow(workflow) + } + + if err := repo_model.UpdateRepoUnit(cfgUnit); err != nil { + ctx.ServerError("UpdateRepoUnit", err) + return + } + + if isEnable { + ctx.Flash.Success(ctx.Tr("actions.workflow.enable_success", workflow)) + } else { + ctx.Flash.Success(ctx.Tr("actions.workflow.disable_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) +} diff --git a/routers/web/repo/issue_label.go b/routers/web/repo/issue_label.go index 5d326bab58084..257610d3af54b 100644 --- a/routers/web/repo/issue_label.go +++ b/routers/web/repo/issue_label.go @@ -14,6 +14,7 @@ import ( "code.gitea.io/gitea/modules/label" "code.gitea.io/gitea/modules/log" repo_module "code.gitea.io/gitea/modules/repository" + "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/services/forms" issue_service "code.gitea.io/gitea/services/issue" @@ -111,11 +112,12 @@ func NewLabel(ctx *context.Context) { } l := &issues_model.Label{ - RepoID: ctx.Repo.Repository.ID, - Name: form.Title, - Exclusive: form.Exclusive, - Description: form.Description, - Color: form.Color, + RepoID: ctx.Repo.Repository.ID, + Name: form.Title, + Exclusive: form.Exclusive, + Description: form.Description, + Color: form.Color, + ArchivedUnix: timeutil.TimeStamp(0), } if err := issues_model.NewLabel(ctx, l); err != nil { ctx.ServerError("NewLabel", err) @@ -137,11 +139,12 @@ func UpdateLabel(ctx *context.Context) { } return } - l.Name = form.Title l.Exclusive = form.Exclusive l.Description = form.Description l.Color = form.Color + + l.SetArchived(form.IsArchived) if err := issues_model.UpdateLabel(l); err != nil { ctx.ServerError("UpdateLabel", err) return diff --git a/routers/web/repo/issue_label_test.go b/routers/web/repo/issue_label_test.go index 4c9a359438015..e29582f9687ff 100644 --- a/routers/web/repo/issue_label_test.go +++ b/routers/web/repo/issue_label_test.go @@ -97,9 +97,10 @@ func TestUpdateLabel(t *testing.T) { test.LoadUser(t, ctx, 2) test.LoadRepo(t, ctx, 1) web.SetForm(ctx, &forms.CreateLabelForm{ - ID: 2, - Title: "newnameforlabel", - Color: "#abcdef", + ID: 2, + Title: "newnameforlabel", + Color: "#abcdef", + IsArchived: true, }) UpdateLabel(ctx) assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.Status()) diff --git a/routers/web/web.go b/routers/web/web.go index f857a36b04a8d..e70e360d5991d 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -1200,6 +1200,8 @@ func registerRoutes(m *web.Route) { m.Group("/actions", func() { m.Get("", actions.List) + m.Post("/disable", reqRepoAdmin, actions.DisableWorkflowFile) + m.Post("/enable", reqRepoAdmin, actions.EnableWorkflowFile) m.Group("/runs/{run}", func() { m.Combo(""). diff --git a/services/actions/notifier_helper.go b/services/actions/notifier_helper.go index 0cc2d17f4ef44..75c99ff19cd2b 100644 --- a/services/actions/notifier_helper.go +++ b/services/actions/notifier_helper.go @@ -150,7 +150,14 @@ func notify(ctx context.Context, input *notifyInput) error { if len(workflows) == 0 { log.Trace("repo %s with commit %s couldn't find workflows", input.Repo.RepoPath(), commit.ID) } else { + actionsConfig := input.Repo.MustGetUnit(ctx, unit_model.TypeActions).ActionsConfig() + for _, wf := range workflows { + if actionsConfig.IsWorkflowDisabled(wf.EntryName) { + log.Trace("repo %s has disable workflows %s", input.Repo.RepoPath(), wf.EntryName) + continue + } + if wf.TriggerEvent != actions_module.GithubEventPullRequestTarget { detectedWorkflows = append(detectedWorkflows, wf) } diff --git a/services/auth/source/oauth2/jwtsigningkey.go b/services/auth/source/oauth2/jwtsigningkey.go index ff0d426e2239c..eca0b8b7e1205 100644 --- a/services/auth/source/oauth2/jwtsigningkey.go +++ b/services/auth/source/oauth2/jwtsigningkey.go @@ -336,16 +336,7 @@ func InitSigningKey() error { // loadSymmetricKey checks if the configured secret is valid. // If it is not valid, it will return an error. func loadSymmetricKey() (any, error) { - key := make([]byte, 32) - n, err := base64.RawURLEncoding.Decode(key, []byte(setting.OAuth2.JWTSecretBase64)) - if err != nil { - return nil, err - } - if n != 32 { - return nil, fmt.Errorf("JWT secret must be 32 bytes long") - } - - return key, nil + return util.Base64FixedDecode(base64.RawURLEncoding, []byte(setting.OAuth2.JWTSecretBase64), 32) } // loadOrCreateAsymmetricKey checks if the configured private key exists. diff --git a/services/auth/source/oauth2/providers.go b/services/auth/source/oauth2/providers.go index 16620fad6dc34..7572aa20c0a13 100644 --- a/services/auth/source/oauth2/providers.go +++ b/services/auth/source/oauth2/providers.go @@ -56,7 +56,7 @@ func (p *AuthSourceProvider) DisplayName() string { func (p *AuthSourceProvider) IconHTML() template.HTML { if p.iconURL != "" { - img := fmt.Sprintf(`%s`, + img := fmt.Sprintf(`%s`, html.EscapeString(p.iconURL), html.EscapeString(p.DisplayName()), ) return template.HTML(img) diff --git a/services/convert/issue.go b/services/convert/issue.go index d81840f025210..33fad31d48ae1 100644 --- a/services/convert/issue.go +++ b/services/convert/issue.go @@ -208,6 +208,7 @@ func ToLabel(label *issues_model.Label, repo *repo_model.Repository, org *user_m Exclusive: label.Exclusive, Color: strings.TrimLeft(label.Color, "#"), Description: label.Description, + IsArchived: label.IsArchived(), } // calculate URL diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go index 8c763e17cbb5d..b36c8cc9b6613 100644 --- a/services/forms/repo_form.go +++ b/services/forms/repo_form.go @@ -569,6 +569,7 @@ type CreateLabelForm struct { ID int64 Title string `binding:"Required;MaxSize(50)" locale:"repo.issues.label_title"` Exclusive bool `form:"exclusive"` + IsArchived bool `form:"is_archived"` Description string `binding:"MaxSize(200)" locale:"repo.issues.label_description"` Color string `binding:"Required;MaxSize(7)" locale:"repo.issues.label_color"` } diff --git a/templates/repo/actions/list.tmpl b/templates/repo/actions/list.tmpl index e73cf71f6beed..9d256820aea71 100644 --- a/templates/repo/actions/list.tmpl +++ b/templates/repo/actions/list.tmpl @@ -2,6 +2,8 @@
{{template "repo/header" .}}
+ {{template "base/alert" .}} +
- {{template "repo/actions/runs_list" .}}
diff --git a/templates/repo/issue/labels/edit_delete_label.tmpl b/templates/repo/issue/labels/edit_delete_label.tmpl index b4eb6be7fcd1d..d64782090cc84 100644 --- a/templates/repo/issue/labels/edit_delete_label.tmpl +++ b/templates/repo/issue/labels/edit_delete_label.tmpl @@ -33,6 +33,16 @@
{{svg "octicon-alert"}} {{.locale.Tr "repo.issues.label_exclusive_warning" | Safe}}
+
+
+
+
+ + +
+ + {{svg "octicon-info"}} +
diff --git a/templates/repo/issue/labels/label_list.tmpl b/templates/repo/issue/labels/label_list.tmpl index 9eee95be92369..c15833d952d9a 100644 --- a/templates/repo/issue/labels/label_list.tmpl +++ b/templates/repo/issue/labels/label_list.tmpl @@ -44,10 +44,10 @@
diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 8cf5332bafc48..a5bea8a4cb941 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -17057,6 +17057,11 @@ "x-go-name": "Exclusive", "example": false }, + "is_archived": { + "type": "boolean", + "x-go-name": "IsArchived", + "example": false + }, "name": { "type": "string", "x-go-name": "Name" @@ -18001,6 +18006,11 @@ "x-go-name": "Exclusive", "example": false }, + "is_archived": { + "type": "boolean", + "x-go-name": "IsArchived", + "example": false + }, "name": { "type": "string", "x-go-name": "Name" @@ -19479,6 +19489,11 @@ "format": "int64", "x-go-name": "ID" }, + "is_archived": { + "type": "boolean", + "x-go-name": "IsArchived", + "example": false + }, "name": { "type": "string", "x-go-name": "Name" diff --git a/web_src/css/base.css b/web_src/css/base.css index d44f949318a73..eca08fa1b910a 100644 --- a/web_src/css/base.css +++ b/web_src/css/base.css @@ -653,6 +653,18 @@ a.label, color: var(--color-text); } +/* replace item margin on secondary menu items with gap and remove both the + negative margins on the menu as well as margin on the items */ +.ui.secondary.menu { + margin-left: 0; + margin-right: 0; + gap: .35714286em; +} +.ui.secondary.menu .item { + margin-left: 0; + margin-right: 0; +} + .ui.secondary.menu .dropdown.item:hover, .ui.secondary.menu a.item:hover { color: var(--color-text); @@ -670,6 +682,11 @@ a.label, padding-right: 0.85714286em; } +/* remove the menu clearfix so that it won't add undesired gaps when using "gap" */ +.ui.menu::after { + content: normal; +} + .ui.menu .dropdown.item .menu { background: var(--color-body); } diff --git a/web_src/css/features/dropzone.css b/web_src/css/features/dropzone.css index 0ce067ef30ec7..509dbf1dfe127 100644 --- a/web_src/css/features/dropzone.css +++ b/web_src/css/features/dropzone.css @@ -51,3 +51,9 @@ .dropzone .dz-preview:hover .dz-image img { filter: opacity(0.5) !important; } + +.ui .field .dropzone .dz-preview .dz-progress { + /* by default the progress-bar is vertically centered (top: 50%), it's better to put it after the "details (size, filename)", + then the layout from top to bottom is: size, filename, progress */ + top: 7em; +} diff --git a/web_src/js/features/comp/LabelEdit.js b/web_src/js/features/comp/LabelEdit.js index 18676d25e6cc9..2a22190e10dd9 100644 --- a/web_src/js/features/comp/LabelEdit.js +++ b/web_src/js/features/comp/LabelEdit.js @@ -36,7 +36,7 @@ export function initCompLabelEdit(selector) { $('.new-label.modal').modal({ onApprove() { $('.new-label.form').trigger('submit'); - } + }, }).modal('show'); return false; }); @@ -49,6 +49,9 @@ export function initCompLabelEdit(selector) { const nameInput = $('.edit-label .label-name-input'); nameInput.val($(this).data('title')); + const isArchivedCheckbox = $('.edit-label .label-is-archived-input'); + isArchivedCheckbox.prop('checked', this.hasAttribute('data-is-archived')); + const exclusiveCheckbox = $('.edit-label .label-exclusive-input'); exclusiveCheckbox.prop('checked', this.hasAttribute('data-exclusive')); // Warn when label was previously not exclusive and used in issues @@ -64,7 +67,7 @@ export function initCompLabelEdit(selector) { $('.edit-label.modal').modal({ onApprove() { $('.edit-label.form').trigger('submit'); - } + }, }).modal('show'); return false; });