use super::api::{DashboardResponse, DockerDetail, MavenDetail, PackageDetail, RepoInfo}; use super::components::*; use super::i18n::{get_translations, Lang}; /// Renders the main dashboard page with dark theme pub fn render_dashboard(data: &DashboardResponse, lang: Lang) -> String { let t = get_translations(lang); // Render global stats let global_stats = render_global_stats( data.global_stats.downloads, data.global_stats.uploads, data.global_stats.artifacts, data.global_stats.cache_hit_percent, data.global_stats.storage_bytes, lang, ); // Render registry cards let registry_cards: String = data .registry_stats .iter() .map(|r| { let icon = match r.name.as_str() { "docker" => icons::DOCKER, "maven" => icons::MAVEN, "npm" => icons::NPM, "cargo" => icons::CARGO, "pypi" => icons::PYPI, _ => icons::DOCKER, }; let display_name = match r.name.as_str() { "docker" => "Docker", "maven" => "Maven", "npm" => "npm", "cargo" => "Cargo", "pypi" => "PyPI", _ => &r.name, }; render_registry_card( display_name, icon, r.artifact_count, r.downloads, r.uploads, r.size_bytes, &format!("/ui/{}", r.name), &t, ) }) .collect(); // Render mount points let mount_data: Vec<(String, String, Option)> = data .mount_points .iter() .map(|m| { ( m.registry.clone(), m.mount_path.clone(), m.proxy_upstream.clone(), ) }) .collect(); let mount_points = render_mount_points_table(&mount_data, &t); // Render activity log let activity_rows: String = if data.activity.is_empty() { format!( r##"{}"##, t.no_activity ) } else { data.activity .iter() .map(|entry| { let time_ago = format_relative_time(&entry.timestamp); render_activity_row( &time_ago, &entry.action.to_string(), &entry.artifact, &entry.registry, &entry.source, ) }) .collect() }; let activity_log = render_activity_log(&activity_rows, &t); // Format uptime let hours = data.uptime_seconds / 3600; let mins = (data.uptime_seconds % 3600) / 60; let uptime_str = format!("{}h {}m", hours, mins); // Render bragging footer let bragging_footer = render_bragging_footer(lang); let content = format!( r##"

{}

{}

{}
{}
{}
{}
{} {}
{} "##, t.dashboard_title, t.dashboard_subtitle, t.uptime, uptime_str, global_stats, registry_cards, mount_points, activity_log, bragging_footer, ); let polling_script = render_polling_script(); layout_dark( t.dashboard_title, &content, Some("dashboard"), &polling_script, lang, ) } /// Format timestamp as relative time (e.g., "2 min ago") fn format_relative_time(timestamp: &chrono::DateTime) -> String { let now = chrono::Utc::now(); let diff = now.signed_duration_since(*timestamp); if diff.num_seconds() < 60 { "just now".to_string() } else if diff.num_minutes() < 60 { let mins = diff.num_minutes(); format!("{} min{} ago", mins, if mins == 1 { "" } else { "s" }) } else if diff.num_hours() < 24 { let hours = diff.num_hours(); format!("{} hour{} ago", hours, if hours == 1 { "" } else { "s" }) } else { let days = diff.num_days(); format!("{} day{} ago", days, if days == 1 { "" } else { "s" }) } } /// Renders a registry list page (docker, maven, npm, cargo, pypi) pub fn render_registry_list( registry_type: &str, title: &str, repos: &[RepoInfo], lang: Lang, ) -> String { let t = get_translations(lang); let icon = get_registry_icon(registry_type); let table_rows = if repos.is_empty() { format!( r##"
📭
{}
{}
"##, t.no_repos_found, t.push_first_artifact ) } 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" => t.tags, _ => t.versions, }; let content = format!( r##"
{}

{}

{} {}

{}
{} {} {} {}
"##, icon, title, repos.len(), t.repositories, t.search_placeholder, registry_type, t.name, version_label, t.size, t.updated, table_rows ); layout_dark(title, &content, Some(registry_type), "", lang) } /// Renders Docker image detail page pub fn render_docker_detail(name: &str, detail: &DockerDetail, lang: Lang) -> String { let _t = get_translations(lang); 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_dark( &format!("{} - Docker", name), &content, Some("docker"), "", lang, ) } /// Renders package detail page (npm, cargo, pypi) pub fn render_package_detail( registry_type: &str, name: &str, detail: &PackageDetail, lang: Lang, ) -> String { let _t = get_translations(lang); 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_dark( &format!("{} - {}", name, registry_title), &content, Some(registry_type), "", lang, ) } /// Renders Maven artifact detail page pub fn render_maven_detail(path: &str, detail: &MavenDetail, lang: Lang) -> String { let _t = get_translations(lang); 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_dark( &format!("{} - Maven", path), &content, Some("maven"), "", lang, ) } /// 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 }