use super::api::{DockerDetail, MavenDetail, PackageDetail, RegistryStats, RepoInfo}; use super::components::*; /// Renders the main dashboard page pub fn render_dashboard(stats: &RegistryStats) -> String { let content = format!( r##"

Dashboard

Overview of all registries

{} {} {} {} {}

Quick Links

{}
Docker Registry
API: /v2/
{}
Maven Repository
API: /maven2/
{}
npm Registry
API: /npm/
{}
Cargo Registry
API: /cargo/
{}
PyPI Repository
API: /simple/
"##, stat_card( "Docker", icons::DOCKER, stats.docker, "/ui/docker", "images" ), stat_card("Maven", icons::MAVEN, stats.maven, "/ui/maven", "artifacts"), stat_card("npm", icons::NPM, stats.npm, "/ui/npm", "packages"), stat_card("Cargo", icons::CARGO, stats.cargo, "/ui/cargo", "crates"), stat_card("PyPI", icons::PYPI, stats.pypi, "/ui/pypi", "packages"), // Quick Links icons icons::DOCKER, icons::MAVEN, icons::NPM, icons::CARGO, icons::PYPI, ); layout("Dashboard", &content, Some("dashboard")) } /// Renders a registry list page (docker, maven, npm, cargo, pypi) pub fn render_registry_list(registry_type: &str, title: &str, repos: &[RepoInfo]) -> String { let icon = get_registry_icon(registry_type); let table_rows = if repos.is_empty() { r##"
📭
No repositories found
Push your first artifact to see it here
"## .to_string() } else { repos .iter() .map(|repo| { let detail_url = format!("/ui/{}/{}", registry_type, encode_uri_component(&repo.name)); format!( r##" {} {} {} {} "##, detail_url, detail_url, html_escape(&repo.name), repo.versions, format_size(repo.size), &repo.updated ) }) .collect::>() .join("") }; let version_label = match registry_type { "docker" => "Tags", "maven" => "Versions", _ => "Versions", }; let content = format!( r##"
{}

{}

{} repositories

{}
Name {} Size Updated
"##, icon, title, repos.len(), registry_type, version_label, table_rows ); layout(title, &content, Some(registry_type)) } /// Renders Docker image detail page pub fn render_docker_detail(name: &str, detail: &DockerDetail) -> String { let tags_rows = if detail.tags.is_empty() { r##"No tags found"##.to_string() } else { detail .tags .iter() .map(|tag| { format!( r##" {} {} {} "##, html_escape(&tag.name), format_size(tag.size), &tag.created ) }) .collect::>() .join("") }; let pull_cmd = format!("docker pull 127.0.0.1:4000/{}", name); let content = format!( r##"
{}

{}

Pull Command

{}

Tags ({} total)

{}
Tag Size Created
"##, html_escape(name), icons::DOCKER, html_escape(name), pull_cmd, pull_cmd, detail.tags.len(), tags_rows ); layout(&format!("{} - Docker", name), &content, Some("docker")) } /// Renders package detail page (npm, cargo, pypi) pub fn render_package_detail(registry_type: &str, name: &str, detail: &PackageDetail) -> String { let icon = get_registry_icon(registry_type); let registry_title = get_registry_title(registry_type); let versions_rows = if detail.versions.is_empty() { r##"No versions found"##.to_string() } else { detail .versions .iter() .map(|v| { format!( r##" {} {} {} "##, html_escape(&v.version), format_size(v.size), &v.published ) }) .collect::>() .join("") }; let install_cmd = match registry_type { "npm" => format!("npm install {} --registry http://127.0.0.1:4000/npm", name), "cargo" => format!("cargo add {}", name), "pypi" => format!( "pip install {} --index-url http://127.0.0.1:4000/simple", name ), _ => String::new(), }; let content = format!( r##"
{} / {}
{}

{}

Install Command

{}

Versions ({} total)

{}
Version Size Published
"##, registry_type, registry_title, html_escape(name), icon, html_escape(name), install_cmd, install_cmd, detail.versions.len(), versions_rows ); layout( &format!("{} - {}", name, registry_title), &content, Some(registry_type), ) } /// Renders Maven artifact detail page pub fn render_maven_detail(path: &str, detail: &MavenDetail) -> String { let artifact_rows = if detail.artifacts.is_empty() { r##"No artifacts found"##.to_string() } else { detail.artifacts.iter().map(|a| { let download_url = format!("/maven2/{}/{}", path, a.filename); format!(r##" {} {} "##, download_url, html_escape(&a.filename), format_size(a.size)) }).collect::>().join("") }; // Extract artifact name from path (last component before version) let parts: Vec<&str> = path.split('/').collect(); let artifact_name = if parts.len() >= 2 { parts[parts.len() - 2] } else { path }; let dep_cmd = format!( r#" {} {} {} "#, parts[..parts.len().saturating_sub(2)].join("."), artifact_name, parts.last().unwrap_or(&"") ); let content = format!( r##"
{}

{}

Maven Dependency

{}

Artifacts ({} files)

{}
Filename Size
"##, html_escape(path), icons::MAVEN, html_escape(path), html_escape(&dep_cmd), detail.artifacts.len(), artifact_rows ); layout(&format!("{} - Maven", path), &content, Some("maven")) } /// Returns SVG icon path for the registry type fn get_registry_icon(registry_type: &str) -> &'static str { match registry_type { "docker" => icons::DOCKER, "maven" => icons::MAVEN, "npm" => icons::NPM, "cargo" => icons::CARGO, "pypi" => icons::PYPI, _ => { r#""# } } } fn get_registry_title(registry_type: &str) -> &'static str { match registry_type { "docker" => "Docker Registry", "maven" => "Maven Repository", "npm" => "npm Registry", "cargo" => "Cargo Registry", "pypi" => "PyPI Repository", _ => "Registry", } } /// Simple URL encoding for path components pub fn encode_uri_component(s: &str) -> String { let mut result = String::new(); for c in s.chars() { match c { 'a'..='z' | 'A'..='Z' | '0'..='9' | '-' | '_' | '.' | '~' => result.push(c), _ => { for byte in c.to_string().as_bytes() { result.push_str(&format!("%{:02X}", byte)); } } } } result }