mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-11-04 08:34:30 +01:00 
			
		
		
		
	Merge c0f910e322afa4c60207e44395246584dcb7b6e0 into 98ff7d077376db1225f266095788c6bd9414288a
This commit is contained in:
		
						commit
						9a86efee86
					
				
							
								
								
									
										72
									
								
								PACKAGES_EXPLORE_FEATURE.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								PACKAGES_EXPLORE_FEATURE.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,72 @@
 | 
				
			|||||||
 | 
					# Packages Explore Feature
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Overview
 | 
				
			||||||
 | 
					This feature adds a new "Packages" tab to the Explore page, allowing users to discover and browse packages that they have access to across the Gitea instance.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Features
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### User-Facing Features
 | 
				
			||||||
 | 
					1. **Packages Tab in Explore**: A new tab in the explore navigation that displays all accessible packages
 | 
				
			||||||
 | 
					2. **Search and Filter**: Users can search packages by name and filter by package type (npm, Maven, Docker, etc.)
 | 
				
			||||||
 | 
					3. **Permission-Based Access**: Only shows packages that the user has permission to view based on:
 | 
				
			||||||
 | 
					   - Public user packages (visible to everyone)
 | 
				
			||||||
 | 
					   - Limited visibility user packages (visible to logged-in users)
 | 
				
			||||||
 | 
					   - Organization packages (visible based on org visibility and membership)
 | 
				
			||||||
 | 
					   - Private packages (only visible to the owner)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Admin Features
 | 
				
			||||||
 | 
					1. **Toggle Control**: Admins can enable/disable the packages explore page via `app.ini` configuration
 | 
				
			||||||
 | 
					2. **Configuration Setting**: `[service.explore]` section with `DISABLE_PACKAGES_PAGE` option
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Configuration
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Add the following to your `app.ini` file under the `[service.explore]` section:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```ini
 | 
				
			||||||
 | 
					[service.explore]
 | 
				
			||||||
 | 
					; Disable the packages explore page
 | 
				
			||||||
 | 
					DISABLE_PACKAGES_PAGE = false
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Set to `true` to hide the packages tab from the explore page.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Implementation Details
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Backend Changes
 | 
				
			||||||
 | 
					- **New Handler**: `routers/web/explore/packages.go` - Handles package listing with permission filtering
 | 
				
			||||||
 | 
					- **Configuration**: `modules/setting/service.go` - Added `DisablePackagesPage` setting
 | 
				
			||||||
 | 
					- **Route**: Added `/explore/packages` route in `routers/web/web.go`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Frontend Changes
 | 
				
			||||||
 | 
					- **Template**: `templates/explore/packages.tmpl` - Displays package list with search/filter
 | 
				
			||||||
 | 
					- **Navigation**: Updated `templates/explore/navbar.tmpl` to include packages tab
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Permission Logic
 | 
				
			||||||
 | 
					The feature implements proper access control by:
 | 
				
			||||||
 | 
					1. Fetching packages from the database
 | 
				
			||||||
 | 
					2. Checking each package's owner visibility:
 | 
				
			||||||
 | 
					   - For user-owned packages: Check user visibility (public/limited/private)
 | 
				
			||||||
 | 
					   - For org-owned packages: Check org visibility and user membership
 | 
				
			||||||
 | 
					3. Filtering results to only show accessible packages
 | 
				
			||||||
 | 
					4. Respecting the `DISABLE_PACKAGES_PAGE` configuration setting
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Security Considerations
 | 
				
			||||||
 | 
					- Anonymous users only see packages from public users/organizations
 | 
				
			||||||
 | 
					- Logged-in users see packages from public and limited visibility users, plus organizations they're members of
 | 
				
			||||||
 | 
					- Private user packages are only visible to the owner
 | 
				
			||||||
 | 
					- The feature requires packages to be enabled (`[packages] ENABLED = true`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Testing
 | 
				
			||||||
 | 
					To test the feature:
 | 
				
			||||||
 | 
					1. Enable packages in your Gitea instance
 | 
				
			||||||
 | 
					2. Create packages under different users/organizations with varying visibility settings
 | 
				
			||||||
 | 
					3. Access `/explore/packages` as different user types (anonymous, logged-in, org member)
 | 
				
			||||||
 | 
					4. Verify that only appropriate packages are displayed
 | 
				
			||||||
 | 
					5. Test the admin toggle by setting `DISABLE_PACKAGES_PAGE = true` and verifying the tab disappears
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Future Enhancements
 | 
				
			||||||
 | 
					Potential improvements for future versions:
 | 
				
			||||||
 | 
					- Add sorting options (by date, name, downloads)
 | 
				
			||||||
 | 
					- Implement more efficient database-level permission filtering
 | 
				
			||||||
 | 
					- Add package statistics and trending packages
 | 
				
			||||||
 | 
					- Support for package categories/tags
 | 
				
			||||||
@ -946,6 +946,9 @@ LEVEL = Info
 | 
				
			|||||||
;;
 | 
					;;
 | 
				
			||||||
;; Disable the code explore page.
 | 
					;; Disable the code explore page.
 | 
				
			||||||
;DISABLE_CODE_PAGE = false
 | 
					;DISABLE_CODE_PAGE = false
 | 
				
			||||||
 | 
					;;
 | 
				
			||||||
 | 
					;; Disable the packages explore page.
 | 
				
			||||||
 | 
					;DISABLE_PACKAGES_PAGE = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 | 
					;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 | 
				
			||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 | 
					;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 | 
				
			||||||
 | 
				
			|||||||
@ -97,6 +97,7 @@ var Service = struct {
 | 
				
			|||||||
		DisableUsersPage         bool `ini:"DISABLE_USERS_PAGE"`
 | 
							DisableUsersPage         bool `ini:"DISABLE_USERS_PAGE"`
 | 
				
			||||||
		DisableOrganizationsPage bool `ini:"DISABLE_ORGANIZATIONS_PAGE"`
 | 
							DisableOrganizationsPage bool `ini:"DISABLE_ORGANIZATIONS_PAGE"`
 | 
				
			||||||
		DisableCodePage          bool `ini:"DISABLE_CODE_PAGE"`
 | 
							DisableCodePage          bool `ini:"DISABLE_CODE_PAGE"`
 | 
				
			||||||
 | 
							DisablePackagesPage      bool `ini:"DISABLE_PACKAGES_PAGE"`
 | 
				
			||||||
	} `ini:"service.explore"`
 | 
						} `ini:"service.explore"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	QoS struct {
 | 
						QoS struct {
 | 
				
			||||||
 | 
				
			|||||||
@ -30,6 +30,8 @@ func Code(ctx *context.Context) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	ctx.Data["UsersPageIsDisabled"] = setting.Service.Explore.DisableUsersPage
 | 
						ctx.Data["UsersPageIsDisabled"] = setting.Service.Explore.DisableUsersPage
 | 
				
			||||||
	ctx.Data["OrganizationsPageIsDisabled"] = setting.Service.Explore.DisableOrganizationsPage
 | 
						ctx.Data["OrganizationsPageIsDisabled"] = setting.Service.Explore.DisableOrganizationsPage
 | 
				
			||||||
 | 
						ctx.Data["PackagesPageIsDisabled"] = setting.Service.Explore.DisablePackagesPage
 | 
				
			||||||
 | 
						ctx.Data["PackagesEnabled"] = setting.Packages.Enabled
 | 
				
			||||||
	ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
 | 
						ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
 | 
				
			||||||
	ctx.Data["Title"] = ctx.Tr("explore")
 | 
						ctx.Data["Title"] = ctx.Tr("explore")
 | 
				
			||||||
	ctx.Data["PageIsExplore"] = true
 | 
						ctx.Data["PageIsExplore"] = true
 | 
				
			||||||
 | 
				
			|||||||
@ -22,6 +22,8 @@ func Organizations(ctx *context.Context) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	ctx.Data["UsersPageIsDisabled"] = setting.Service.Explore.DisableUsersPage
 | 
						ctx.Data["UsersPageIsDisabled"] = setting.Service.Explore.DisableUsersPage
 | 
				
			||||||
	ctx.Data["CodePageIsDisabled"] = setting.Service.Explore.DisableCodePage
 | 
						ctx.Data["CodePageIsDisabled"] = setting.Service.Explore.DisableCodePage
 | 
				
			||||||
 | 
						ctx.Data["PackagesPageIsDisabled"] = setting.Service.Explore.DisablePackagesPage
 | 
				
			||||||
 | 
						ctx.Data["PackagesEnabled"] = setting.Packages.Enabled
 | 
				
			||||||
	ctx.Data["Title"] = ctx.Tr("explore")
 | 
						ctx.Data["Title"] = ctx.Tr("explore")
 | 
				
			||||||
	ctx.Data["PageIsExplore"] = true
 | 
						ctx.Data["PageIsExplore"] = true
 | 
				
			||||||
	ctx.Data["PageIsExploreOrganizations"] = true
 | 
						ctx.Data["PageIsExploreOrganizations"] = true
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										124
									
								
								routers/web/explore/packages.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								routers/web/explore/packages.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,124 @@
 | 
				
			|||||||
 | 
					// Copyright 2025 The Gitea Authors. All rights reserved.
 | 
				
			||||||
 | 
					// SPDX-License-Identifier: MIT
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package explore
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/models/db"
 | 
				
			||||||
 | 
						org_model "code.gitea.io/gitea/models/organization"
 | 
				
			||||||
 | 
						packages_model "code.gitea.io/gitea/models/packages"
 | 
				
			||||||
 | 
						user_model "code.gitea.io/gitea/models/user"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/optional"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/setting"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/structs"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/templates"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/services/context"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						tplExplorePackages templates.TplName = "explore/packages"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Packages render explore packages page
 | 
				
			||||||
 | 
					func Packages(ctx *context.Context) {
 | 
				
			||||||
 | 
						ctx.Data["UsersPageIsDisabled"] = setting.Service.Explore.DisableUsersPage
 | 
				
			||||||
 | 
						ctx.Data["OrganizationsPageIsDisabled"] = setting.Service.Explore.DisableOrganizationsPage
 | 
				
			||||||
 | 
						ctx.Data["CodePageIsDisabled"] = setting.Service.Explore.DisableCodePage
 | 
				
			||||||
 | 
						ctx.Data["PackagesPageIsDisabled"] = setting.Service.Explore.DisablePackagesPage
 | 
				
			||||||
 | 
						ctx.Data["PackagesEnabled"] = setting.Packages.Enabled
 | 
				
			||||||
 | 
						ctx.Data["Title"] = ctx.Tr("explore")
 | 
				
			||||||
 | 
						ctx.Data["PageIsExplore"] = true
 | 
				
			||||||
 | 
						ctx.Data["PageIsExplorePackages"] = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						page := ctx.FormInt("page")
 | 
				
			||||||
 | 
						if page <= 0 {
 | 
				
			||||||
 | 
							page = 1
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						query := ctx.FormTrim("q")
 | 
				
			||||||
 | 
						packageType := ctx.FormTrim("type")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ctx.Data["Query"] = query
 | 
				
			||||||
 | 
						ctx.Data["PackageType"] = packageType
 | 
				
			||||||
 | 
						ctx.Data["AvailableTypes"] = packages_model.TypeList
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get all packages matching the search criteria
 | 
				
			||||||
 | 
						pvs, total, err := packages_model.SearchLatestVersions(ctx, &packages_model.PackageSearchOptions{
 | 
				
			||||||
 | 
							Paginator: &db.ListOptions{
 | 
				
			||||||
 | 
								PageSize: setting.UI.PackagesPagingNum * 3, // Get more to account for filtering
 | 
				
			||||||
 | 
								Page:     page,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							Type:       packages_model.Type(packageType),
 | 
				
			||||||
 | 
							Name:       packages_model.SearchValue{Value: query},
 | 
				
			||||||
 | 
							IsInternal: optional.Some(false),
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							ctx.ServerError("SearchLatestVersions", err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Filter packages based on user permissions
 | 
				
			||||||
 | 
						accessiblePVs := make([]*packages_model.PackageVersion, 0, len(pvs))
 | 
				
			||||||
 | 
						for _, pv := range pvs {
 | 
				
			||||||
 | 
							pkg, err := packages_model.GetPackageByID(ctx, pv.PackageID)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								ctx.ServerError("GetPackageByID", err)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							owner, err := user_model.GetUserByID(ctx, pkg.OwnerID)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								ctx.ServerError("GetUserByID", err)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Check if user has access to this package based on owner visibility
 | 
				
			||||||
 | 
							hasAccess := false
 | 
				
			||||||
 | 
							if owner.IsOrganization() {
 | 
				
			||||||
 | 
								// For organizations, check if user can see the org
 | 
				
			||||||
 | 
								if ctx.Doer != nil {
 | 
				
			||||||
 | 
									isMember, err := org_model.IsOrganizationMember(ctx, owner.ID, ctx.Doer.ID)
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										ctx.ServerError("IsOrganizationMember", err)
 | 
				
			||||||
 | 
										return
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									hasAccess = isMember || owner.Visibility == structs.VisibleTypePublic
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									hasAccess = owner.Visibility == structs.VisibleTypePublic
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								// For users, check visibility
 | 
				
			||||||
 | 
								if ctx.Doer != nil {
 | 
				
			||||||
 | 
									hasAccess = owner.Visibility == structs.VisibleTypePublic ||
 | 
				
			||||||
 | 
										owner.Visibility == structs.VisibleTypeLimited ||
 | 
				
			||||||
 | 
										owner.ID == ctx.Doer.ID
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									hasAccess = owner.Visibility == structs.VisibleTypePublic
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if hasAccess {
 | 
				
			||||||
 | 
								accessiblePVs = append(accessiblePVs, pv)
 | 
				
			||||||
 | 
								if len(accessiblePVs) >= setting.UI.PackagesPagingNum {
 | 
				
			||||||
 | 
									break
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pds, err := packages_model.GetPackageDescriptors(ctx, accessiblePVs)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							ctx.ServerError("GetPackageDescriptors", err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ctx.Data["Total"] = int64(len(accessiblePVs))
 | 
				
			||||||
 | 
						ctx.Data["PackageDescriptors"] = pds
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pager := context.NewPagination(int(total), setting.UI.PackagesPagingNum, page, 5)
 | 
				
			||||||
 | 
						pager.AddParamFromRequest(ctx.Req)
 | 
				
			||||||
 | 
						ctx.Data["Page"] = pager
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ctx.HTML(http.StatusOK, tplExplorePackages)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -149,6 +149,8 @@ func Repos(ctx *context.Context) {
 | 
				
			|||||||
	ctx.Data["UsersPageIsDisabled"] = setting.Service.Explore.DisableUsersPage
 | 
						ctx.Data["UsersPageIsDisabled"] = setting.Service.Explore.DisableUsersPage
 | 
				
			||||||
	ctx.Data["OrganizationsPageIsDisabled"] = setting.Service.Explore.DisableOrganizationsPage
 | 
						ctx.Data["OrganizationsPageIsDisabled"] = setting.Service.Explore.DisableOrganizationsPage
 | 
				
			||||||
	ctx.Data["CodePageIsDisabled"] = setting.Service.Explore.DisableCodePage
 | 
						ctx.Data["CodePageIsDisabled"] = setting.Service.Explore.DisableCodePage
 | 
				
			||||||
 | 
						ctx.Data["PackagesPageIsDisabled"] = setting.Service.Explore.DisablePackagesPage
 | 
				
			||||||
 | 
						ctx.Data["PackagesEnabled"] = setting.Packages.Enabled
 | 
				
			||||||
	ctx.Data["Title"] = ctx.Tr("explore")
 | 
						ctx.Data["Title"] = ctx.Tr("explore")
 | 
				
			||||||
	ctx.Data["PageIsExplore"] = true
 | 
						ctx.Data["PageIsExplore"] = true
 | 
				
			||||||
	ctx.Data["ShowRepoOwnerOnList"] = true
 | 
						ctx.Data["ShowRepoOwnerOnList"] = true
 | 
				
			||||||
 | 
				
			|||||||
@ -134,6 +134,8 @@ func Users(ctx *context.Context) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	ctx.Data["OrganizationsPageIsDisabled"] = setting.Service.Explore.DisableOrganizationsPage
 | 
						ctx.Data["OrganizationsPageIsDisabled"] = setting.Service.Explore.DisableOrganizationsPage
 | 
				
			||||||
	ctx.Data["CodePageIsDisabled"] = setting.Service.Explore.DisableCodePage
 | 
						ctx.Data["CodePageIsDisabled"] = setting.Service.Explore.DisableCodePage
 | 
				
			||||||
 | 
						ctx.Data["PackagesPageIsDisabled"] = setting.Service.Explore.DisablePackagesPage
 | 
				
			||||||
 | 
						ctx.Data["PackagesEnabled"] = setting.Packages.Enabled
 | 
				
			||||||
	ctx.Data["Title"] = ctx.Tr("explore")
 | 
						ctx.Data["Title"] = ctx.Tr("explore")
 | 
				
			||||||
	ctx.Data["PageIsExplore"] = true
 | 
						ctx.Data["PageIsExplore"] = true
 | 
				
			||||||
	ctx.Data["PageIsExploreUsers"] = true
 | 
						ctx.Data["PageIsExploreUsers"] = true
 | 
				
			||||||
 | 
				
			|||||||
@ -508,6 +508,7 @@ func registerWebRoutes(m *web.Router) {
 | 
				
			|||||||
				return
 | 
									return
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}, explore.Code)
 | 
							}, explore.Code)
 | 
				
			||||||
 | 
							m.Get("/packages", packagesEnabled, explore.Packages)
 | 
				
			||||||
		m.Get("/topics/search", explore.TopicSearch)
 | 
							m.Get("/topics/search", explore.TopicSearch)
 | 
				
			||||||
	}, optExploreSignIn)
 | 
						}, optExploreSignIn)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -18,5 +18,10 @@
 | 
				
			|||||||
			{{svg "octicon-code"}} {{ctx.Locale.Tr "explore.code"}}
 | 
								{{svg "octicon-code"}} {{ctx.Locale.Tr "explore.code"}}
 | 
				
			||||||
		</a>
 | 
							</a>
 | 
				
			||||||
		{{end}}
 | 
							{{end}}
 | 
				
			||||||
 | 
							{{if and .PackagesEnabled (not .PackagesPageIsDisabled)}}
 | 
				
			||||||
 | 
							<a class="{{if .PageIsExplorePackages}}active {{end}}item" href="{{AppSubUrl}}/explore/packages">
 | 
				
			||||||
 | 
								{{svg "octicon-package"}} {{ctx.Locale.Tr "packages.title"}}
 | 
				
			||||||
 | 
							</a>
 | 
				
			||||||
 | 
							{{end}}
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
</overflow-menu>
 | 
					</overflow-menu>
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										50
									
								
								templates/explore/packages.tmpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								templates/explore/packages.tmpl
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,50 @@
 | 
				
			|||||||
 | 
					{{template "base/head" .}}
 | 
				
			||||||
 | 
					<div role="main" aria-label="{{.Title}}" class="page-content explore packages">
 | 
				
			||||||
 | 
						{{template "explore/navbar" .}}
 | 
				
			||||||
 | 
						<div class="ui container">
 | 
				
			||||||
 | 
							{{template "base/alert" .}}
 | 
				
			||||||
 | 
							<form class="ui form ignore-dirty">
 | 
				
			||||||
 | 
								<div class="ui small fluid action input">
 | 
				
			||||||
 | 
									{{template "shared/search/input" dict "Value" .Query "Placeholder" (ctx.Locale.Tr "search.package_kind")}}
 | 
				
			||||||
 | 
									<select class="ui small dropdown" name="type">
 | 
				
			||||||
 | 
										<option value="">{{ctx.Locale.Tr "packages.filter.type"}}</option>
 | 
				
			||||||
 | 
										<option value="all">{{ctx.Locale.Tr "packages.filter.type.all"}}</option>
 | 
				
			||||||
 | 
										{{range $type := .AvailableTypes}}
 | 
				
			||||||
 | 
										<option{{if eq $.PackageType $type}} selected="selected"{{end}} value="{{$type}}">{{$type.Name}}</option>
 | 
				
			||||||
 | 
										{{end}}
 | 
				
			||||||
 | 
									</select>
 | 
				
			||||||
 | 
									{{template "shared/search/button"}}
 | 
				
			||||||
 | 
								</div>
 | 
				
			||||||
 | 
							</form>
 | 
				
			||||||
 | 
							<div>
 | 
				
			||||||
 | 
								{{range .PackageDescriptors}}
 | 
				
			||||||
 | 
								<div class="flex-list">
 | 
				
			||||||
 | 
									<div class="flex-item">
 | 
				
			||||||
 | 
										<div class="flex-item-main">
 | 
				
			||||||
 | 
											<div class="flex-item-title">
 | 
				
			||||||
 | 
												<a href="{{.VersionWebLink}}">{{.Package.Name}}</a>
 | 
				
			||||||
 | 
												<span class="ui label">{{svg .Package.Type.SVGName 16}} {{.Package.Type.Name}}</span>
 | 
				
			||||||
 | 
											</div>
 | 
				
			||||||
 | 
											<div class="flex-item-body">
 | 
				
			||||||
 | 
												{{$timeStr := DateUtils.TimeSince .Version.CreatedUnix}}
 | 
				
			||||||
 | 
												{{ctx.Locale.Tr "packages.published_by" $timeStr .Creator.HomeLink .Creator.GetDisplayName}}
 | 
				
			||||||
 | 
											</div>
 | 
				
			||||||
 | 
										</div>
 | 
				
			||||||
 | 
									</div>
 | 
				
			||||||
 | 
								</div>
 | 
				
			||||||
 | 
								{{else}}
 | 
				
			||||||
 | 
								{{if eq .Total 0}}
 | 
				
			||||||
 | 
									<div class="empty-placeholder">
 | 
				
			||||||
 | 
										{{svg "octicon-package" 48}}
 | 
				
			||||||
 | 
										<h2>{{ctx.Locale.Tr "packages.empty"}}</h2>
 | 
				
			||||||
 | 
										<p>{{ctx.Locale.Tr "packages.empty.documentation" "https://docs.gitea.com/usage/packages/overview/"}}</p>
 | 
				
			||||||
 | 
									</div>
 | 
				
			||||||
 | 
								{{else}}
 | 
				
			||||||
 | 
									<p class="tw-py-4">{{ctx.Locale.Tr "packages.filter.no_result"}}</p>
 | 
				
			||||||
 | 
								{{end}}
 | 
				
			||||||
 | 
								{{end}}
 | 
				
			||||||
 | 
								{{template "base/paginate" .}}
 | 
				
			||||||
 | 
							</div>
 | 
				
			||||||
 | 
						</div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					{{template "base/footer" .}}
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user