Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions internal/server/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"}},
}

Expand Down Expand Up @@ -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")
}

Expand Down
12 changes: 12 additions & 0 deletions internal/server/static/vendor/lucide.min.js

Large diffs are not rendered by default.

10 changes: 7 additions & 3 deletions internal/server/templates/layout/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -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>

Expand Down
12 changes: 6 additions & 6 deletions internal/server/templates/layout/footer.html
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -26,11 +31,6 @@ <h3 class="text-sm font-semibold text-gray-900 dark:text-gray-100 mb-3">Supporte
</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}}
62 changes: 41 additions & 21 deletions internal/server/templates/layout/header.html
Original file line number Diff line number Diff line change
@@ -1,40 +1,60 @@
{{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>
</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>
<span>git-pkgs proxy</span>
</a>
<div class="hidden md:block flex-1 max-w-md mx-4">
{{template "search_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>
<svg class="w-5 h-5 block dark:hidden" fill="currentColor" viewBox="0 0 20 20">
<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}}
13 changes: 13 additions & 0 deletions internal/server/templates/layout/styles.html
Original file line number Diff line number Diff line change
Expand Up @@ -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}}
17 changes: 10 additions & 7 deletions internal/server/templates/pages/browse_source.html
Original file line number Diff line number Diff line change
Expand Up @@ -100,35 +100,38 @@ <h2 class="font-semibold font-mono text-sm" id="file-path">Select a file</h2>
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
Expand Down
Loading