diff --git a/options/locale/locale_en-US.json b/options/locale/locale_en-US.json index 328f56407b..968444cd0e 100644 --- a/options/locale/locale_en-US.json +++ b/options/locale/locale_en-US.json @@ -3322,6 +3322,13 @@ "admin.packages.bulk.global.partial": "Enabled global access for %d package(s), %d failed (may already exist as global)", "admin.packages.bulk.automatch.success": "Auto-matched %d package(s) to repositories", "admin.packages.bulk.automatch.none": "No matching repositories found for selected packages", + "admin.packages.bulk.make_private": "Make Private", + "admin.packages.bulk.make_public": "Make Public", + "admin.packages.bulk.private.enabled": "Made %d package(s) private", + "admin.packages.bulk.private.disabled": "Made %d package(s) public", + "admin.packages.visibility": "Visibility", + "admin.packages.visibility.private": "Private", + "admin.packages.visibility.public": "Public", "admin.packages.automatch.button": "Find matching repository", "admin.packages.automatch.match": "Match", "admin.packages.automatch.success": "Package linked to matching repository", @@ -3726,6 +3733,14 @@ "packages.no_metadata": "No metadata.", "packages.empty.documentation": "For more information on the package registry, see the documentation.", "packages.empty.repo": "Did you upload a package, but it's not shown here? Go to package settings and link it to this repo.", + "packages.visibility.public": "Public Packages", + "packages.visibility.private": "Private Packages", + "packages.bulk.actions": "Bulk Actions", + "packages.bulk.make_private": "Make Private", + "packages.bulk.make_public": "Make Public", + "packages.bulk.selected": "Selected:", + "packages.bulk.select_all": "Select all", + "packages.bulk.no_selection": "Please select at least one package.", "packages.registry.documentation": "For more information on the %s registry, see the documentation.", "packages.filter.type": "Type", "packages.filter.type.all": "All", diff --git a/routers/web/admin/packages.go b/routers/web/admin/packages.go index 240119f58d..0d1011d16a 100644 --- a/routers/web/admin/packages.go +++ b/routers/web/admin/packages.go @@ -177,6 +177,41 @@ func BulkAutoMatch(ctx *context.Context) { ctx.JSONRedirect(setting.AppSubURL + "/-/admin/packages") } +// BulkSetPrivate sets/unsets private flag on multiple packages +func BulkSetPrivate(ctx *context.Context) { + packageIDs := ctx.FormStrings("ids[]") + isPrivate := ctx.FormBool("is_private") + + ids := make([]int64, 0, len(packageIDs)) + for _, idStr := range packageIDs { + var id int64 + if _, err := fmt.Sscanf(idStr, "%d", &id); err == nil && id > 0 { + ids = append(ids, id) + } + } + + if len(ids) == 0 { + ctx.Flash.Error(ctx.Tr("admin.packages.bulk.no_selection")) + ctx.JSONRedirect(setting.AppSubURL + "/-/admin/packages") + return + } + + succeeded := 0 + for _, id := range ids { + if err := packages_model.SetPackageIsPrivate(ctx, id, isPrivate); err == nil { + succeeded++ + } + } + + if isPrivate { + ctx.Flash.Success(ctx.Tr("admin.packages.bulk.private.enabled", succeeded)) + } else { + ctx.Flash.Success(ctx.Tr("admin.packages.bulk.private.disabled", succeeded)) + } + + ctx.JSONRedirect(setting.AppSubURL + "/-/admin/packages") +} + // SingleAutoMatch automatically matches a single package to a repository func SingleAutoMatch(ctx *context.Context) { packageID := ctx.FormInt64("id") diff --git a/routers/web/repo/packages.go b/routers/web/repo/packages.go index 6d44b9fde1..29e05aadcc 100644 --- a/routers/web/repo/packages.go +++ b/routers/web/repo/packages.go @@ -5,6 +5,7 @@ package repo import ( "net/http" + "strconv" "code.gitcaddy.com/server/v3/models/db" "code.gitcaddy.com/server/v3/models/packages" @@ -19,7 +20,7 @@ const ( tplPackagesList templates.TplName = "repo/packages" ) -// Packages displays a list of all packages in the repository +// Packages displays a list of all packages in the repository, grouped by visibility func Packages(ctx *context.Context) { page := max(ctx.FormInt("page"), 1) query := ctx.FormTrim("q") @@ -47,6 +48,16 @@ func Packages(ctx *context.Context) { return } + // Group packages by visibility + var publicPackages, privatePackages []*packages.PackageDescriptor + for _, pd := range pds { + if pd.Package.IsPrivate { + privatePackages = append(privatePackages, pd) + } else { + publicPackages = append(publicPackages, pd) + } + } + hasPackages, err := packages.HasRepositoryPackages(ctx, ctx.Repo.Repository.ID) if err != nil { ctx.ServerError("HasRepositoryPackages", err) @@ -61,6 +72,8 @@ func Packages(ctx *context.Context) { ctx.Data["HasPackages"] = hasPackages ctx.Data["CanWritePackages"] = ctx.Repo.CanWrite(unit.TypePackages) || ctx.IsUserSiteAdmin() ctx.Data["PackageDescriptors"] = pds + ctx.Data["PublicPackages"] = publicPackages + ctx.Data["PrivatePackages"] = privatePackages ctx.Data["Total"] = total ctx.Data["RepositoryAccessMap"] = map[int64]bool{ctx.Repo.Repository.ID: true} // There is only the current repository @@ -70,3 +83,34 @@ func Packages(ctx *context.Context) { ctx.HTML(http.StatusOK, tplPackagesList) } + +// BulkSetPackageVisibility changes visibility for multiple packages +func BulkSetPackageVisibility(ctx *context.Context) { + if !ctx.Repo.CanWrite(unit.TypePackages) && !ctx.IsUserSiteAdmin() { + ctx.JSONError(ctx.Tr("packages.settings.visibility.no_permission")) + return + } + + isPrivate := ctx.FormString("is_private") == "true" + ids := ctx.Req.Form["ids[]"] + + for _, idStr := range ids { + id, err := strconv.ParseInt(idStr, 10, 64) + if err != nil { + continue + } + + // Verify the package belongs to this repository + pkg, err := packages.GetPackageByID(ctx, id) + if err != nil || pkg.RepoID != ctx.Repo.Repository.ID { + continue + } + + if err := packages.SetPackageIsPrivate(ctx, id, isPrivate); err != nil { + ctx.ServerError("SetPackageIsPrivate", err) + return + } + } + + ctx.JSONOK() +} diff --git a/routers/web/web.go b/routers/web/web.go index 73d948e104..e86945abe1 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -851,6 +851,7 @@ func registerWebRoutes(m *web.Router) { m.Post("/delete", admin.DeletePackageVersion) m.Post("/cleanup", admin.CleanupExpiredData) m.Post("/bulk-global", admin.BulkSetGlobal) + m.Post("/bulk-private", admin.BulkSetPrivate) m.Post("/bulk-automatch", admin.BulkAutoMatch) m.Post("/automatch", admin.SingleAutoMatch) }, packagesEnabled) @@ -1638,6 +1639,7 @@ func registerWebRoutes(m *web.Router) { m.Group("/{username}/{reponame}", func() { if setting.Packages.Enabled { m.Get("/packages", repo.Packages) + m.Post("/packages/bulk-visibility", reqSignIn, repo.BulkSetPackageVisibility) } }, optSignIn, context.RepoAssignment) diff --git a/templates/admin/packages/list.tmpl b/templates/admin/packages/list.tmpl index e3bbf77836..c5ae5f208a 100644 --- a/templates/admin/packages/list.tmpl +++ b/templates/admin/packages/list.tmpl @@ -34,6 +34,9 @@