mirror of
https://github.com/deps-rs/deps.rs.git
synced 2024-11-22 10:26:30 +00:00
html frontend
This commit is contained in:
parent
21cd986d5e
commit
d821851fd8
12 changed files with 411 additions and 176 deletions
|
@ -7,6 +7,7 @@ authors = ["Sam Rijs <srijs@airpost.net>"]
|
||||||
futures = "0.1.18"
|
futures = "0.1.18"
|
||||||
hyper = "0.11.15"
|
hyper = "0.11.15"
|
||||||
hyper-tls = "0.1.2"
|
hyper-tls = "0.1.2"
|
||||||
|
maud = "0.17.2"
|
||||||
route-recognizer = "0.1.12"
|
route-recognizer = "0.1.12"
|
||||||
semver = { version = "0.9.0", features = ["serde"] }
|
semver = { version = "0.9.0", features = ["serde"] }
|
||||||
serde = "1.0.27"
|
serde = "1.0.27"
|
||||||
|
|
70
assets/static/style.css
Normal file
70
assets/static/style.css
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
@import url('https://fonts.googleapis.com/css?family=Fira+Sans:400,500,600,700');
|
||||||
|
@import url('https://fonts.googleapis.com/css?family=Source+Code+Pro:400,600');
|
||||||
|
|
||||||
|
html {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
background: #eee;
|
||||||
|
font-family: "Fira Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
padding: 30px 40px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background: #fff;
|
||||||
|
border-left: 5px solid #aaa;
|
||||||
|
border-right: 5px solid #aaa;
|
||||||
|
width: 800px;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
font-family: "Source Code Pro", Menlo, Monaco, Consolas, "DejaVu Sans Mono", Inconsolata, monospace;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
background: #dedede;
|
||||||
|
color: #555;
|
||||||
|
padding: .5em .3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-family: "Source Code Pro", Menlo, Monaco, Consolas, "DejaVu Sans Mono", Inconsolata, monospace;
|
||||||
|
background: #dedede;
|
||||||
|
padding: .1em .3em;
|
||||||
|
border-radius: .3em;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 code {
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border: 0;
|
||||||
|
border-spacing: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr:nth-child(even) {
|
||||||
|
background: #f3f3f3;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr:nth-child(odd) {
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
padding: .5em .7em;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.status {
|
||||||
|
padding: .1em .3em;
|
||||||
|
border-radius: .3em;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.status.up-to-date {
|
||||||
|
background: #97ca00;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.status.outdated {
|
||||||
|
background: #dfb317;
|
||||||
|
}
|
167
src/api.rs
167
src/api.rs
|
@ -1,167 +0,0 @@
|
||||||
use std::collections::BTreeMap;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use futures::{Future, IntoFuture, future};
|
|
||||||
use hyper::{Error as HyperError, Method, Request, Response, StatusCode};
|
|
||||||
use hyper::header::ContentType;
|
|
||||||
use route_recognizer::{Params, Router};
|
|
||||||
use semver::{Version, VersionReq};
|
|
||||||
use serde_json;
|
|
||||||
use slog::Logger;
|
|
||||||
use tokio_service::Service;
|
|
||||||
|
|
||||||
use ::assets;
|
|
||||||
use ::engine::{Engine, AnalyzeDependenciesOutcome};
|
|
||||||
use ::models::repo::RepoPath;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
enum StatusFormat {
|
|
||||||
Json,
|
|
||||||
Svg
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Route {
|
|
||||||
Status(StatusFormat)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Api {
|
|
||||||
engine: Engine,
|
|
||||||
router: Arc<Router<Route>>
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Api {
|
|
||||||
pub fn new(engine: Engine) -> Api {
|
|
||||||
let mut router = Router::new();
|
|
||||||
router.add("/repo/:site/:qual/:name/status.json", Route::Status(StatusFormat::Json));
|
|
||||||
router.add("/repo/:site/:qual/:name/status.svg", Route::Status(StatusFormat::Svg));
|
|
||||||
|
|
||||||
Api { engine, router: Arc::new(router) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
|
||||||
struct AnalyzeDependenciesResponseDetail {
|
|
||||||
required: VersionReq,
|
|
||||||
latest: Option<Version>,
|
|
||||||
outdated: bool
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
|
||||||
struct AnalyzeDependenciesResponseSingle {
|
|
||||||
dependencies: BTreeMap<String, AnalyzeDependenciesResponseDetail>,
|
|
||||||
#[serde(rename="dev-dependencies")]
|
|
||||||
dev_dependencies: BTreeMap<String, AnalyzeDependenciesResponseDetail>,
|
|
||||||
#[serde(rename="build-dependencies")]
|
|
||||||
build_dependencies: BTreeMap<String, AnalyzeDependenciesResponseDetail>
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
|
||||||
struct AnalyzeDependenciesResponse {
|
|
||||||
crates: BTreeMap<String, AnalyzeDependenciesResponseSingle>
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Service for Api {
|
|
||||||
type Request = Request;
|
|
||||||
type Response = Response;
|
|
||||||
type Error = HyperError;
|
|
||||||
type Future = Box<Future<Item=Response, Error=HyperError>>;
|
|
||||||
|
|
||||||
fn call(&self, req: Request) -> Self::Future {
|
|
||||||
if let Ok(route_match) = self.router.recognize(req.uri().path()) {
|
|
||||||
match route_match.handler {
|
|
||||||
&Route::Status(format) => {
|
|
||||||
if *req.method() == Method::Get {
|
|
||||||
return Box::new(self.status(req, route_match.params, format));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut response = Response::new();
|
|
||||||
response.set_status(StatusCode::NotFound);
|
|
||||||
Box::new(future::ok(response))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Api {
|
|
||||||
fn status(&self, _req: Request, params: Params, format: StatusFormat) ->
|
|
||||||
impl Future<Item=Response, Error=HyperError>
|
|
||||||
{
|
|
||||||
let engine = self.engine.clone();
|
|
||||||
|
|
||||||
let site = params.find("site").expect("route param 'site' not found");
|
|
||||||
let qual = params.find("qual").expect("route param 'qual' not found");
|
|
||||||
let name = params.find("name").expect("route param 'name' not found");
|
|
||||||
|
|
||||||
RepoPath::from_parts(site, qual, name).into_future().then(move |repo_path_result| {
|
|
||||||
match repo_path_result {
|
|
||||||
Err(err) => {
|
|
||||||
let mut response = Response::new();
|
|
||||||
response.set_status(StatusCode::BadRequest);
|
|
||||||
response.set_body(format!("{:?}", err));
|
|
||||||
future::Either::A(future::ok(response))
|
|
||||||
},
|
|
||||||
Ok(repo_path) => {
|
|
||||||
future::Either::B(engine.analyze_dependencies(repo_path).then(move |analyze_result| {
|
|
||||||
match analyze_result {
|
|
||||||
Err(err) => {
|
|
||||||
let mut response = Response::new();
|
|
||||||
response.set_status(StatusCode::InternalServerError);
|
|
||||||
response.set_body(format!("{:?}", err));
|
|
||||||
future::Either::A(future::ok(response))
|
|
||||||
},
|
|
||||||
Ok(analysis_outcome) => {
|
|
||||||
let response = Api::status_format_analysis(analysis_outcome, format);
|
|
||||||
future::Either::B(future::ok(response))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn status_format_analysis(analysis_outcome: AnalyzeDependenciesOutcome, format: StatusFormat) -> Response {
|
|
||||||
match format {
|
|
||||||
StatusFormat::Json => {
|
|
||||||
let single = AnalyzeDependenciesResponseSingle {
|
|
||||||
dependencies: analysis_outcome.deps.main.into_iter()
|
|
||||||
.map(|(name, analyzed)| (name.into(), AnalyzeDependenciesResponseDetail {
|
|
||||||
outdated: analyzed.is_outdated(),
|
|
||||||
required: analyzed.required,
|
|
||||||
latest: analyzed.latest
|
|
||||||
})).collect(),
|
|
||||||
dev_dependencies: analysis_outcome.deps.dev.into_iter()
|
|
||||||
.map(|(name, analyzed)| (name.into(), AnalyzeDependenciesResponseDetail {
|
|
||||||
outdated: analyzed.is_outdated(),
|
|
||||||
required: analyzed.required,
|
|
||||||
latest: analyzed.latest
|
|
||||||
})).collect(),
|
|
||||||
build_dependencies: analysis_outcome.deps.build.into_iter()
|
|
||||||
.map(|(name, analyzed)| (name.into(), AnalyzeDependenciesResponseDetail {
|
|
||||||
outdated: analyzed.is_outdated(),
|
|
||||||
required: analyzed.required,
|
|
||||||
latest: analyzed.latest
|
|
||||||
})).collect()
|
|
||||||
};
|
|
||||||
let multi = AnalyzeDependenciesResponse {
|
|
||||||
crates: vec![(analysis_outcome.name.into(), single)].into_iter().collect()
|
|
||||||
};
|
|
||||||
Response::new()
|
|
||||||
.with_header(ContentType::json())
|
|
||||||
.with_body(serde_json::to_string(&multi).unwrap())
|
|
||||||
},
|
|
||||||
StatusFormat::Svg => {
|
|
||||||
let mut response = Response::new()
|
|
||||||
.with_header(ContentType("image/svg+xml;charset=utf-8".parse().unwrap()));
|
|
||||||
if analysis_outcome.deps.any_outdated() {
|
|
||||||
response.set_body(assets::BADGE_OUTDATED_SVG.to_vec());
|
|
||||||
} else {
|
|
||||||
response.set_body(assets::BADGE_UPTODATE_SVG.to_vec());
|
|
||||||
}
|
|
||||||
response
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
pub static BADGE_UPTODATE_SVG: &'static [u8; 978] =
|
|
||||||
include_bytes!("../assets/badges/up-to-date.svg");
|
|
||||||
pub static BADGE_OUTDATED_SVG: &'static [u8; 974] =
|
|
||||||
include_bytes!("../assets/badges/outdated.svg");
|
|
11
src/main.rs
11
src/main.rs
|
@ -1,9 +1,11 @@
|
||||||
#![feature(ascii_ctype)]
|
#![feature(ascii_ctype)]
|
||||||
#![feature(conservative_impl_trait)]
|
#![feature(conservative_impl_trait)]
|
||||||
|
#![feature(proc_macro)]
|
||||||
|
|
||||||
extern crate futures;
|
extern crate futures;
|
||||||
extern crate hyper;
|
extern crate hyper;
|
||||||
extern crate hyper_tls;
|
extern crate hyper_tls;
|
||||||
|
extern crate maud;
|
||||||
extern crate route_recognizer;
|
extern crate route_recognizer;
|
||||||
extern crate semver;
|
extern crate semver;
|
||||||
#[macro_use] extern crate serde_derive;
|
#[macro_use] extern crate serde_derive;
|
||||||
|
@ -19,8 +21,7 @@ mod models;
|
||||||
mod parsers;
|
mod parsers;
|
||||||
mod interactors;
|
mod interactors;
|
||||||
mod engine;
|
mod engine;
|
||||||
mod assets;
|
mod server;
|
||||||
mod api;
|
|
||||||
|
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
@ -32,7 +33,7 @@ use hyper_tls::HttpsConnector;
|
||||||
use slog::Drain;
|
use slog::Drain;
|
||||||
use tokio_core::reactor::Core;
|
use tokio_core::reactor::Core;
|
||||||
|
|
||||||
use self::api::Api;
|
use self::server::Server;
|
||||||
use self::engine::Engine;
|
use self::engine::Engine;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
@ -63,9 +64,9 @@ fn main() {
|
||||||
logger: logger.clone()
|
logger: logger.clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
let api = Api::new(engine);
|
let server = Server::new(engine);
|
||||||
|
|
||||||
let serve = http.serve_addr_handle(&addr, &handle, move || Ok(api.clone()))
|
let serve = http.serve_addr_handle(&addr, &handle, move || Ok(server.clone()))
|
||||||
.expect("failed to bind server");
|
.expect("failed to bind server");
|
||||||
|
|
||||||
let serving = serve.for_each(move |conn| {
|
let serving = serve.for_each(move |conn| {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct RepoPath {
|
pub struct RepoPath {
|
||||||
pub site: RepoSite,
|
pub site: RepoSite,
|
||||||
pub qual: RepoQualifier,
|
pub qual: RepoQualifier,
|
||||||
|
@ -19,6 +20,7 @@ impl RepoPath {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct RepoValidationError;
|
pub struct RepoValidationError;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct RepoSite(String);
|
pub struct RepoSite(String);
|
||||||
|
|
||||||
impl FromStr for RepoSite {
|
impl FromStr for RepoSite {
|
||||||
|
@ -43,6 +45,7 @@ impl AsRef<str> for RepoSite {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct RepoQualifier(String);
|
pub struct RepoQualifier(String);
|
||||||
|
|
||||||
impl FromStr for RepoQualifier {
|
impl FromStr for RepoQualifier {
|
||||||
|
@ -67,6 +70,7 @@ impl AsRef<str> for RepoQualifier {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct RepoName(String);
|
pub struct RepoName(String);
|
||||||
|
|
||||||
impl FromStr for RepoName {
|
impl FromStr for RepoName {
|
||||||
|
|
9
src/server/assets.rs
Normal file
9
src/server/assets.rs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
//pub mod templates;
|
||||||
|
|
||||||
|
pub static BADGE_UPTODATE_SVG: &'static [u8; 978] =
|
||||||
|
include_bytes!("../../assets/badges/up-to-date.svg");
|
||||||
|
pub static BADGE_OUTDATED_SVG: &'static [u8; 974] =
|
||||||
|
include_bytes!("../../assets/badges/outdated.svg");
|
||||||
|
|
||||||
|
pub static STATIC_STYLE_CSS: &'static str =
|
||||||
|
include_str!("../../assets/static/style.css");
|
140
src/server/mod.rs
Normal file
140
src/server/mod.rs
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use futures::{Future, IntoFuture, future};
|
||||||
|
use hyper::{Error as HyperError, Method, Request, Response, StatusCode};
|
||||||
|
use hyper::header::ContentType;
|
||||||
|
use route_recognizer::{Params, Router};
|
||||||
|
use slog::Logger;
|
||||||
|
use tokio_service::Service;
|
||||||
|
|
||||||
|
mod assets;
|
||||||
|
mod views;
|
||||||
|
|
||||||
|
use ::engine::{Engine, AnalyzeDependenciesOutcome};
|
||||||
|
use ::models::repo::RepoPath;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
enum StatusFormat {
|
||||||
|
Html,
|
||||||
|
Json,
|
||||||
|
Svg
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
enum StaticFile {
|
||||||
|
StyleCss
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Route {
|
||||||
|
Static(StaticFile),
|
||||||
|
Status(StatusFormat)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Server {
|
||||||
|
engine: Engine,
|
||||||
|
router: Arc<Router<Route>>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Server {
|
||||||
|
pub fn new(engine: Engine) -> Server {
|
||||||
|
let mut router = Router::new();
|
||||||
|
|
||||||
|
router.add("/static/style.css", Route::Static(StaticFile::StyleCss));
|
||||||
|
|
||||||
|
router.add("/repo/:site/:qual/:name", Route::Status(StatusFormat::Html));
|
||||||
|
router.add("/repo/:site/:qual/:name/status.json", Route::Status(StatusFormat::Json));
|
||||||
|
router.add("/repo/:site/:qual/:name/status.svg", Route::Status(StatusFormat::Svg));
|
||||||
|
|
||||||
|
Server { engine, router: Arc::new(router) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Service for Server {
|
||||||
|
type Request = Request;
|
||||||
|
type Response = Response;
|
||||||
|
type Error = HyperError;
|
||||||
|
type Future = Box<Future<Item=Response, Error=HyperError>>;
|
||||||
|
|
||||||
|
fn call(&self, req: Request) -> Self::Future {
|
||||||
|
if let Ok(route_match) = self.router.recognize(req.uri().path()) {
|
||||||
|
match route_match.handler {
|
||||||
|
&Route::Status(format) => {
|
||||||
|
if *req.method() == Method::Get {
|
||||||
|
return Box::new(self.status(req, route_match.params, format));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
&Route::Static(file) => {
|
||||||
|
if *req.method() == Method::Get {
|
||||||
|
return Box::new(future::ok(Server::static_file(file)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut response = Response::new();
|
||||||
|
response.set_status(StatusCode::NotFound);
|
||||||
|
Box::new(future::ok(response))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Server {
|
||||||
|
fn status(&self, _req: Request, params: Params, format: StatusFormat) ->
|
||||||
|
impl Future<Item=Response, Error=HyperError>
|
||||||
|
{
|
||||||
|
let server = self.clone();
|
||||||
|
|
||||||
|
let site = params.find("site").expect("route param 'site' not found");
|
||||||
|
let qual = params.find("qual").expect("route param 'qual' not found");
|
||||||
|
let name = params.find("name").expect("route param 'name' not found");
|
||||||
|
|
||||||
|
RepoPath::from_parts(site, qual, name).into_future().then(move |repo_path_result| {
|
||||||
|
match repo_path_result {
|
||||||
|
Err(err) => {
|
||||||
|
let mut response = Response::new();
|
||||||
|
response.set_status(StatusCode::BadRequest);
|
||||||
|
response.set_body(format!("{:?}", err));
|
||||||
|
future::Either::A(future::ok(response))
|
||||||
|
},
|
||||||
|
Ok(repo_path) => {
|
||||||
|
future::Either::B(server.engine.analyze_dependencies(repo_path.clone()).then(move |analyze_result| {
|
||||||
|
match analyze_result {
|
||||||
|
Err(err) => {
|
||||||
|
let mut response = Response::new();
|
||||||
|
response.set_status(StatusCode::InternalServerError);
|
||||||
|
response.set_body(format!("{:?}", err));
|
||||||
|
future::Either::A(future::ok(response))
|
||||||
|
},
|
||||||
|
Ok(analysis_outcome) => {
|
||||||
|
let response = Server::status_format_analysis(analysis_outcome, format, repo_path);
|
||||||
|
future::Either::B(future::ok(response))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn status_format_analysis(analysis_outcome: AnalyzeDependenciesOutcome, format: StatusFormat, repo_path: RepoPath) -> Response {
|
||||||
|
match format {
|
||||||
|
StatusFormat::Json =>
|
||||||
|
views::status_json(analysis_outcome),
|
||||||
|
StatusFormat::Svg =>
|
||||||
|
views::status_svg(analysis_outcome),
|
||||||
|
StatusFormat::Html =>
|
||||||
|
views::status_html(analysis_outcome, repo_path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn static_file(file: StaticFile) -> Response {
|
||||||
|
match file {
|
||||||
|
StaticFile::StyleCss => {
|
||||||
|
Response::new()
|
||||||
|
.with_header(ContentType("text/css".parse().unwrap()))
|
||||||
|
.with_body(assets::STATIC_STYLE_CSS)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
8
src/server/views/mod.rs
Normal file
8
src/server/views/mod.rs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
mod status_html;
|
||||||
|
pub use self::status_html::status_html;
|
||||||
|
|
||||||
|
mod status_json;
|
||||||
|
pub use self::status_json::status_json;
|
||||||
|
|
||||||
|
mod status_svg;
|
||||||
|
pub use self::status_svg::status_svg;
|
97
src/server/views/status_html.rs
Normal file
97
src/server/views/status_html.rs
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
use hyper::Response;
|
||||||
|
use hyper::header::ContentType;
|
||||||
|
use maud::{Markup, html};
|
||||||
|
|
||||||
|
use ::engine::AnalyzeDependenciesOutcome;
|
||||||
|
use ::models::crates::{CrateName, AnalyzedDependency};
|
||||||
|
use ::models::repo::RepoPath;
|
||||||
|
|
||||||
|
const SELF_BASE_URL: &'static str = "http://example.com";
|
||||||
|
|
||||||
|
fn dependency_table<I: IntoIterator<Item=(CrateName, AnalyzedDependency)>>(deps: I) -> Markup {
|
||||||
|
html! {
|
||||||
|
table {
|
||||||
|
tr {
|
||||||
|
th "Crate"
|
||||||
|
th "Required"
|
||||||
|
th "Latest"
|
||||||
|
th "Status"
|
||||||
|
}
|
||||||
|
@for (name, dep) in deps {
|
||||||
|
tr {
|
||||||
|
td {
|
||||||
|
a href=(format!("https://crates.io/crates/{}", name.as_ref())) (name.as_ref())
|
||||||
|
}
|
||||||
|
td code (dep.required.to_string())
|
||||||
|
td {
|
||||||
|
@if let Some(ref latest) = dep.latest {
|
||||||
|
code (latest.to_string())
|
||||||
|
} @else {
|
||||||
|
"N/A"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
td {
|
||||||
|
@if dep.is_outdated() {
|
||||||
|
span class="status outdated" "out of date"
|
||||||
|
} @else {
|
||||||
|
span class="status up-to-date" "up to date"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn status_html(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!("{}/{}", SELF_BASE_URL, self_path);
|
||||||
|
let title = format!("{} / {} - Dependency Status", repo_path.qual.as_ref(), repo_path.name.as_ref());
|
||||||
|
|
||||||
|
let rendered = html! {
|
||||||
|
html {
|
||||||
|
head {
|
||||||
|
title (title)
|
||||||
|
link rel="stylesheet" type="text/css" href="/static/style.css";
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
header {
|
||||||
|
h1 {
|
||||||
|
"Dependency status for "
|
||||||
|
code (format!("{}/{}", repo_path.qual.as_ref(), repo_path.name.as_ref()))
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
"Crate "
|
||||||
|
code (analysis_outcome.name.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
|
img src=(format!("/{}/status.svg", self_path));
|
||||||
|
|
||||||
|
pre {
|
||||||
|
(format!("[![dependency status]({}/status.svg)]({})", status_base_url, status_base_url))
|
||||||
|
}
|
||||||
|
|
||||||
|
@if !analysis_outcome.deps.main.is_empty() {
|
||||||
|
h3 "Dependencies"
|
||||||
|
(dependency_table(analysis_outcome.deps.main))
|
||||||
|
}
|
||||||
|
|
||||||
|
@if !analysis_outcome.deps.dev.is_empty() {
|
||||||
|
h3 "Dev dependencies"
|
||||||
|
(dependency_table(analysis_outcome.deps.dev))
|
||||||
|
}
|
||||||
|
|
||||||
|
@if !analysis_outcome.deps.build.is_empty() {
|
||||||
|
h3 "Build dependencies"
|
||||||
|
(dependency_table(analysis_outcome.deps.build))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Response::new()
|
||||||
|
.with_header(ContentType::html())
|
||||||
|
.with_body(rendered.0)
|
||||||
|
}
|
60
src/server/views/status_json.rs
Normal file
60
src/server/views/status_json.rs
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
use hyper::Response;
|
||||||
|
use hyper::header::ContentType;
|
||||||
|
use semver::{Version, VersionReq};
|
||||||
|
use serde_json;
|
||||||
|
|
||||||
|
use ::engine::AnalyzeDependenciesOutcome;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
struct AnalyzeDependenciesResponseDetail {
|
||||||
|
required: VersionReq,
|
||||||
|
latest: Option<Version>,
|
||||||
|
outdated: bool
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
struct AnalyzeDependenciesResponseSingle {
|
||||||
|
dependencies: BTreeMap<String, AnalyzeDependenciesResponseDetail>,
|
||||||
|
#[serde(rename="dev-dependencies")]
|
||||||
|
dev_dependencies: BTreeMap<String, AnalyzeDependenciesResponseDetail>,
|
||||||
|
#[serde(rename="build-dependencies")]
|
||||||
|
build_dependencies: BTreeMap<String, AnalyzeDependenciesResponseDetail>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
struct AnalyzeDependenciesResponse {
|
||||||
|
crates: BTreeMap<String, AnalyzeDependenciesResponseSingle>
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn status_json(analysis_outcome: AnalyzeDependenciesOutcome) -> Response {
|
||||||
|
let single = AnalyzeDependenciesResponseSingle {
|
||||||
|
dependencies: analysis_outcome.deps.main.into_iter()
|
||||||
|
.map(|(name, analyzed)| (name.into(), AnalyzeDependenciesResponseDetail {
|
||||||
|
outdated: analyzed.is_outdated(),
|
||||||
|
required: analyzed.required,
|
||||||
|
latest: analyzed.latest
|
||||||
|
})).collect(),
|
||||||
|
dev_dependencies: analysis_outcome.deps.dev.into_iter()
|
||||||
|
.map(|(name, analyzed)| (name.into(), AnalyzeDependenciesResponseDetail {
|
||||||
|
outdated: analyzed.is_outdated(),
|
||||||
|
required: analyzed.required,
|
||||||
|
latest: analyzed.latest
|
||||||
|
})).collect(),
|
||||||
|
build_dependencies: analysis_outcome.deps.build.into_iter()
|
||||||
|
.map(|(name, analyzed)| (name.into(), AnalyzeDependenciesResponseDetail {
|
||||||
|
outdated: analyzed.is_outdated(),
|
||||||
|
required: analyzed.required,
|
||||||
|
latest: analyzed.latest
|
||||||
|
})).collect()
|
||||||
|
};
|
||||||
|
|
||||||
|
let multi = AnalyzeDependenciesResponse {
|
||||||
|
crates: vec![(analysis_outcome.name.into(), single)].into_iter().collect()
|
||||||
|
};
|
||||||
|
|
||||||
|
Response::new()
|
||||||
|
.with_header(ContentType::json())
|
||||||
|
.with_body(serde_json::to_string(&multi).unwrap())
|
||||||
|
}
|
16
src/server/views/status_svg.rs
Normal file
16
src/server/views/status_svg.rs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
use hyper::Response;
|
||||||
|
use hyper::header::ContentType;
|
||||||
|
|
||||||
|
use ::server::assets;
|
||||||
|
use ::engine::AnalyzeDependenciesOutcome;
|
||||||
|
|
||||||
|
pub fn status_svg(analysis_outcome: AnalyzeDependenciesOutcome) -> Response {
|
||||||
|
let mut response = Response::new()
|
||||||
|
.with_header(ContentType("image/svg+xml;charset=utf-8".parse().unwrap()));
|
||||||
|
if analysis_outcome.deps.any_outdated() {
|
||||||
|
response.set_body(assets::BADGE_OUTDATED_SVG.to_vec());
|
||||||
|
} else {
|
||||||
|
response.set_body(assets::BADGE_UPTODATE_SVG.to_vec());
|
||||||
|
}
|
||||||
|
response
|
||||||
|
}
|
Loading…
Reference in a new issue