mirror of
https://github.com/git-pkgs/proxy.git
synced 2026-07-06 14:23:20 -04:00
Compare commits
1 commit
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
330a76a68a |
9 changed files with 94 additions and 41 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -14,8 +14,8 @@ proxy
|
|||
coverage.html
|
||||
coverage.txt
|
||||
|
||||
# Dependency directories
|
||||
vendor/
|
||||
# Go vendor directory (repo root only; embedded UI vendor dirs are tracked)
|
||||
/vendor/
|
||||
|
||||
# Go workspace file
|
||||
go.work
|
||||
|
|
|
|||
|
|
@ -450,7 +450,8 @@ func TestStaticFiles(t *testing.T) {
|
|||
path string
|
||||
contentTypes []string
|
||||
}{
|
||||
{"/ui/static/tailwind.js", []string{"text/javascript", "application/javascript"}},
|
||||
{"/ui/static/vendor/tailwind.js", []string{"text/javascript", "application/javascript"}},
|
||||
{"/ui/static/vendor/lucide.min.js", []string{"text/javascript", "application/javascript"}},
|
||||
{"/ui/static/style.css", []string{"text/css"}},
|
||||
}
|
||||
|
||||
|
|
@ -533,7 +534,7 @@ func TestDashboardWithEnrichmentStats(t *testing.T) {
|
|||
body := w.Body.String()
|
||||
|
||||
// Dashboard should link to Tailwind JS
|
||||
if !strings.Contains(body, "/ui/static/tailwind.js") {
|
||||
if !strings.Contains(body, "/ui/static/vendor/tailwind.js") {
|
||||
t.Error("dashboard should link to Tailwind JS")
|
||||
}
|
||||
|
||||
|
|
|
|||
12
internal/server/static/vendor/lucide.min.js
vendored
Normal file
12
internal/server/static/vendor/lucide.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -11,19 +11,23 @@
|
|||
<meta property="og:title" content="{{template "title" .}}">
|
||||
<meta property="og:site_name" content="git-pkgs proxy">
|
||||
{{end}}
|
||||
<script src="/ui/static/tailwind.js"></script>
|
||||
<script src="/ui/static/vendor/tailwind.js"></script>
|
||||
<script src="/ui/static/vendor/lucide.min.js" defer></script>
|
||||
<script>
|
||||
tailwind.config = { darkMode: 'class' }
|
||||
if (localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
||||
document.documentElement.classList.add('dark')
|
||||
}
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
if (window.lucide) lucide.createIcons({ attrs: { 'aria-hidden': 'true', focusable: 'false' } })
|
||||
})
|
||||
</script>
|
||||
{{block "head" .}}{{end}}
|
||||
</head>
|
||||
<body class="h-full bg-gray-50 dark:bg-gray-950 text-gray-900 dark:text-gray-100">
|
||||
<body class="min-h-full flex flex-col bg-gray-50 dark:bg-gray-950 text-gray-900 dark:text-gray-100">
|
||||
{{template "header" .}}
|
||||
|
||||
<main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
<main class="flex-1 w-full max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
{{block "content" .}}{{end}}
|
||||
</main>
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,12 @@
|
|||
<div>
|
||||
<h3 class="text-sm font-semibold text-gray-900 dark:text-gray-100 mb-3">About</h3>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||
git-pkgs proxy is a caching proxy for package registries supporting 16+ ecosystems.
|
||||
git-pkgs proxy is a caching proxy for package registries supporting 17+ ecosystems.
|
||||
</p>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mt-2">
|
||||
<a href="https://github.com/git-pkgs/proxy" target="_blank" rel="noopener" class="inline-flex items-center gap-1 hover:text-gray-900 dark:hover:text-gray-100">
|
||||
<i data-lucide="github" class="w-4 h-4"></i><span>github.com/git-pkgs/proxy</span>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
|
|
@ -26,11 +31,6 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-8 pt-8 border-t border-gray-200 dark:border-gray-800">
|
||||
<p class="text-xs text-center text-gray-500 dark:text-gray-400">
|
||||
Powered by <a href="https://github.com/git-pkgs" class="hover:text-gray-900 dark:hover:text-gray-100" target="_blank">git-pkgs</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
{{end}}
|
||||
|
|
|
|||
|
|
@ -1,31 +1,21 @@
|
|||
{{define "header"}}
|
||||
<header class="sticky top-0 z-50 border-b border-gray-200 dark:border-gray-800 bg-white/80 dark:bg-gray-900/80 backdrop-blur-sm">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex justify-between items-center h-16">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="flex justify-between items-center h-16 gap-4">
|
||||
<a href="/ui/" class="flex items-center gap-2 text-xl font-semibold hover:text-gray-700 dark:hover:text-gray-300 shrink-0">
|
||||
<svg class="w-8 h-8 text-primary-600 dark:text-primary-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4"/>
|
||||
</svg>
|
||||
<a href="/ui/" class="text-xl font-semibold hover:text-gray-700 dark:hover:text-gray-300">git-pkgs proxy</a>
|
||||
<span>git-pkgs proxy</span>
|
||||
</a>
|
||||
<div class="hidden md:block flex-1 max-w-md mx-4">
|
||||
{{template "search_form" .}}
|
||||
</div>
|
||||
<div class="flex-1 max-w-md mx-8">
|
||||
<form action="/ui/search" method="get" class="relative">
|
||||
<input
|
||||
type="text"
|
||||
name="q"
|
||||
placeholder="Search packages..."
|
||||
class="w-full px-4 py-2 pl-10 text-sm bg-gray-100 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"
|
||||
/>
|
||||
<svg class="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
|
||||
</svg>
|
||||
</form>
|
||||
</div>
|
||||
<nav class="flex items-center gap-6">
|
||||
<a href="/ui/install" class="text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100">Install</a>
|
||||
<a href="/health" class="text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100">Health</a>
|
||||
<a href="/stats" class="text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100">API</a>
|
||||
<button id="theme-toggle" class="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors">
|
||||
<nav class="hidden md:flex items-center gap-6">
|
||||
{{template "nav_links" .}}
|
||||
</nav>
|
||||
<div class="flex items-center gap-2">
|
||||
<button id="theme-toggle" class="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors" aria-label="Toggle theme">
|
||||
<svg class="w-5 h-5 hidden dark:block" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z"/>
|
||||
</svg>
|
||||
|
|
@ -33,8 +23,38 @@
|
|||
<path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button id="nav-toggle" type="button" class="md:hidden p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors" aria-label="Toggle menu" aria-expanded="false" aria-controls="mobile-nav">
|
||||
<span class="nav-icon-open"><i data-lucide="menu" class="w-5 h-5"></i></span>
|
||||
<span class="nav-icon-close hidden"><i data-lucide="x" class="w-5 h-5"></i></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="mobile-nav" class="hidden md:hidden pb-4 space-y-3">
|
||||
{{template "search_form" .}}
|
||||
<nav class="flex flex-col">
|
||||
{{template "nav_links" .}}
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
{{end}}
|
||||
|
||||
{{define "search_form"}}
|
||||
<form action="/ui/search" method="get" class="relative">
|
||||
<input
|
||||
type="text"
|
||||
name="q"
|
||||
placeholder="Search packages..."
|
||||
class="w-full px-4 py-2 pl-10 text-sm bg-gray-100 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"
|
||||
/>
|
||||
<svg class="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
|
||||
</svg>
|
||||
</form>
|
||||
{{end}}
|
||||
|
||||
{{define "nav_links"}}
|
||||
<a href="/ui/install" class="text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 py-2 md:py-0">Install</a>
|
||||
<a href="/health" class="text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 py-2 md:py-0">Health</a>
|
||||
<a href="/stats" class="text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 py-2 md:py-0">API</a>
|
||||
{{end}}
|
||||
|
|
|
|||
|
|
@ -9,5 +9,18 @@
|
|||
localStorage.theme = 'dark';
|
||||
}
|
||||
});
|
||||
|
||||
(function() {
|
||||
const toggle = document.getElementById('nav-toggle');
|
||||
const menu = document.getElementById('mobile-nav');
|
||||
if (!toggle || !menu) return;
|
||||
toggle.addEventListener('click', function() {
|
||||
const open = toggle.getAttribute('aria-expanded') === 'true';
|
||||
toggle.setAttribute('aria-expanded', String(!open));
|
||||
menu.classList.toggle('hidden');
|
||||
toggle.querySelector('.nav-icon-open').classList.toggle('hidden');
|
||||
toggle.querySelector('.nav-icon-close').classList.toggle('hidden');
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
{{end}}
|
||||
|
|
|
|||
|
|
@ -100,35 +100,38 @@ function renderFileTree(files, basePath) {
|
|||
if (basePath) {
|
||||
const parentPath = basePath.split('/').slice(0, -2).join('/');
|
||||
html += `
|
||||
<div class="px-2 py-1 hover:bg-gray-100 dark:hover:bg-gray-800 rounded cursor-pointer text-sm"
|
||||
<div class="px-2 py-1 hover:bg-gray-100 dark:hover:bg-gray-800 rounded cursor-pointer text-sm flex items-center gap-2 text-gray-500 dark:text-gray-400"
|
||||
onclick="loadFileTree('${escapeHTML(parentPath)}'); currentPath='${escapeHTML(parentPath)}';">
|
||||
<span class="text-gray-500 dark:text-gray-400">📁 ..</span>
|
||||
<i data-lucide="corner-left-up" class="w-4 h-4 shrink-0"></i><span>..</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// Render files and directories
|
||||
for (const file of files) {
|
||||
const icon = file.is_dir ? '📁' : '📄';
|
||||
const classes = 'px-2 py-1 hover:bg-gray-100 dark:hover:bg-gray-800 rounded cursor-pointer text-sm truncate';
|
||||
const iconName = file.is_dir ? 'folder' : 'file';
|
||||
const iconClass = file.is_dir ? 'text-amber-500 dark:text-amber-400' : 'text-gray-500 dark:text-gray-400';
|
||||
const classes = 'px-2 py-1 hover:bg-gray-100 dark:hover:bg-gray-800 rounded cursor-pointer text-sm flex items-center gap-2';
|
||||
const iconHTML = `<i data-lucide="${iconName}" class="w-4 h-4 shrink-0 ${iconClass}"></i>`;
|
||||
|
||||
if (file.is_dir) {
|
||||
html += `
|
||||
<div class="${classes}" onclick="loadFileTree('${escapeHTML(file.path)}'); currentPath='${escapeHTML(file.path)}';">
|
||||
<span>${icon} ${escapeHTML(file.name)}</span>
|
||||
${iconHTML}<span class="truncate">${escapeHTML(file.name)}</span>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
html += `
|
||||
<div class="${classes}" onclick="loadFile('${escapeHTML(file.path)}')">
|
||||
<span>${icon} ${escapeHTML(file.name)}</span>
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400 ml-2">${formatSize(file.size)}</span>
|
||||
${iconHTML}<span class="truncate">${escapeHTML(file.name)}</span>
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400 ml-auto shrink-0">${formatSize(file.size)}</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
container.innerHTML = html;
|
||||
if (window.lucide) lucide.createIcons({ attrs: { 'aria-hidden': 'true', focusable: 'false' } });
|
||||
}
|
||||
|
||||
// Load and display file content
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue