mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-26 12:27:06 +00:00 
			
		
		
		
	Enable/disable owner and repo projects independently (#28805)
Part of #23318 Add menu in repo settings to allow for repo admin to decide not just if projects are enabled or disabled per repo, but also which kind of projects (repo-level/owner-level) are enabled. If repo projects disabled, don't show the projects tab.  --------- Co-authored-by: delvh <dev.lh@web.de>
This commit is contained in:
		| @@ -520,6 +520,7 @@ | |||||||
|   id: 75 |   id: 75 | ||||||
|   repo_id: 1 |   repo_id: 1 | ||||||
|   type: 8 |   type: 8 | ||||||
|  |   config: "{\"ProjectsMode\":\"all\"}" | ||||||
|   created_unix: 946684810 |   created_unix: 946684810 | ||||||
|  |  | ||||||
| - | - | ||||||
| @@ -650,12 +651,6 @@ | |||||||
|   type: 2 |   type: 2 | ||||||
|   created_unix: 946684810 |   created_unix: 946684810 | ||||||
|  |  | ||||||
| - |  | ||||||
|   id: 98 |  | ||||||
|   repo_id: 1 |  | ||||||
|   type: 8 |  | ||||||
|   created_unix: 946684810 |  | ||||||
|  |  | ||||||
| - | - | ||||||
|   id: 99 |   id: 99 | ||||||
|   repo_id: 1 |   repo_id: 1 | ||||||
|   | |||||||
| @@ -411,6 +411,11 @@ func (repo *Repository) MustGetUnit(ctx context.Context, tp unit.Type) *RepoUnit | |||||||
| 			Type:   tp, | 			Type:   tp, | ||||||
| 			Config: new(ActionsConfig), | 			Config: new(ActionsConfig), | ||||||
| 		} | 		} | ||||||
|  | 	} else if tp == unit.TypeProjects { | ||||||
|  | 		return &RepoUnit{ | ||||||
|  | 			Type:   tp, | ||||||
|  | 			Config: new(ProjectsConfig), | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return &RepoUnit{ | 	return &RepoUnit{ | ||||||
|   | |||||||
| @@ -202,6 +202,53 @@ func (cfg *ActionsConfig) ToDB() ([]byte, error) { | |||||||
| 	return json.Marshal(cfg) | 	return json.Marshal(cfg) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // ProjectsMode represents the projects enabled for a repository | ||||||
|  | type ProjectsMode string | ||||||
|  |  | ||||||
|  | const ( | ||||||
|  | 	// ProjectsModeRepo allows only repo-level projects | ||||||
|  | 	ProjectsModeRepo ProjectsMode = "repo" | ||||||
|  | 	// ProjectsModeOwner allows only owner-level projects | ||||||
|  | 	ProjectsModeOwner ProjectsMode = "owner" | ||||||
|  | 	// ProjectsModeAll allows both kinds of projects | ||||||
|  | 	ProjectsModeAll ProjectsMode = "all" | ||||||
|  | 	// ProjectsModeNone doesn't allow projects | ||||||
|  | 	ProjectsModeNone ProjectsMode = "none" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // ProjectsConfig describes projects config | ||||||
|  | type ProjectsConfig struct { | ||||||
|  | 	ProjectsMode ProjectsMode | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // FromDB fills up a ProjectsConfig from serialized format. | ||||||
|  | func (cfg *ProjectsConfig) FromDB(bs []byte) error { | ||||||
|  | 	return json.UnmarshalHandleDoubleEncode(bs, &cfg) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ToDB exports a ProjectsConfig to a serialized format. | ||||||
|  | func (cfg *ProjectsConfig) ToDB() ([]byte, error) { | ||||||
|  | 	return json.Marshal(cfg) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (cfg *ProjectsConfig) GetProjectsMode() ProjectsMode { | ||||||
|  | 	if cfg.ProjectsMode != "" { | ||||||
|  | 		return cfg.ProjectsMode | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return ProjectsModeNone | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (cfg *ProjectsConfig) IsProjectsAllowed(m ProjectsMode) bool { | ||||||
|  | 	projectsMode := cfg.GetProjectsMode() | ||||||
|  |  | ||||||
|  | 	if m == ProjectsModeNone { | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return projectsMode == m || projectsMode == ProjectsModeAll | ||||||
|  | } | ||||||
|  |  | ||||||
| // BeforeSet is invoked from XORM before setting the value of a field of this object. | // BeforeSet is invoked from XORM before setting the value of a field of this object. | ||||||
| func (r *RepoUnit) BeforeSet(colName string, val xorm.Cell) { | func (r *RepoUnit) BeforeSet(colName string, val xorm.Cell) { | ||||||
| 	switch colName { | 	switch colName { | ||||||
| @@ -217,7 +264,9 @@ func (r *RepoUnit) BeforeSet(colName string, val xorm.Cell) { | |||||||
| 			r.Config = new(IssuesConfig) | 			r.Config = new(IssuesConfig) | ||||||
| 		case unit.TypeActions: | 		case unit.TypeActions: | ||||||
| 			r.Config = new(ActionsConfig) | 			r.Config = new(ActionsConfig) | ||||||
| 		case unit.TypeCode, unit.TypeReleases, unit.TypeWiki, unit.TypeProjects, unit.TypePackages: | 		case unit.TypeProjects: | ||||||
|  | 			r.Config = new(ProjectsConfig) | ||||||
|  | 		case unit.TypeCode, unit.TypeReleases, unit.TypeWiki, unit.TypePackages: | ||||||
| 			fallthrough | 			fallthrough | ||||||
| 		default: | 		default: | ||||||
| 			r.Config = new(UnitConfig) | 			r.Config = new(UnitConfig) | ||||||
| @@ -265,6 +314,11 @@ func (r *RepoUnit) ActionsConfig() *ActionsConfig { | |||||||
| 	return r.Config.(*ActionsConfig) | 	return r.Config.(*ActionsConfig) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // ProjectsConfig returns config for unit.ProjectsConfig | ||||||
|  | func (r *RepoUnit) ProjectsConfig() *ProjectsConfig { | ||||||
|  | 	return r.Config.(*ProjectsConfig) | ||||||
|  | } | ||||||
|  |  | ||||||
| func getUnitsByRepoID(ctx context.Context, repoID int64) (units []*RepoUnit, err error) { | func getUnitsByRepoID(ctx context.Context, repoID int64) (units []*RepoUnit, err error) { | ||||||
| 	var tmpUnits []*RepoUnit | 	var tmpUnits []*RepoUnit | ||||||
| 	if err := db.GetEngine(ctx).Where("repo_id = ?", repoID).Find(&tmpUnits); err != nil { | 	if err := db.GetEngine(ctx).Where("repo_id = ?", repoID).Find(&tmpUnits); err != nil { | ||||||
|   | |||||||
| @@ -93,6 +93,12 @@ func CreateRepositoryByExample(ctx context.Context, doer, u *user_model.User, re | |||||||
| 					AllowRebaseUpdate: true, | 					AllowRebaseUpdate: true, | ||||||
| 				}, | 				}, | ||||||
| 			}) | 			}) | ||||||
|  | 		} else if tp == unit.TypeProjects { | ||||||
|  | 			units = append(units, repo_model.RepoUnit{ | ||||||
|  | 				RepoID: repo.ID, | ||||||
|  | 				Type:   tp, | ||||||
|  | 				Config: &repo_model.ProjectsConfig{ProjectsMode: repo_model.ProjectsModeAll}, | ||||||
|  | 			}) | ||||||
| 		} else { | 		} else { | ||||||
| 			units = append(units, repo_model.RepoUnit{ | 			units = append(units, repo_model.RepoUnit{ | ||||||
| 				RepoID: repo.ID, | 				RepoID: repo.ID, | ||||||
|   | |||||||
| @@ -90,6 +90,7 @@ type Repository struct { | |||||||
| 	ExternalWiki                  *ExternalWiki    `json:"external_wiki,omitempty"` | 	ExternalWiki                  *ExternalWiki    `json:"external_wiki,omitempty"` | ||||||
| 	HasPullRequests               bool             `json:"has_pull_requests"` | 	HasPullRequests               bool             `json:"has_pull_requests"` | ||||||
| 	HasProjects                   bool             `json:"has_projects"` | 	HasProjects                   bool             `json:"has_projects"` | ||||||
|  | 	ProjectsMode                  string           `json:"projects_mode"` | ||||||
| 	HasReleases                   bool             `json:"has_releases"` | 	HasReleases                   bool             `json:"has_releases"` | ||||||
| 	HasPackages                   bool             `json:"has_packages"` | 	HasPackages                   bool             `json:"has_packages"` | ||||||
| 	HasActions                    bool             `json:"has_actions"` | 	HasActions                    bool             `json:"has_actions"` | ||||||
| @@ -180,6 +181,8 @@ type EditRepoOption struct { | |||||||
| 	HasPullRequests *bool `json:"has_pull_requests,omitempty"` | 	HasPullRequests *bool `json:"has_pull_requests,omitempty"` | ||||||
| 	// either `true` to enable project unit, or `false` to disable them. | 	// either `true` to enable project unit, or `false` to disable them. | ||||||
| 	HasProjects *bool `json:"has_projects,omitempty"` | 	HasProjects *bool `json:"has_projects,omitempty"` | ||||||
|  | 	// `repo` to only allow repo-level projects, `owner` to only allow owner projects, `all` to allow both. | ||||||
|  | 	ProjectsMode *string `json:"projects_mode,omitempty" binding:"In(repo,owner,all)"` | ||||||
| 	// either `true` to enable releases unit, or `false` to disable them. | 	// either `true` to enable releases unit, or `false` to disable them. | ||||||
| 	HasReleases *bool `json:"has_releases,omitempty"` | 	HasReleases *bool `json:"has_releases,omitempty"` | ||||||
| 	// either `true` to enable packages unit, or `false` to disable them. | 	// either `true` to enable packages unit, or `false` to disable them. | ||||||
|   | |||||||
| @@ -2090,7 +2090,11 @@ settings.pulls.default_delete_branch_after_merge = Delete pull request branch af | |||||||
| settings.pulls.default_allow_edits_from_maintainers = Allow edits from maintainers by default | settings.pulls.default_allow_edits_from_maintainers = Allow edits from maintainers by default | ||||||
| settings.releases_desc = Enable Repository Releases | settings.releases_desc = Enable Repository Releases | ||||||
| settings.packages_desc = Enable Repository Packages Registry | settings.packages_desc = Enable Repository Packages Registry | ||||||
| settings.projects_desc = Enable Repository Projects | settings.projects_desc = Enable Projects | ||||||
|  | settings.projects_mode_desc = Projects Mode (which kinds of projects to show) | ||||||
|  | settings.projects_mode_repo = Repo projects only | ||||||
|  | settings.projects_mode_owner = Only user or org projects | ||||||
|  | settings.projects_mode_all = All projects | ||||||
| settings.actions_desc = Enable Repository Actions | settings.actions_desc = Enable Repository Actions | ||||||
| settings.admin_settings = Administrator Settings | settings.admin_settings = Administrator Settings | ||||||
| settings.admin_enable_health_check = Enable Repository Health Checks (git fsck) | settings.admin_enable_health_check = Enable Repository Health Checks (git fsck) | ||||||
|   | |||||||
| @@ -944,13 +944,33 @@ func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if opts.HasProjects != nil && !unit_model.TypeProjects.UnitGlobalDisabled() { | 	currHasProjects := repo.UnitEnabled(ctx, unit_model.TypeProjects) | ||||||
| 		if *opts.HasProjects { | 	newHasProjects := currHasProjects | ||||||
|  | 	if opts.HasProjects != nil { | ||||||
|  | 		newHasProjects = *opts.HasProjects | ||||||
|  | 	} | ||||||
|  | 	if currHasProjects || newHasProjects { | ||||||
|  | 		if newHasProjects && !unit_model.TypeProjects.UnitGlobalDisabled() { | ||||||
|  | 			unit, err := repo.GetUnit(ctx, unit_model.TypeProjects) | ||||||
|  | 			var config *repo_model.ProjectsConfig | ||||||
|  | 			if err != nil { | ||||||
|  | 				config = &repo_model.ProjectsConfig{ | ||||||
|  | 					ProjectsMode: repo_model.ProjectsModeAll, | ||||||
|  | 				} | ||||||
|  | 			} else { | ||||||
|  | 				config = unit.ProjectsConfig() | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if opts.ProjectsMode != nil { | ||||||
|  | 				config.ProjectsMode = repo_model.ProjectsMode(*opts.ProjectsMode) | ||||||
|  | 			} | ||||||
|  |  | ||||||
| 			units = append(units, repo_model.RepoUnit{ | 			units = append(units, repo_model.RepoUnit{ | ||||||
| 				RepoID: repo.ID, | 				RepoID: repo.ID, | ||||||
| 				Type:   unit_model.TypeProjects, | 				Type:   unit_model.TypeProjects, | ||||||
|  | 				Config: config, | ||||||
| 			}) | 			}) | ||||||
| 		} else { | 		} else if !newHasProjects && !unit_model.TypeProjects.UnitGlobalDisabled() { | ||||||
| 			deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeProjects) | 			deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeProjects) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -587,52 +587,63 @@ func retrieveProjects(ctx *context.Context, repo *repo_model.Repository) { | |||||||
| 	if repo.Owner.IsOrganization() { | 	if repo.Owner.IsOrganization() { | ||||||
| 		repoOwnerType = project_model.TypeOrganization | 		repoOwnerType = project_model.TypeOrganization | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	projectsUnit := repo.MustGetUnit(ctx, unit.TypeProjects) | ||||||
|  |  | ||||||
|  | 	var openProjects []*project_model.Project | ||||||
|  | 	var closedProjects []*project_model.Project | ||||||
| 	var err error | 	var err error | ||||||
| 	projects, err := db.Find[project_model.Project](ctx, project_model.SearchOptions{ |  | ||||||
| 		ListOptions: db.ListOptionsAll, | 	if projectsUnit.ProjectsConfig().IsProjectsAllowed(repo_model.ProjectsModeRepo) { | ||||||
| 		RepoID:      repo.ID, | 		openProjects, err = db.Find[project_model.Project](ctx, project_model.SearchOptions{ | ||||||
| 		IsClosed:    optional.Some(false), | 			ListOptions: db.ListOptionsAll, | ||||||
| 		Type:        project_model.TypeRepository, | 			RepoID:      repo.ID, | ||||||
| 	}) | 			IsClosed:    optional.Some(false), | ||||||
| 	if err != nil { | 			Type:        project_model.TypeRepository, | ||||||
| 		ctx.ServerError("GetProjects", err) | 		}) | ||||||
| 		return | 		if err != nil { | ||||||
| 	} | 			ctx.ServerError("GetProjects", err) | ||||||
| 	projects2, err := db.Find[project_model.Project](ctx, project_model.SearchOptions{ | 			return | ||||||
| 		ListOptions: db.ListOptionsAll, | 		} | ||||||
| 		OwnerID:     repo.OwnerID, | 		closedProjects, err = db.Find[project_model.Project](ctx, project_model.SearchOptions{ | ||||||
| 		IsClosed:    optional.Some(false), | 			ListOptions: db.ListOptionsAll, | ||||||
| 		Type:        repoOwnerType, | 			RepoID:      repo.ID, | ||||||
| 	}) | 			IsClosed:    optional.Some(true), | ||||||
| 	if err != nil { | 			Type:        project_model.TypeRepository, | ||||||
| 		ctx.ServerError("GetProjects", err) | 		}) | ||||||
| 		return | 		if err != nil { | ||||||
|  | 			ctx.ServerError("GetProjects", err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	ctx.Data["OpenProjects"] = append(projects, projects2...) | 	if projectsUnit.ProjectsConfig().IsProjectsAllowed(repo_model.ProjectsModeOwner) { | ||||||
|  | 		openProjects2, err := db.Find[project_model.Project](ctx, project_model.SearchOptions{ | ||||||
| 	projects, err = db.Find[project_model.Project](ctx, project_model.SearchOptions{ | 			ListOptions: db.ListOptionsAll, | ||||||
| 		ListOptions: db.ListOptionsAll, | 			OwnerID:     repo.OwnerID, | ||||||
| 		RepoID:      repo.ID, | 			IsClosed:    optional.Some(false), | ||||||
| 		IsClosed:    optional.Some(true), | 			Type:        repoOwnerType, | ||||||
| 		Type:        project_model.TypeRepository, | 		}) | ||||||
| 	}) | 		if err != nil { | ||||||
| 	if err != nil { | 			ctx.ServerError("GetProjects", err) | ||||||
| 		ctx.ServerError("GetProjects", err) | 			return | ||||||
| 		return | 		} | ||||||
| 	} | 		openProjects = append(openProjects, openProjects2...) | ||||||
| 	projects2, err = db.Find[project_model.Project](ctx, project_model.SearchOptions{ | 		closedProjects2, err := db.Find[project_model.Project](ctx, project_model.SearchOptions{ | ||||||
| 		ListOptions: db.ListOptionsAll, | 			ListOptions: db.ListOptionsAll, | ||||||
| 		OwnerID:     repo.OwnerID, | 			OwnerID:     repo.OwnerID, | ||||||
| 		IsClosed:    optional.Some(true), | 			IsClosed:    optional.Some(true), | ||||||
| 		Type:        repoOwnerType, | 			Type:        repoOwnerType, | ||||||
| 	}) | 		}) | ||||||
| 	if err != nil { | 		if err != nil { | ||||||
| 		ctx.ServerError("GetProjects", err) | 			ctx.ServerError("GetProjects", err) | ||||||
| 		return | 			return | ||||||
|  | 		} | ||||||
|  | 		closedProjects = append(closedProjects, closedProjects2...) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	ctx.Data["ClosedProjects"] = append(projects, projects2...) | 	ctx.Data["OpenProjects"] = openProjects | ||||||
|  | 	ctx.Data["ClosedProjects"] = closedProjects | ||||||
| } | } | ||||||
|  |  | ||||||
| // repoReviewerSelection items to bee shown | // repoReviewerSelection items to bee shown | ||||||
|   | |||||||
| @@ -14,7 +14,7 @@ import ( | |||||||
| 	issues_model "code.gitea.io/gitea/models/issues" | 	issues_model "code.gitea.io/gitea/models/issues" | ||||||
| 	"code.gitea.io/gitea/models/perm" | 	"code.gitea.io/gitea/models/perm" | ||||||
| 	project_model "code.gitea.io/gitea/models/project" | 	project_model "code.gitea.io/gitea/models/project" | ||||||
| 	attachment_model "code.gitea.io/gitea/models/repo" | 	repo_model "code.gitea.io/gitea/models/repo" | ||||||
| 	"code.gitea.io/gitea/models/unit" | 	"code.gitea.io/gitea/models/unit" | ||||||
| 	"code.gitea.io/gitea/modules/base" | 	"code.gitea.io/gitea/modules/base" | ||||||
| 	"code.gitea.io/gitea/modules/json" | 	"code.gitea.io/gitea/modules/json" | ||||||
| @@ -33,16 +33,17 @@ const ( | |||||||
| 	tplProjectsView base.TplName = "repo/projects/view" | 	tplProjectsView base.TplName = "repo/projects/view" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // MustEnableProjects check if projects are enabled in settings | // MustEnableRepoProjects check if repo projects are enabled in settings | ||||||
| func MustEnableProjects(ctx *context.Context) { | func MustEnableRepoProjects(ctx *context.Context) { | ||||||
| 	if unit.TypeProjects.UnitGlobalDisabled() { | 	if unit.TypeProjects.UnitGlobalDisabled() { | ||||||
| 		ctx.NotFound("EnableKanbanBoard", nil) | 		ctx.NotFound("EnableKanbanBoard", nil) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if ctx.Repo.Repository != nil { | 	if ctx.Repo.Repository != nil { | ||||||
| 		if !ctx.Repo.CanRead(unit.TypeProjects) { | 		projectsUnit := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypeProjects) | ||||||
| 			ctx.NotFound("MustEnableProjects", nil) | 		if !ctx.Repo.CanRead(unit.TypeProjects) || !projectsUnit.ProjectsConfig().IsProjectsAllowed(repo_model.ProjectsModeRepo) { | ||||||
|  | 			ctx.NotFound("MustEnableRepoProjects", nil) | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| @@ -325,10 +326,10 @@ func ViewProject(ctx *context.Context) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if project.CardType != project_model.CardTypeTextOnly { | 	if project.CardType != project_model.CardTypeTextOnly { | ||||||
| 		issuesAttachmentMap := make(map[int64][]*attachment_model.Attachment) | 		issuesAttachmentMap := make(map[int64][]*repo_model.Attachment) | ||||||
| 		for _, issuesList := range issuesMap { | 		for _, issuesList := range issuesMap { | ||||||
| 			for _, issue := range issuesList { | 			for _, issue := range issuesList { | ||||||
| 				if issueAttachment, err := attachment_model.GetAttachmentsByIssueIDImagesLatest(ctx, issue.ID); err == nil { | 				if issueAttachment, err := repo_model.GetAttachmentsByIssueIDImagesLatest(ctx, issue.ID); err == nil { | ||||||
| 					issuesAttachmentMap[issue.ID] = issueAttachment | 					issuesAttachmentMap[issue.ID] = issueAttachment | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|   | |||||||
| @@ -533,6 +533,9 @@ func SettingsPost(ctx *context.Context) { | |||||||
| 			units = append(units, repo_model.RepoUnit{ | 			units = append(units, repo_model.RepoUnit{ | ||||||
| 				RepoID: repo.ID, | 				RepoID: repo.ID, | ||||||
| 				Type:   unit_model.TypeProjects, | 				Type:   unit_model.TypeProjects, | ||||||
|  | 				Config: &repo_model.ProjectsConfig{ | ||||||
|  | 					ProjectsMode: repo_model.ProjectsMode(form.ProjectsMode), | ||||||
|  | 				}, | ||||||
| 			}) | 			}) | ||||||
| 		} else if !unit_model.TypeProjects.UnitGlobalDisabled() { | 		} else if !unit_model.TypeProjects.UnitGlobalDisabled() { | ||||||
| 			deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeProjects) | 			deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeProjects) | ||||||
|   | |||||||
| @@ -1344,7 +1344,7 @@ func registerRoutes(m *web.Route) { | |||||||
| 					}) | 					}) | ||||||
| 				}) | 				}) | ||||||
| 			}, reqRepoProjectsWriter, context.RepoMustNotBeArchived()) | 			}, reqRepoProjectsWriter, context.RepoMustNotBeArchived()) | ||||||
| 		}, reqRepoProjectsReader, repo.MustEnableProjects) | 		}, reqRepoProjectsReader, repo.MustEnableRepoProjects) | ||||||
|  |  | ||||||
| 		m.Group("/actions", func() { | 		m.Group("/actions", func() { | ||||||
| 			m.Get("", actions.List) | 			m.Get("", actions.List) | ||||||
|   | |||||||
| @@ -113,8 +113,11 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR | |||||||
| 		defaultAllowMaintainerEdit = config.DefaultAllowMaintainerEdit | 		defaultAllowMaintainerEdit = config.DefaultAllowMaintainerEdit | ||||||
| 	} | 	} | ||||||
| 	hasProjects := false | 	hasProjects := false | ||||||
| 	if _, err := repo.GetUnit(ctx, unit_model.TypeProjects); err == nil { | 	projectsMode := repo_model.ProjectsModeAll | ||||||
|  | 	if unit, err := repo.GetUnit(ctx, unit_model.TypeProjects); err == nil { | ||||||
| 		hasProjects = true | 		hasProjects = true | ||||||
|  | 		config := unit.ProjectsConfig() | ||||||
|  | 		projectsMode = config.ProjectsMode | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	hasReleases := false | 	hasReleases := false | ||||||
| @@ -211,6 +214,7 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR | |||||||
| 		InternalTracker:               internalTracker, | 		InternalTracker:               internalTracker, | ||||||
| 		HasWiki:                       hasWiki, | 		HasWiki:                       hasWiki, | ||||||
| 		HasProjects:                   hasProjects, | 		HasProjects:                   hasProjects, | ||||||
|  | 		ProjectsMode:                  string(projectsMode), | ||||||
| 		HasReleases:                   hasReleases, | 		HasReleases:                   hasReleases, | ||||||
| 		HasPackages:                   hasPackages, | 		HasPackages:                   hasPackages, | ||||||
| 		HasActions:                    hasActions, | 		HasActions:                    hasActions, | ||||||
|   | |||||||
| @@ -142,6 +142,7 @@ type RepoSettingForm struct { | |||||||
| 	ExternalTrackerRegexpPattern          string | 	ExternalTrackerRegexpPattern          string | ||||||
| 	EnableCloseIssuesViaCommitInAnyBranch bool | 	EnableCloseIssuesViaCommitInAnyBranch bool | ||||||
| 	EnableProjects                        bool | 	EnableProjects                        bool | ||||||
|  | 	ProjectsMode                          string | ||||||
| 	EnableReleases                        bool | 	EnableReleases                        bool | ||||||
| 	EnablePackages                        bool | 	EnablePackages                        bool | ||||||
| 	EnablePulls                           bool | 	EnablePulls                           bool | ||||||
|   | |||||||
| @@ -174,7 +174,8 @@ | |||||||
| 					</a> | 					</a> | ||||||
| 				{{end}} | 				{{end}} | ||||||
|  |  | ||||||
| 				{{if and (not .UnitProjectsGlobalDisabled) (.Permission.CanRead $.UnitTypeProjects)}} | 				{{$projectsUnit := .Repository.MustGetUnit $.Context $.UnitTypeProjects}} | ||||||
|  | 				{{if and (not .UnitProjectsGlobalDisabled) (.Permission.CanRead $.UnitTypeProjects) ($projectsUnit.ProjectsConfig.IsProjectsAllowed "repo")}} | ||||||
| 					<a href="{{.RepoLink}}/projects" class="{{if .IsProjectsPage}}active {{end}}item"> | 					<a href="{{.RepoLink}}/projects" class="{{if .IsProjectsPage}}active {{end}}item"> | ||||||
| 						{{svg "octicon-project"}} {{ctx.Locale.Tr "repo.project_board"}} | 						{{svg "octicon-project"}} {{ctx.Locale.Tr "repo.project_board"}} | ||||||
| 						{{if .Repository.NumOpenProjects}} | 						{{if .Repository.NumOpenProjects}} | ||||||
|   | |||||||
| @@ -446,13 +446,45 @@ | |||||||
|  |  | ||||||
| 				{{$isProjectsEnabled := .Repository.UnitEnabled $.Context $.UnitTypeProjects}} | 				{{$isProjectsEnabled := .Repository.UnitEnabled $.Context $.UnitTypeProjects}} | ||||||
| 				{{$isProjectsGlobalDisabled := .UnitTypeProjects.UnitGlobalDisabled}} | 				{{$isProjectsGlobalDisabled := .UnitTypeProjects.UnitGlobalDisabled}} | ||||||
|  | 				{{$projectsUnit := .Repository.MustGetUnit $.Context $.UnitTypeProjects}} | ||||||
| 				<div class="inline field"> | 				<div class="inline field"> | ||||||
| 					<label>{{ctx.Locale.Tr "repo.project_board"}}</label> | 					<label>{{ctx.Locale.Tr "repo.project_board"}}</label> | ||||||
| 					<div class="ui checkbox{{if $isProjectsGlobalDisabled}} disabled{{end}}"{{if $isProjectsGlobalDisabled}} data-tooltip-content="{{ctx.Locale.Tr "repo.unit_disabled"}}"{{end}}> | 					<div class="ui checkbox{{if $isProjectsGlobalDisabled}} disabled{{end}}"{{if $isProjectsGlobalDisabled}} data-tooltip-content="{{ctx.Locale.Tr "repo.unit_disabled"}}"{{end}}> | ||||||
| 						<input class="enable-system" name="enable_projects" type="checkbox" {{if $isProjectsEnabled}}checked{{end}}> | 						<input class="enable-system" name="enable_projects" type="checkbox" data-target="#projects_box" {{if $isProjectsEnabled}}checked{{end}}> | ||||||
| 						<label>{{ctx.Locale.Tr "repo.settings.projects_desc"}}</label> | 						<label>{{ctx.Locale.Tr "repo.settings.projects_desc"}}</label> | ||||||
| 					</div> | 					</div> | ||||||
| 				</div> | 				</div> | ||||||
|  | 				<div class="field {{if not $isProjectsEnabled}} disabled{{end}} gt-pl-4" id="projects_box"> | ||||||
|  | 					<p> | ||||||
|  | 						{{ctx.Locale.Tr "repo.settings.projects_mode_desc"}} | ||||||
|  | 					</p> | ||||||
|  | 					<div class="ui dropdown selection"> | ||||||
|  | 						<select name="projects_mode"> | ||||||
|  | 							<option value="repo" {{if or (not $isProjectsEnabled) (eq $projectsUnit.ProjectsConfig.ProjectsMode "repo")}}selected{{end}}>{{ctx.Locale.Tr "repo.settings.projects_mode_repo"}}</option> | ||||||
|  | 							<option value="owner" {{if or (not $isProjectsEnabled) (eq $projectsUnit.ProjectsConfig.ProjectsMode "owner")}}selected{{end}}>{{ctx.Locale.Tr "repo.settings.projects_mode_owner"}}</option> | ||||||
|  | 							<option value="all" {{if or (not $isProjectsEnabled) (eq $projectsUnit.ProjectsConfig.ProjectsMode "all")}}selected{{end}}>{{ctx.Locale.Tr "repo.settings.projects_mode_all"}}</option> | ||||||
|  | 						</select> | ||||||
|  | 						{{svg "octicon-triangle-down" 14 "dropdown icon"}} | ||||||
|  | 						<div class="default text"> | ||||||
|  | 							{{if (eq $projectsUnit.ProjectsConfig.ProjectsMode "repo")}} | ||||||
|  | 								{{ctx.Locale.Tr "repo.settings.projects_mode_repo"}} | ||||||
|  | 							{{end}} | ||||||
|  | 							{{if (eq $projectsUnit.ProjectsConfig.ProjectsMode "owner")}} | ||||||
|  | 								{{ctx.Locale.Tr "repo.settings.projects_mode_owner"}} | ||||||
|  | 							{{end}} | ||||||
|  | 							{{if (eq $projectsUnit.ProjectsConfig.ProjectsMode "all")}} | ||||||
|  | 								{{ctx.Locale.Tr "repo.settings.projects_mode_all"}} | ||||||
|  | 							{{end}} | ||||||
|  | 						</div> | ||||||
|  | 						<div class="menu"> | ||||||
|  | 							<div class="item" data-value="repo">{{ctx.Locale.Tr "repo.settings.projects_mode_repo"}}</div> | ||||||
|  | 							<div class="item" data-value="owner">{{ctx.Locale.Tr "repo.settings.projects_mode_owner"}}</div> | ||||||
|  | 							<div class="item" data-value="all">{{ctx.Locale.Tr "repo.settings.projects_mode_all"}}</div> | ||||||
|  | 						</div> | ||||||
|  | 					</div> | ||||||
|  | 				</div> | ||||||
|  |  | ||||||
|  | 				<div class="divider"></div> | ||||||
|  |  | ||||||
| 				{{$isReleasesEnabled := .Repository.UnitEnabled $.Context $.UnitTypeReleases}} | 				{{$isReleasesEnabled := .Repository.UnitEnabled $.Context $.UnitTypeReleases}} | ||||||
| 				{{$isReleasesGlobalDisabled := .UnitTypeReleases.UnitGlobalDisabled}} | 				{{$isReleasesGlobalDisabled := .UnitTypeReleases.UnitGlobalDisabled}} | ||||||
|   | |||||||
							
								
								
									
										9
									
								
								templates/swagger/v1_json.tmpl
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										9
									
								
								templates/swagger/v1_json.tmpl
									
									
									
										generated
									
									
									
								
							| @@ -19570,6 +19570,11 @@ | |||||||
|           "type": "boolean", |           "type": "boolean", | ||||||
|           "x-go-name": "Private" |           "x-go-name": "Private" | ||||||
|         }, |         }, | ||||||
|  |         "projects_mode": { | ||||||
|  |           "description": "`repo` to only allow repo-level projects, `owner` to only allow owner projects, `all` to allow both.", | ||||||
|  |           "type": "string", | ||||||
|  |           "x-go-name": "ProjectsMode" | ||||||
|  |         }, | ||||||
|         "template": { |         "template": { | ||||||
|           "description": "either `true` to make this repository a template or `false` to make it a normal repository", |           "description": "either `true` to make this repository a template or `false` to make it a normal repository", | ||||||
|           "type": "boolean", |           "type": "boolean", | ||||||
| @@ -22491,6 +22496,10 @@ | |||||||
|           "type": "boolean", |           "type": "boolean", | ||||||
|           "x-go-name": "Private" |           "x-go-name": "Private" | ||||||
|         }, |         }, | ||||||
|  |         "projects_mode": { | ||||||
|  |           "type": "string", | ||||||
|  |           "x-go-name": "ProjectsMode" | ||||||
|  |         }, | ||||||
|         "release_counter": { |         "release_counter": { | ||||||
|           "type": "integer", |           "type": "integer", | ||||||
|           "format": "int64", |           "format": "int64", | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Denys Konovalov
					Denys Konovalov