1
0
Fork 1
mirror of https://github.com/git-pkgs/proxy.git synced 2026-07-07 06:43:20 -04:00
pkg-proxy/internal/server/templates/pages/packages_list.html
Andrew Nesbitt 08d975c397
Mount web UI under /ui (#148)
* Mount web UI under /ui (closes #123)

The UI now lives under /ui so reverse proxies can apply different
access rules to it (e.g. require auth) while leaving the package
endpoints (/npm, /pypi, /v2, ...) open to build machines.

- GET / redirects to /ui/
- /api/browse and /api/compare move to /ui/api/browse and
  /ui/api/compare since only the browser JS calls them
- /health, /stats, /metrics, /openapi.json and /api/* stay at root

* README: nginx example for splitting UI auth from package endpoints

The /ui prefix lets a reverse proxy apply different access rules to the
web UI than to the package endpoints. Adds the snippet that motivated
the prefix change so deployers don't have to derive it.
2026-06-07 15:47:29 +01:00

115 lines
6.2 KiB
HTML

{{define "title"}}Cached Packages - git-pkgs proxy{{end}}
{{define "content"}}
<div class="mb-8">
<h1 class="text-3xl font-bold mb-2">Cached Packages</h1>
<p class="text-gray-600 dark:text-gray-400">
{{.Count}} packages{{if .Ecosystem}} in {{.Ecosystem}}{{end}}
</p>
</div>
<div class="mb-6 flex items-center gap-4">
<div class="flex-1">
<label for="ecosystem-filter" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Filter by ecosystem</label>
<select id="ecosystem-filter" class="w-full max-w-xs px-4 py-2 text-sm bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-700 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400">
<option value="">All ecosystems</option>
{{range supportedEcosystems}}
<option value="{{.}}"{{if eq $.Ecosystem .}} selected{{end}}>{{ecosystemBadgeLabel .}}</option>
{{end}}
</select>
</div>
<div class="flex-1">
<label for="sort-by" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Sort by</label>
<select id="sort-by" class="w-full max-w-xs px-4 py-2 text-sm bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-700 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400">
<option value="hits"{{if eq .SortBy "hits"}} selected{{end}}>Cache hits</option>
<option value="name"{{if eq .SortBy "name"}} selected{{end}}>Name</option>
<option value="size"{{if eq .SortBy "size"}} selected{{end}}>Size</option>
<option value="cached_at"{{if eq .SortBy "cached_at"}} selected{{end}}>Recently cached</option>
<option value="ecosystem"{{if eq .SortBy "ecosystem"}} selected{{end}}>Ecosystem</option>
<option value="vulns"{{if eq .SortBy "vulns"}} selected{{end}}>Vulnerabilities</option>
</select>
</div>
</div>
{{if .Results}}
<div class="bg-white dark:bg-gray-900 rounded-xl shadow-sm border border-gray-200 dark:border-gray-800">
<div class="divide-y divide-gray-200 dark:divide-gray-800">
{{range .Results}}
<div class="px-6 py-4 flex items-center justify-between">
<div class="min-w-0 flex-1">
<div class="flex items-center gap-2">
{{template "ecosystem_badge" .Ecosystem}}
<a href="/ui/package/{{.Ecosystem}}/{{.Name}}" class="font-medium truncate hover:text-blue-600 dark:hover:text-blue-400">{{.Name}}</a>
{{if .LatestVersion}}
<span class="text-gray-500 dark:text-gray-400">@{{.LatestVersion}}</span>
{{end}}
</div>
<div class="flex items-center gap-2 mt-1">
{{if .License}}
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-{{if eq .LicenseCategory "permissive"}}green{{else if eq .LicenseCategory "copyleft"}}pink{{else}}gray{{end}}-100 text-{{if eq .LicenseCategory "permissive"}}green{{else if eq .LicenseCategory "copyleft"}}pink{{else}}gray{{end}}-700 dark:bg-{{if eq .LicenseCategory "permissive"}}green{{else if eq .LicenseCategory "copyleft"}}pink{{else}}gray{{end}}-900 dark:text-{{if eq .LicenseCategory "permissive"}}green{{else if eq .LicenseCategory "copyleft"}}pink{{else}}gray{{end}}-300">{{.License}}</span>
{{end}}
{{if gt .VulnCount 0}}
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-red-100 text-red-700 dark:bg-red-900 dark:text-red-300">{{.VulnCount}} vulns</span>
{{end}}
</div>
</div>
<div class="flex items-center gap-4 text-sm text-gray-500 dark:text-gray-400">
<span>{{.Hits}} hits</span>
<span>{{.SizeFormatted}}</span>
{{if .CachedAt}}
<span>{{.CachedAt}}</span>
{{end}}
</div>
</div>
{{end}}
</div>
</div>
{{else}}
<div class="bg-white dark:bg-gray-900 rounded-xl shadow-sm border border-gray-200 dark:border-gray-800 p-12 text-center">
<p class="text-gray-500 dark:text-gray-400">No cached packages found{{if .Ecosystem}} in {{.Ecosystem}}{{end}}</p>
<a href="/ui/" class="mt-4 inline-block text-sm text-blue-600 dark:text-blue-400 hover:underline">Return to dashboard</a>
</div>
{{end}}
{{if gt .TotalPages 1}}
<div class="mt-6 flex items-center justify-center gap-2">
{{if gt .Page 1}}
<a href="?{{if .Ecosystem}}ecosystem={{.Ecosystem}}&{{end}}{{if .SortBy}}sort={{.SortBy}}&{{end}}page={{sub .Page 1}}"
class="px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-700 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700">
Previous
</a>
{{end}}
<span class="px-4 py-2 text-sm text-gray-600 dark:text-gray-400">
Page {{.Page}} of {{.TotalPages}}
</span>
{{if lt .Page .TotalPages}}
<a href="?{{if .Ecosystem}}ecosystem={{.Ecosystem}}&{{end}}{{if .SortBy}}sort={{.SortBy}}&{{end}}page={{add .Page 1}}"
class="px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-700 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700">
Next
</a>
{{end}}
</div>
{{end}}
<script>
document.getElementById('ecosystem-filter').addEventListener('change', function(e) {
const ecosystem = e.target.value;
const sortBy = document.getElementById('sort-by').value;
const params = new URLSearchParams();
if (ecosystem) params.set('ecosystem', ecosystem);
if (sortBy) params.set('sort', sortBy);
window.location.href = '/packages?' + params.toString();
});
document.getElementById('sort-by').addEventListener('change', function(e) {
const sortBy = e.target.value;
const ecosystem = document.getElementById('ecosystem-filter').value;
const params = new URLSearchParams();
if (ecosystem) params.set('ecosystem', ecosystem);
if (sortBy) params.set('sort', sortBy);
window.location.href = '/packages?' + params.toString();
});
</script>
{{end}}