mirror of
https://github.com/git-pkgs/proxy.git
synced 2026-07-07 14:53:20 -04:00
* 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.
136 lines
7.2 KiB
HTML
136 lines
7.2 KiB
HTML
{{define "title"}}{{.Package.Name}} - git-pkgs proxy{{end}}
|
|
|
|
{{define "content"}}
|
|
<div class="mb-6">
|
|
<div class="flex items-center gap-3 mb-2">
|
|
{{template "ecosystem_badge" .Package.Ecosystem}}
|
|
<h1 class="text-3xl font-bold">{{.Package.Name}}</h1>
|
|
</div>
|
|
{{if .Package.Description.Valid}}
|
|
<p class="text-gray-600 dark:text-gray-400">{{.Package.Description.String}}</p>
|
|
{{end}}
|
|
</div>
|
|
|
|
<div class="grid md:grid-cols-2 gap-6 mb-8">
|
|
<div class="bg-white dark:bg-gray-900 rounded-xl p-6 shadow-sm border border-gray-200 dark:border-gray-800">
|
|
<h2 class="text-lg font-semibold mb-4">Package Info</h2>
|
|
<dl class="space-y-2">
|
|
{{if .Package.LatestVersion.Valid}}
|
|
<div><dt class="inline text-gray-500 dark:text-gray-400">Latest Version:</dt> <dd class="inline font-mono">{{.Package.LatestVersion.String}}</dd></div>
|
|
{{end}}
|
|
{{if .Package.License.Valid}}
|
|
<div><dt class="inline text-gray-500 dark:text-gray-400">License:</dt> <dd class="inline"><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">{{.Package.License.String}}</span></dd></div>
|
|
{{end}}
|
|
{{if .Package.Homepage.Valid}}
|
|
<div><dt class="inline text-gray-500 dark:text-gray-400">Homepage:</dt> <dd class="inline"><a href="{{.Package.Homepage.String}}" class="text-blue-600 dark:text-blue-400 hover:underline" target="_blank">{{.Package.Homepage.String}}</a></dd></div>
|
|
{{end}}
|
|
{{if .Package.RepositoryURL.Valid}}
|
|
<div><dt class="inline text-gray-500 dark:text-gray-400">Repository:</dt> <dd class="inline"><a href="{{.Package.RepositoryURL.String}}" class="text-blue-600 dark:text-blue-400 hover:underline" target="_blank">{{.Package.RepositoryURL.String}}</a></dd></div>
|
|
{{end}}
|
|
</dl>
|
|
</div>
|
|
|
|
{{if .Vulnerabilities}}
|
|
<div class="bg-white dark:bg-gray-900 rounded-xl p-6 shadow-sm border border-red-200 dark:border-red-900">
|
|
<h2 class="text-lg font-semibold mb-4 text-red-600 dark:text-red-400">Security Vulnerabilities ({{len .Vulnerabilities}})</h2>
|
|
<div class="space-y-3">
|
|
{{range .Vulnerabilities}}
|
|
<div class="border-l-4 border-{{if eq .Severity "critical"}}red{{else if eq .Severity "high"}}orange{{else if eq .Severity "medium"}}yellow{{else}}blue{{end}}-500 pl-3">
|
|
<div class="font-semibold text-sm">{{.VulnID}}</div>
|
|
{{if .Summary.Valid}}<p class="text-sm text-gray-600 dark:text-gray-400">{{.Summary.String}}</p>{{end}}
|
|
<div class="flex gap-2 mt-1">
|
|
<span class="text-xs uppercase font-medium text-{{if eq .Severity "critical"}}red{{else if eq .Severity "high"}}orange{{else if eq .Severity "medium"}}yellow{{else}}blue{{end}}-600 dark:text-{{if eq .Severity "critical"}}red{{else if eq .Severity "high"}}orange{{else if eq .Severity "medium"}}yellow{{else}}blue{{end}}-400">{{.Severity}}</span>
|
|
{{if .FixedVersion.Valid}}<span class="text-xs text-gray-500 dark:text-gray-400">Fixed in {{.FixedVersion.String}}</span>{{end}}
|
|
</div>
|
|
</div>
|
|
{{end}}
|
|
</div>
|
|
</div>
|
|
{{end}}
|
|
</div>
|
|
|
|
{{if .Versions}}
|
|
<div class="bg-white dark:bg-gray-900 rounded-xl shadow-sm border border-gray-200 dark:border-gray-800">
|
|
<div class="px-6 py-4 border-b border-gray-200 dark:border-gray-800 flex items-center justify-between">
|
|
<h2 class="text-lg font-semibold">Versions ({{len .Versions}})</h2>
|
|
{{if gt (len .Versions) 1}}
|
|
<button id="compare-btn" class="px-3 py-1 text-sm bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors">
|
|
Compare Versions
|
|
</button>
|
|
{{end}}
|
|
</div>
|
|
<div class="divide-y divide-gray-200 dark:divide-gray-800">
|
|
{{range .Versions}}
|
|
<div class="px-6 py-3 flex items-center justify-between version-row">
|
|
<div class="flex items-center gap-3">
|
|
<input type="checkbox" class="version-checkbox hidden" data-version="{{.Version}}" />
|
|
<a href="/ui/package/{{$.Package.Ecosystem}}/{{$.Package.Name}}/{{.Version}}" class="font-mono text-sm hover:text-blue-600 dark:hover:text-blue-400">{{.PURL}}</a>
|
|
{{if .Yanked}}<span class="ml-2 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">yanked</span>{{end}}
|
|
</div>
|
|
{{if .PublishedAt.Valid}}<span class="text-sm text-gray-500 dark:text-gray-400">{{.PublishedAt.Time.Format "2006-01-02"}}</span>{{end}}
|
|
</div>
|
|
{{end}}
|
|
</div>
|
|
</div>
|
|
|
|
{{if gt (len .Versions) 1}}
|
|
<script>
|
|
let compareMode = false;
|
|
const ecosystem = '{{.Package.Ecosystem}}';
|
|
const packageName = '{{.Package.Name}}';
|
|
|
|
document.getElementById('compare-btn').addEventListener('click', function() {
|
|
compareMode = !compareMode;
|
|
|
|
if (compareMode) {
|
|
this.textContent = 'Cancel';
|
|
this.classList.remove('bg-blue-600', 'hover:bg-blue-700');
|
|
this.classList.add('bg-gray-600', 'hover:bg-gray-700');
|
|
|
|
// Show checkboxes
|
|
document.querySelectorAll('.version-checkbox').forEach(cb => {
|
|
cb.classList.remove('hidden');
|
|
});
|
|
|
|
// Add instruction
|
|
const instruction = document.createElement('div');
|
|
instruction.id = 'compare-instruction';
|
|
instruction.className = 'px-6 py-3 bg-blue-50 dark:bg-blue-900/20 border-t border-blue-200 dark:border-blue-900 text-sm text-blue-800 dark:text-blue-300';
|
|
instruction.textContent = 'Select two versions to compare';
|
|
this.parentElement.parentElement.appendChild(instruction);
|
|
} else {
|
|
this.textContent = 'Compare Versions';
|
|
this.classList.remove('bg-gray-600', 'hover:bg-gray-700');
|
|
this.classList.add('bg-blue-600', 'hover:bg-blue-700');
|
|
|
|
// Hide checkboxes
|
|
document.querySelectorAll('.version-checkbox').forEach(cb => {
|
|
cb.classList.add('hidden');
|
|
cb.checked = false;
|
|
});
|
|
|
|
// Remove instruction
|
|
document.getElementById('compare-instruction')?.remove();
|
|
}
|
|
});
|
|
|
|
// Handle checkbox changes
|
|
document.addEventListener('change', function(e) {
|
|
if (e.target.classList.contains('version-checkbox')) {
|
|
const checked = document.querySelectorAll('.version-checkbox:checked');
|
|
|
|
if (checked.length === 2) {
|
|
// Navigate to compare page
|
|
const v1 = checked[0].dataset.version;
|
|
const v2 = checked[1].dataset.version;
|
|
window.location.href = `/ui/package/${ecosystem}/${packageName}/compare/${v1}...${v2}`;
|
|
} else if (checked.length > 2) {
|
|
// Uncheck the oldest selection
|
|
checked[0].checked = false;
|
|
}
|
|
}
|
|
});
|
|
</script>
|
|
{{end}}
|
|
{{end}}
|
|
{{end}}
|