diff --git a/options/locale/locale_en-US.json b/options/locale/locale_en-US.json index 968444cd0e..08bc79c2b5 100644 --- a/options/locale/locale_en-US.json +++ b/options/locale/locale_en-US.json @@ -3329,6 +3329,10 @@ "admin.packages.visibility": "Visibility", "admin.packages.visibility.private": "Private", "admin.packages.visibility.public": "Public", + "admin.packages.bulk.delete": "Delete Selected", + "admin.packages.bulk.delete.confirm": "Are you sure you want to delete the selected packages and all their versions? This action cannot be undone.", + "admin.packages.bulk.delete.success": "Deleted %d package(s) with %d version(s)", + "admin.packages.bulk.delete.none": "No packages were deleted", "admin.packages.automatch.button": "Find matching repository", "admin.packages.automatch.match": "Match", "admin.packages.automatch.success": "Package linked to matching repository", diff --git a/routers/web/admin/packages.go b/routers/web/admin/packages.go index 0d1011d16a..00644940f7 100644 --- a/routers/web/admin/packages.go +++ b/routers/web/admin/packages.go @@ -81,6 +81,57 @@ func Packages(ctx *context.Context) { ctx.HTML(http.StatusOK, tplPackagesList) } +// BulkDeletePackages deletes all versions of selected packages +func BulkDeletePackages(ctx *context.Context) { + packageIDs := ctx.FormStrings("ids[]") + + 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 + } + + deletedVersions := 0 + deletedPackages := 0 + + for _, packageID := range ids { + // Get all versions of this package + versions, _, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{ + PackageID: packageID, + }) + if err != nil { + continue + } + + packageDeleted := false + for _, pv := range versions { + if err := packages_service.RemovePackageVersion(ctx, ctx.Doer, pv); err == nil { + deletedVersions++ + packageDeleted = true + } + } + if packageDeleted { + deletedPackages++ + } + } + + if deletedPackages > 0 { + ctx.Flash.Success(ctx.Tr("admin.packages.bulk.delete.success", deletedPackages, deletedVersions)) + } else { + ctx.Flash.Warning(ctx.Tr("admin.packages.bulk.delete.none")) + } + + ctx.JSONRedirect(setting.AppSubURL + "/-/admin/packages") +} + // DeletePackageVersion deletes a package version func DeletePackageVersion(ctx *context.Context) { pv, err := packages_model.GetVersionByID(ctx, ctx.FormInt64("id")) diff --git a/routers/web/web.go b/routers/web/web.go index e86945abe1..abac6d0ef9 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -852,6 +852,7 @@ func registerWebRoutes(m *web.Router) { m.Post("/cleanup", admin.CleanupExpiredData) m.Post("/bulk-global", admin.BulkSetGlobal) m.Post("/bulk-private", admin.BulkSetPrivate) + m.Post("/bulk-delete", admin.BulkDeletePackages) m.Post("/bulk-automatch", admin.BulkAutoMatch) m.Post("/automatch", admin.SingleAutoMatch) }, packagesEnabled) diff --git a/templates/admin/packages/list.tmpl b/templates/admin/packages/list.tmpl index c5ae5f208a..7d8a48043e 100644 --- a/templates/admin/packages/list.tmpl +++ b/templates/admin/packages/list.tmpl @@ -38,6 +38,8 @@
{{svg "octicon-eye" 14}} {{ctx.Locale.Tr "admin.packages.bulk.make_public"}}
{{svg "octicon-link" 14}} {{ctx.Locale.Tr "admin.packages.bulk.automatch"}}
+
+
{{svg "octicon-trash" 14}} {{ctx.Locale.Tr "admin.packages.bulk.delete"}}
@@ -199,6 +201,12 @@ document.addEventListener('DOMContentLoaded', function() { case 'automatch': url = '{{AppSubUrl}}/-/admin/packages/bulk-automatch'; break; + case 'delete': + if (!confirm('{{ctx.Locale.Tr "admin.packages.bulk.delete.confirm"}}')) { + return; + } + url = '{{AppSubUrl}}/-/admin/packages/bulk-delete'; + break; } fetch(url, {