package server import ( "html/template" "sort" "strings" "github.com/git-pkgs/proxy/internal/database" ) // DashboardData contains data for rendering the dashboard. type DashboardData struct { Layout Stats DashboardStats EnrichmentStats EnrichmentStatsView RecentPackages []PackageInfo PopularPackages []PackageInfo } // DashboardStats contains cache statistics for the dashboard. type DashboardStats struct { CachedArtifacts int64 TotalSize string TotalPackages int64 TotalVersions int64 } // EnrichmentStatsView contains enrichment statistics for display. type EnrichmentStatsView struct { EnrichedPackages int64 VulnSyncedPackages int64 TotalVulnerabilities int64 CriticalVulns int64 HighVulns int64 MediumVulns int64 LowVulns int64 HasVulns bool } // PackageInfo contains information about a cached package. type PackageInfo struct { Ecosystem string Name string Version string Size string Hits int64 CachedAt string License string LicenseCategory string VulnCount int64 LatestVersion string IsOutdated bool } // RegistryConfig contains configuration instructions for a package registry. type RegistryConfig struct { ID string Name string Language string Endpoint string Instructions template.HTML } // PackageShowData contains data for rendering the package show page. type PackageShowData struct { Layout Package *database.Package Versions []database.Version Vulnerabilities []database.Vulnerability LicenseCategory string } // VersionShowData contains data for rendering the version show page. type VersionShowData struct { Layout Package *database.Package Version *database.Version Artifacts []database.Artifact Vulnerabilities []database.Vulnerability IsOutdated bool LicenseCategory string HasCachedArtifact bool } // SearchPageData contains data for rendering the search results page. type SearchPageData struct { Layout Query string Ecosystem string Results []SearchResultItem Count int Page int PerPage int TotalPages int } // SearchResultItem represents a single search result for display. type SearchResultItem struct { Ecosystem string Name string LatestVersion string License string LicenseCategory string Hits int64 Size int64 SizeFormatted string CachedAt string VulnCount int64 } // PackagesListPageData contains data for rendering the packages list page. type PackagesListPageData struct { Layout Ecosystem string SortBy string Results []SearchResultItem Count int TotalPackages int64 EcosystemFilters []EcosystemFilter Page int PerPage int TotalPages int } // EcosystemFilter represents an ecosystem filter pill with a package count. type EcosystemFilter struct { Ecosystem string Count int64 } func supportedEcosystems() []string { // this list should be kept sorted in lexicographic order so // that the ecosystem filter pills in the UI will be in the // expected order return []string{ "alpine", "arch", "cargo", "composer", "conan", "conda", "cran", "deb", "gem", "golang", "hex", "julia", "maven", "npm", "nuget", "oci", "pub", "pypi", "rpm", "swift", } } func ecosystemBadgeLabel(ecosystem string) string { switch ecosystem { case "oci": return "container" case "deb": return "debian" default: return ecosystem } } func ecosystemColorClasses(ecosystem string) string { switch ecosystem { case "npm", "maven": return "bg-red-100 text-red-700 dark:bg-red-900/50 dark:text-red-300" case "cargo": return "bg-orange-100 text-orange-700 dark:bg-orange-900/50 dark:text-orange-300" case "gem": return "bg-pink-100 text-pink-700 dark:bg-pink-900/50 dark:text-pink-300" case "go", "golang": return "bg-cyan-100 text-cyan-700 dark:bg-cyan-900/50 dark:text-cyan-300" case "hex": return "bg-purple-100 text-purple-700 dark:bg-purple-900/50 dark:text-purple-300" case "pub": return "bg-blue-100 text-blue-700 dark:bg-blue-900/50 dark:text-blue-300" case "pypi": return "bg-yellow-100 text-yellow-700 dark:bg-yellow-900/50 dark:text-yellow-300" case "nuget": return "bg-indigo-100 text-indigo-700 dark:bg-indigo-900/50 dark:text-indigo-300" case "composer": return "bg-violet-100 text-violet-700 dark:bg-violet-900/50 dark:text-violet-300" case "conan": return "bg-teal-100 text-teal-700 dark:bg-teal-900/50 dark:text-teal-300" case "conda": return "bg-green-100 text-green-700 dark:bg-green-900/50 dark:text-green-300" case "cran": return "bg-slate-100 text-slate-700 dark:bg-slate-800 dark:text-slate-300" case "julia": return "bg-emerald-100 text-emerald-700 dark:bg-emerald-900/50 dark:text-emerald-300" case "swift": return "bg-orange-100 text-orange-700 dark:bg-orange-900/50 dark:text-orange-300" case "oci": return "bg-sky-100 text-sky-700 dark:bg-sky-900/50 dark:text-sky-300" case "deb": return "bg-red-100 text-red-800 dark:bg-red-900/50 dark:text-red-300" case "rpm": return "bg-amber-100 text-amber-800 dark:bg-amber-900/50 dark:text-amber-300" case "alpine": return "bg-lime-100 text-lime-800 dark:bg-lime-900/50 dark:text-lime-300" case "arch": return "bg-blue-100 text-blue-800 dark:bg-blue-900/50 dark:text-blue-300" default: return "bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300" } } func ecosystemBadgeClasses(ecosystem string) string { return "inline-flex items-center px-2 py-0.5 rounded text-xs font-medium " + ecosystemColorClasses(ecosystem) } func ecosystemPillClasses(ecosystem string) string { return "inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium hover:opacity-90 " + ecosystemColorClasses(ecosystem) } func buildEcosystemFilters(counts map[string]int64) []EcosystemFilter { var filters []EcosystemFilter seen := make(map[string]bool, len(counts)) for _, eco := range supportedEcosystems() { count := counts[eco] if count > 0 { filters = append(filters, EcosystemFilter{Ecosystem: eco, Count: count}) seen[eco] = true } } var extra []string for eco, count := range counts { if count > 0 && !seen[eco] { extra = append(extra, eco) } } sort.Strings(extra) for _, eco := range extra { filters = append(filters, EcosystemFilter{Ecosystem: eco, Count: counts[eco]}) } return filters } func getRegistryConfigs(baseURL string) []RegistryConfig { swiftInsecureFlag := "" if strings.HasPrefix(strings.ToLower(baseURL), "http://") { swiftInsecureFlag = "--allow-insecure-http " } dockerHost := strings.TrimPrefix(strings.TrimPrefix(baseURL, "https://"), "http://") return []RegistryConfig{ { ID: "npm", Name: "npm", Language: "JavaScript", Endpoint: "/npm/", Instructions: template.HTML(`
Configure npm to use the proxy:
# In ~/.npmrc or project .npmrc
registry=` + baseURL + `/npm/
# Or via environment variable
npm_config_registry=` + baseURL + `/npm/ npm install`),
},
{
ID: "cargo",
Name: "Cargo",
Language: "Rust",
Endpoint: "/cargo/",
Instructions: template.HTML(`Configure Cargo to use the proxy (sparse index protocol):
# In ~/.cargo/config.toml or project .cargo/config.toml
[source.crates-io]
replace-with = "proxy"
[source.proxy]
registry = "sparse+` + baseURL + `/cargo/"`),
},
{
ID: "gem",
Name: "RubyGems",
Language: "Ruby",
Endpoint: "/gem/",
Instructions: template.HTML(`Configure Bundler/RubyGems to use the proxy:
# In Gemfile
source "` + baseURL + `/gem"
# Or configure globally
gem sources --add ` + baseURL + `/gem/
bundle config mirror.https://rubygems.org ` + baseURL + `/gem`),
},
{
ID: "go",
Name: "Go Modules",
Language: "Go",
Endpoint: "/go/",
Instructions: template.HTML(`Set the GOPROXY environment variable:
export GOPROXY=` + baseURL + `/go,direct
# Add to your shell profile for persistence`),
},
{
ID: "hex",
Name: "Hex",
Language: "Elixir",
Endpoint: "/hex/",
Instructions: template.HTML(`Configure Hex to use the proxy:
# In ~/.hex/hex.config
{default_url, <<"` + baseURL + `/hex">>}.
# Or via environment variable
export HEX_MIRROR=` + baseURL + `/hex`),
},
{
ID: "pub",
Name: "pub.dev",
Language: "Dart/Flutter",
Endpoint: "/pub/",
Instructions: template.HTML(`Set the PUB_HOSTED_URL environment variable:
export PUB_HOSTED_URL=` + baseURL + `/pub`),
},
{
ID: "pypi",
Name: "PyPI",
Language: "Python",
Endpoint: "/pypi/",
Instructions: template.HTML(`Configure pip to use the proxy:
# Via command line
pip install --index-url ` + baseURL + `/pypi/simple/ package_name
# In ~/.pip/pip.conf
[global]
index-url = ` + baseURL + `/pypi/simple/`),
},
{
ID: "maven",
Name: "Maven",
Language: "Java",
Endpoint: "/maven/",
Instructions: template.HTML(`Configure Maven to use the proxy:
<!-- In ~/.m2/settings.xml -->
<settings>
<mirrors>
<mirror>
<id>proxy</id>
<mirrorOf>central</mirrorOf>
<url>` + baseURL + `/maven/</url>
</mirror>
</mirrors>
</settings>`),
},
{
ID: "gradle",
Name: "Gradle Build Cache",
Language: "Java/Kotlin",
Endpoint: "/gradle/",
Instructions: template.HTML(`Configure Gradle to use the proxy for HttpBuildCache:
// In settings.gradle(.kts)
buildCache {
remote<HttpBuildCache> {
url = uri("` + baseURL + `/gradle/")
push = true
}
}`),
},
{
ID: "nuget",
Name: "NuGet",
Language: ".NET",
Endpoint: "/nuget/",
Instructions: template.HTML(`Configure NuGet to use the proxy:
<!-- In nuget.config -->
<configuration>
<packageSources>
<clear />
<add key="proxy" value="` + baseURL + `/nuget/v3/index.json" />
</packageSources>
</configuration>
# Or via CLI
dotnet nuget add source ` + baseURL + `/nuget/v3/index.json -n proxy`),
},
{
ID: "composer",
Name: "Composer",
Language: "PHP",
Endpoint: "/composer/",
Instructions: template.HTML(`Configure Composer to use the proxy:
// In composer.json
{
"repositories": [
{
"type": "composer",
"url": "` + baseURL + `/composer"
}
]
}
# Or globally
composer config -g repositories.proxy composer ` + baseURL + `/composer`),
},
{
ID: "conan",
Name: "Conan",
Language: "C/C++",
Endpoint: "/conan/",
Instructions: template.HTML(`Configure Conan to use the proxy:
conan remote add proxy ` + baseURL + `/conan
conan remote disable conancenter`),
},
{
ID: "conda",
Name: "Conda",
Language: "Python/R",
Endpoint: "/conda/",
Instructions: template.HTML(`Configure Conda to use the proxy:
# In ~/.condarc
channels:
- ` + baseURL + `/conda/main
- ` + baseURL + `/conda/conda-forge
default_channels:
- ` + baseURL + `/conda/main
# Or via command
conda config --add channels ` + baseURL + `/conda/main`),
},
{
ID: "cran",
Name: "CRAN",
Language: "R",
Endpoint: "/cran/",
Instructions: template.HTML(`Configure R to use the proxy:
# In R session
options(repos = c(CRAN = "` + baseURL + `/cran"))
# In ~/.Rprofile for persistence
local({
r <- getOption("repos")
r["CRAN"] <- "` + baseURL + `/cran"
options(repos = r)
})`),
},
{
ID: "julia",
Name: "Julia",
Language: "Julia",
Endpoint: "/julia/",
Instructions: template.HTML(`Set the Pkg server before starting Julia:
export JULIA_PKG_SERVER=` + baseURL + `/julia
Or inside a running session:
ENV["JULIA_PKG_SERVER"] = "` + baseURL + `/julia"
using Pkg; Pkg.update()`),
},
{
ID: "swift",
Name: "Swift Package Registry",
Language: "Swift",
Endpoint: "/swift/",
Instructions: template.HTML(`Configure SwiftPM to use the proxy for this project:
swift package-registry set ` + swiftInsecureFlag + baseURL + `/swift
Use scoped package identifiers in Package.swift, for example apple.swift-argument-parser.
Configure Docker to use the proxy as a mirror:
# In /etc/docker/daemon.json
{
"registry-mirrors": ["` + baseURL + `"]
}
# Then restart Docker
sudo systemctl restart docker
# Or pull directly
docker pull ` + dockerHost + `/library/nginx:latest`),
},
{
ID: "deb",
Name: "Debian/APT",
Language: "Debian/Ubuntu",
Endpoint: "/debian/",
Instructions: template.HTML(`Configure APT to use the proxy:
# In /etc/apt/sources.list or /etc/apt/sources.list.d/proxy.list
deb ` + baseURL + `/debian stable main contrib
# Replace your existing sources.list entries with the proxy URL
# Then run:
sudo apt update`),
},
{
ID: "rpm",
Name: "RPM/Yum",
Language: "Fedora/RHEL",
Endpoint: "/rpm/",
Instructions: template.HTML(`Configure yum/dnf to use the proxy:
# In /etc/yum.repos.d/proxy.repo
[proxy-fedora]
name=Fedora via Proxy
baseurl=` + baseURL + `/rpm/releases/$releasever/Everything/$basearch/os/
enabled=1
gpgcheck=0
# Then run:
sudo dnf clean all
sudo dnf update`),
},
{
ID: "apk",
Name: "Alpine APK",
Language: "Alpine Linux",
Endpoint: "/apk/",
Instructions: template.HTML(`Configure apk to use the proxy:
# In /etc/apk/repositories
` + baseURL + `/apk/alpine/v3.22/main
` + baseURL + `/apk/alpine/v3.22/community
# Then run:
apk update`),
},
{
ID: "arch",
Name: "Arch Linux pacman",
Language: "Arch Linux",
Endpoint: "/arch/",
Instructions: template.HTML(`Configure pacman to use the proxy:
# In /etc/pacman.d/mirrorlist, above any other Server line
Server = ` + baseURL + `/arch/archlinux/$repo/os/$arch
# Then run:
pacman -Syu`),
},
}
}