diff --git a/src/server/mod.rs b/src/server/mod.rs index e587554..50a91fd 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -124,7 +124,7 @@ impl Server { StatusFormat::Svg => views::status_svg(analysis_outcome), StatusFormat::Html => - views::status_html(analysis_outcome, repo_path) + views::html::status::render(analysis_outcome, repo_path) } } diff --git a/src/server/views/html/mod.rs b/src/server/views/html/mod.rs new file mode 100644 index 0000000..ada01ea --- /dev/null +++ b/src/server/views/html/mod.rs @@ -0,0 +1,37 @@ +use std::env; + +use hyper::Response; +use hyper::header::ContentType; +use maud::{Render, html}; + +pub mod status; + +lazy_static! { + static ref SELF_BASE_URL: String = { + env::var("BASE_URL") + .unwrap_or_else(|_| "http://localhost:8080".to_string()) + }; +} + +fn render_html(title: &str, body: B) -> Response { + let rendered = html! { + html { + head { + meta charset="utf-8"; + meta name="viewport" content="width=device-width, initial-scale=1"; + title (title) + link rel="stylesheet" type="text/css" href="/static/style.css"; + link rel="stylesheet" type="text/css" href="https://fonts.googleapis.com/css?family=Fira+Sans:400,500,600"; + link rel="stylesheet" type="text/css" href="https://fonts.googleapis.com/css?family=Source+Code+Pro"; + link rel="stylesheet" type="text/css" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"; + } + body { + (body) + } + } + }; + + Response::new() + .with_header(ContentType::html()) + .with_body(rendered.0) +} diff --git a/src/server/views/html/status.rs b/src/server/views/html/status.rs new file mode 100644 index 0000000..6301977 --- /dev/null +++ b/src/server/views/html/status.rs @@ -0,0 +1,119 @@ +use std::collections::BTreeMap; + +use base64::display::Base64Display; +use hyper::Response; +use maud::{Markup, html}; + +use ::engine::AnalyzeDependenciesOutcome; +use ::models::crates::{CrateName, AnalyzedDependency}; +use ::models::repo::RepoPath; +use ::server::assets; + +fn dependency_table(title: &str, deps: BTreeMap) -> Markup { + let count_total = deps.len(); + let count_outdated = deps.iter().filter(|&(_, dep)| dep.is_outdated()).count(); + + html! { + h3 class="title is-4" (title) + p class="subtitle is-5" { + @if count_outdated > 0 { + (format!(" ({} total, {} up-to-date, {} outdated)", count_total, count_total - count_outdated, count_outdated)) + } @else { + (format!(" ({} total, all up-to-date)", count_total)) + } + } + + table class="table is-fullwidth is-striped is-hoverable" { + thead { + tr { + th "Crate" + th class="has-text-right" "Required" + th class="has-text-right" "Latest" + th class="has-text-right" "Status" + } + } + tbody { + @for (name, dep) in deps { + tr { + td { + a href=(format!("https://crates.io/crates/{}", name.as_ref())) (name.as_ref()) + } + td class="has-text-right" code (dep.required.to_string()) + td class="has-text-right" { + @if let Some(ref latest) = dep.latest { + code (latest.to_string()) + } @else { + "N/A" + } + } + td class="has-text-right" { + @if dep.is_outdated() { + span class="tag is-warning" "out of date" + } @else { + span class="tag is-success" "up to date" + } + } + } + } + } + } + } +} + +pub fn render(analysis_outcome: AnalyzeDependenciesOutcome, repo_path: RepoPath) -> Response { + let self_path = format!("repo/{}/{}/{}", repo_path.site.as_ref(), repo_path.qual.as_ref(), repo_path.name.as_ref()); + let status_base_url = format!("{}/{}", &super::SELF_BASE_URL as &str, self_path); + let title = format!("{} / {} - Dependency Status", repo_path.qual.as_ref(), repo_path.name.as_ref()); + + let (hero_class, status_asset) = if analysis_outcome.deps.any_outdated() { + ("is-warning", assets::BADGE_OUTDATED_SVG.as_ref()) + } else { + ("is-success", assets::BADGE_UPTODATE_SVG.as_ref()) + }; + + let status_data_url = format!("data:image/svg+xml;base64,{}", Base64Display::standard(status_asset)); + + super::render_html(&title, html! { + section class=(format!("hero {}", hero_class)) { + div class="hero-body" { + div class="container" { + h1 class="title is-1" { + a href=(format!("{}/{}/{}", repo_path.site.to_base_uri(), repo_path.qual.as_ref(), repo_path.name.as_ref())) { + i class="fa fa-github" "" + (format!(" {} / {}", repo_path.qual.as_ref(), repo_path.name.as_ref())) + } + } + + img src=(status_data_url); + } + } + div class="hero-footer" { + div class="container" { + pre class="is-size-7" { + (format!("[![dependency status]({}/status.svg)]({})", status_base_url, status_base_url)) + } + } + } + } + section class="section" { + div class="container" { + h2 class="title is-3" { + "Crate " + code (analysis_outcome.name.as_ref()) + } + + @if !analysis_outcome.deps.main.is_empty() { + (dependency_table("Dependencies", analysis_outcome.deps.main)) + } + + @if !analysis_outcome.deps.dev.is_empty() { + (dependency_table("Dev dependencies", analysis_outcome.deps.dev)) + } + + @if !analysis_outcome.deps.build.is_empty() { + (dependency_table("Build dependencies", analysis_outcome.deps.build)) + } + } + } + }) +} diff --git a/src/server/views/mod.rs b/src/server/views/mod.rs index 031ca34..e965c6d 100644 --- a/src/server/views/mod.rs +++ b/src/server/views/mod.rs @@ -1,5 +1,4 @@ -mod status_html; -pub use self::status_html::status_html; +pub mod html; mod status_json; pub use self::status_json::status_json;