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 @@
{{svg "octicon-globe" 14}} {{ctx.Locale.Tr "admin.packages.bulk.enable_global"}}
{{svg "octicon-lock" 14}} {{ctx.Locale.Tr "admin.packages.bulk.disable_global"}}
+
{{svg "octicon-lock" 14}} {{ctx.Locale.Tr "admin.packages.bulk.make_private"}}
+
{{svg "octicon-eye" 14}} {{ctx.Locale.Tr "admin.packages.bulk.make_public"}}
+
{{svg "octicon-link" 14}} {{ctx.Locale.Tr "admin.packages.bulk.automatch"}}
@@ -58,6 +61,7 @@ {{ctx.Locale.Tr "admin.packages.creator"}} {{ctx.Locale.Tr "admin.packages.repository"}} + {{ctx.Locale.Tr "admin.packages.visibility"}} {{ctx.Locale.Tr "admin.packages.global"}} {{ctx.Locale.Tr "admin.packages.size"}} @@ -91,6 +95,13 @@ {{end}} + + {{if .Package.IsPrivate}} + {{svg "octicon-lock" 12}} {{ctx.Locale.Tr "admin.packages.visibility.private"}} + {{else}} + {{svg "octicon-eye" 12}} {{ctx.Locale.Tr "admin.packages.visibility.public"}} + {{end}} + {{if .Package.IsGlobal}} {{svg "octicon-globe" 12}} {{ctx.Locale.Tr "admin.packages.global.yes"}} @@ -108,7 +119,7 @@ {{else}} - {{ctx.Locale.Tr "no_results_found"}} + {{ctx.Locale.Tr "no_results_found"}} {{end}} @@ -177,6 +188,14 @@ document.addEventListener('DOMContentLoaded', function() { url = '{{AppSubUrl}}/-/admin/packages/bulk-global'; formData.append('is_global', 'false'); break; + case 'make-private': + url = '{{AppSubUrl}}/-/admin/packages/bulk-private'; + formData.append('is_private', 'true'); + break; + case 'make-public': + url = '{{AppSubUrl}}/-/admin/packages/bulk-private'; + formData.append('is_private', 'false'); + break; case 'automatch': url = '{{AppSubUrl}}/-/admin/packages/bulk-automatch'; break; diff --git a/templates/package/shared/list.tmpl b/templates/package/shared/list.tmpl index 910fa79418..39ef828aa0 100644 --- a/templates/package/shared/list.tmpl +++ b/templates/package/shared/list.tmpl @@ -14,37 +14,125 @@ {{end}} -
- {{range .PackageDescriptors}} -
-
-
-
- {{.Package.Name}} - {{svg .Package.Type.SVGName 16}} {{.Package.Type.Name}} - {{if .Package.IsPrivate}} - {{ctx.Locale.Tr "repo.visibility.private"}} - {{end}} -
-
- {{$timeStr := DateUtils.TimeSince .Version.CreatedUnix}} - {{$hasRepositoryAccess := false}} - {{if .Repository}} - {{$hasRepositoryAccess = index $.RepositoryAccessMap .Repository.ID}} - {{end}} - {{if $hasRepositoryAccess}} - {{ctx.Locale.Tr "packages.published_by_in" $timeStr .Creator.HomeLink .Creator.GetDisplayName .Repository.Link .Repository.FullName}} - {{else}} - {{ctx.Locale.Tr "packages.published_by" $timeStr .Creator.HomeLink .Creator.GetDisplayName}} - {{end}} -
-
-
- {{DateUtils.TimeSince .Version.CreatedUnix}} -
+ +{{if and .CanWritePackages (or .PublicPackages .PrivatePackages)}} +
+ - {{else}} + +
+{{end}} + +
+ {{/* Public Packages Section */}} + {{if .PublicPackages}} +
+

+ {{svg "octicon-globe" 16}} + {{ctx.Locale.Tr "packages.visibility.public"}} + {{len .PublicPackages}} + {{if .CanWritePackages}} +
+ +
+ {{end}} +

+
+ {{range .PublicPackages}} +
+
+ {{if $.CanWritePackages}} +
+ +
+ {{end}} +
+
+ {{.Package.Name}} + {{svg .Package.Type.SVGName 16}} {{.Package.Type.Name}} +
+
+ {{$timeStr := DateUtils.TimeSince .Version.CreatedUnix}} + {{$hasRepositoryAccess := false}} + {{if .Repository}} + {{$hasRepositoryAccess = index $.RepositoryAccessMap .Repository.ID}} + {{end}} + {{if $hasRepositoryAccess}} + {{ctx.Locale.Tr "packages.published_by_in" $timeStr .Creator.HomeLink .Creator.GetDisplayName .Repository.Link .Repository.FullName}} + {{else}} + {{ctx.Locale.Tr "packages.published_by" $timeStr .Creator.HomeLink .Creator.GetDisplayName}} + {{end}} +
+
+
+ {{DateUtils.TimeSince .Version.CreatedUnix}} +
+
+
+ {{end}} +
+
+ {{end}} + + {{/* Private Packages Section */}} + {{if .PrivatePackages}} +
+

+ {{svg "octicon-lock" 16}} + {{ctx.Locale.Tr "packages.visibility.private"}} + {{len .PrivatePackages}} + {{if .CanWritePackages}} +
+ +
+ {{end}} +

+
+ {{range .PrivatePackages}} +
+
+ {{if $.CanWritePackages}} +
+ +
+ {{end}} +
+
+ {{.Package.Name}} + {{svg .Package.Type.SVGName 16}} {{.Package.Type.Name}} + {{ctx.Locale.Tr "repo.visibility.private"}} +
+
+ {{$timeStr := DateUtils.TimeSince .Version.CreatedUnix}} + {{$hasRepositoryAccess := false}} + {{if .Repository}} + {{$hasRepositoryAccess = index $.RepositoryAccessMap .Repository.ID}} + {{end}} + {{if $hasRepositoryAccess}} + {{ctx.Locale.Tr "packages.published_by_in" $timeStr .Creator.HomeLink .Creator.GetDisplayName .Repository.Link .Repository.FullName}} + {{else}} + {{ctx.Locale.Tr "packages.published_by" $timeStr .Creator.HomeLink .Creator.GetDisplayName}} + {{end}} +
+
+
+ {{DateUtils.TimeSince .Version.CreatedUnix}} +
+
+
+ {{end}} +
+
+ {{end}} + + {{/* Empty state - no packages at all */}} + {{if and (not .PublicPackages) (not .PrivatePackages)}} {{if not .HasPackages}}
{{svg "octicon-package" 48}} @@ -61,3 +149,76 @@ {{end}} {{template "base/paginate" .}}
+ +{{if .CanWritePackages}} + +{{end}}