Improve command line and web interface

This commit is contained in:
Lucien Greathouse
2019-09-23 17:54:04 -07:00
parent 5a4189a770
commit 5630cea9a0
8 changed files with 168 additions and 42 deletions

8
Cargo.lock generated
View File

@@ -376,7 +376,7 @@ version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", "atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
"humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"termcolor 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "termcolor 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -578,7 +578,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "humantime" name = "humantime"
version = "1.2.0" version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1458,6 +1458,7 @@ dependencies = [
"env_logger 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)",
"humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"hyper 0.12.33 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.12.33 (registry+https://github.com/rust-lang/crates.io-index)",
"jod-thread 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "jod-thread 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1476,6 +1477,7 @@ dependencies = [
"serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)",
"tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"termcolor 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
"uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", "uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)",
"walkdir 2.2.9 (registry+https://github.com/rust-lang/crates.io-index)", "walkdir 2.2.9 (registry+https://github.com/rust-lang/crates.io-index)",
"winreg 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", "winreg 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2194,7 +2196,7 @@ dependencies = [
"checksum http 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)" = "372bcb56f939e449117fb0869c2e8fd8753a8223d92a172c6e808cf123a5b6e4" "checksum http 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)" = "372bcb56f939e449117fb0869c2e8fd8753a8223d92a172c6e808cf123a5b6e4"
"checksum http-body 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6741c859c1b2463a423a1dbce98d418e6c3c3fc720fb0d45528657320920292d" "checksum http-body 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6741c859c1b2463a423a1dbce98d418e6c3c3fc720fb0d45528657320920292d"
"checksum httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" "checksum httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9"
"checksum humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca7e5f2e110db35f93b837c81797f3714500b81d517bf20c431b16d3ca4f114" "checksum humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
"checksum hyper 0.12.33 (registry+https://github.com/rust-lang/crates.io-index)" = "7cb44cbce9d8ee4fb36e4c0ad7b794ac44ebaad924b9c8291a63215bb44c2c8f" "checksum hyper 0.12.33 (registry+https://github.com/rust-lang/crates.io-index)" = "7cb44cbce9d8ee4fb36e4c0ad7b794ac44ebaad924b9c8291a63215bb44c2c8f"
"checksum hyper-tls 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3a800d6aa50af4b5850b2b0f659625ce9504df908e9733b635720483be26174f" "checksum hyper-tls 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3a800d6aa50af4b5850b2b0f659625ce9504df908e9733b635720483be26174f"
"checksum idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" "checksum idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e"

View File

@@ -37,8 +37,8 @@ csv = "1.0"
env_logger = "0.6" env_logger = "0.6"
failure = "0.1.3" failure = "0.1.3"
futures = "0.1" futures = "0.1"
humantime = "1.3.0"
hyper = "0.12" hyper = "0.12"
reqwest = "0.9.20"
jod-thread = "0.1.0" jod-thread = "0.1.0"
log = "0.4" log = "0.4"
maplit = "1.0.1" maplit = "1.0.1"
@@ -48,9 +48,11 @@ rbx_dom_weak = "1.9.0"
rbx_reflection = "3.1.388" rbx_reflection = "3.1.388"
rbx_xml = "0.11.0" rbx_xml = "0.11.0"
regex = "1.0" regex = "1.0"
reqwest = "0.9.20"
ritz = "0.1.0" ritz = "0.1.0"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
termcolor = "1.0.5"
uuid = { version = "0.7", features = ["v4", "serde"] } uuid = { version = "0.7", features = ["v4", "serde"] }
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]

BIN
assets/icon-32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 975 B

View File

@@ -1,11 +1,17 @@
* { * {
margin: 0; margin: 0;
padding: 0; padding: 0;
border: none;
text-decoration: inherit;
color: inherit;
font: inherit; font: inherit;
box-sizing: inherit;
} }
html { html {
box-sizing: border-box;
font-family: sans-serif; font-family: sans-serif;
text-decoration: none;
height: 100%; height: 100%;
} }
@@ -17,27 +23,54 @@ body {
} }
.main { .main {
flex: 0 0;
display: flex;
flex-direction: column;
padding: 1rem; padding: 1rem;
text-align: center;
margin: 0 auto; margin: 0 auto;
width: 100%; width: 100%;
max-width: 60rem; max-width: 50rem;
background-color: #efefef; background-color: #efefef;
border: 1px solid #666; border: 1px solid #666;
border-radius: 4px; border-radius: 4px;
} }
.title { .header {
font-size: 2rem; flex: 0 0;
display: flex;
justify-content: space-around;
align-items: center;
margin-bottom: 1rem;
}
.main-logo {
flex: 0 0;
width: 20rem;
}
.stats {
flex: 0 0 20rem;
padding: 0 1rem;
}
.stat {
display: block;
}
.stat-name {
display: inline;
font-weight: bold; font-weight: bold;
} }
.subtitle { .button-list {
font-size: 1.5rem; flex: 0 0;
font-weight: bold; display: flex;
justify-content: space-around;
} }
.docs { .button {
font-size: 1.3rem; display: inline-block;
font-weight: bold; border: 1px solid #666;
padding: 0.3em 1em;
margin: 0 0.2rem;
} }

View File

@@ -1,7 +1,13 @@
use std::{collections::HashMap, path::PathBuf, sync::Arc}; use std::{
collections::HashMap,
io::{self, Write},
path::PathBuf,
sync::Arc,
};
use failure::Fail; use failure::Fail;
use rbx_dom_weak::RbxInstanceProperties; use rbx_dom_weak::RbxInstanceProperties;
use termcolor::{BufferWriter, Color, ColorChoice, ColorSpec, WriteColor};
use crate::{ use crate::{
imfs::{Imfs, RealFetcher, WatchMode}, imfs::{Imfs, RealFetcher, WatchMode},
@@ -46,7 +52,7 @@ pub fn serve(options: &ServeOptions) -> Result<(), ServeError> {
}) })
.unwrap_or(DEFAULT_PORT); .unwrap_or(DEFAULT_PORT);
println!("Rojo server listening on port {}", port); let _ = show_start_message(port);
let mut tree = RojoTree::new(InstancePropertiesWithMeta { let mut tree = RojoTree::new(InstancePropertiesWithMeta {
properties: RbxInstanceProperties { properties: RbxInstanceProperties {
@@ -75,20 +81,36 @@ pub fn serve(options: &ServeOptions) -> Result<(), ServeError> {
server.start(port); server.start(port);
// let receiver = imfs.change_receiver(); Ok(())
}
// while let Ok(change) = receiver.recv() { fn show_start_message(port: u16) -> io::Result<()> {
// imfs.commit_change(&change) let mut writer = BufferWriter::stderr(ColorChoice::Auto);
// .expect("Failed to commit Imfs change"); let mut buffer = writer.buffer();
// use notify::DebouncedEvent; writeln!(&mut buffer, "Rojo server listening:")?;
// if let DebouncedEvent::Write(path) = change {
// let contents = imfs.get_contents(path)
// .expect("Failed to read changed path");
// println!("{:?}", std::str::from_utf8(contents)); write!(&mut buffer, " Address: ")?;
// } buffer.set_color(ColorSpec::new().set_fg(Some(Color::Green)).set_bold(true))?;
// } writeln!(&mut buffer, "localhost")?;
buffer.set_color(&ColorSpec::new())?;
write!(&mut buffer, " Port: ")?;
buffer.set_color(ColorSpec::new().set_fg(Some(Color::Green)).set_bold(true))?;
writeln!(&mut buffer, "{}", port)?;
writeln!(&mut buffer, "")?;
buffer.set_color(&ColorSpec::new())?;
write!(&mut buffer, "Visit ")?;
buffer.set_color(ColorSpec::new().set_fg(Some(Color::Green)).set_bold(true))?;
write!(&mut buffer, "http://localhost:{}/", port)?;
buffer.set_color(&ColorSpec::new())?;
writeln!(&mut buffer, " in your browser for more information.")?;
writer.print(&buffer)?;
Ok(()) Ok(())
} }

View File

@@ -1,6 +1,6 @@
// Recursion limit bump is to support Ritz, a JSX-like proc macro used for // Recursion limit bump is to support Ritz, a JSX-like proc macro used for
// Rojo's web UI currently. // Rojo's web UI currently.
#![recursion_limit = "128"] #![recursion_limit = "1024"]
#[macro_use] #[macro_use]
mod impl_from; mod impl_from;

View File

@@ -1,6 +1,7 @@
use std::{ use std::{
collections::HashSet, collections::HashSet,
sync::{Arc, Mutex, MutexGuard}, sync::{Arc, Mutex, MutexGuard},
time::Instant,
}; };
use crate::{ use crate::{
@@ -20,6 +21,10 @@ use crate::{
/// future. `ServeSession` would be roughly the right interface to expose for /// future. `ServeSession` would be roughly the right interface to expose for
/// those cases. /// those cases.
pub struct ServeSession<F> { pub struct ServeSession<F> {
/// When the serve session was started. Used only for user-facing
/// diagnostics.
start_time: Instant,
/// The root project for the serve session, if there was one defined. /// The root project for the serve session, if there was one defined.
/// ///
/// This will be defined if a folder with a `default.project.json` file was /// This will be defined if a folder with a `default.project.json` file was
@@ -66,6 +71,8 @@ pub struct ServeSession<F> {
/// that handles ServeSession. /// that handles ServeSession.
impl<F: ImfsFetcher + Send + 'static> ServeSession<F> { impl<F: ImfsFetcher + Send + 'static> ServeSession<F> {
pub fn new(imfs: Imfs<F>, tree: RojoTree, root_project: Option<Project>) -> Self { pub fn new(imfs: Imfs<F>, tree: RojoTree, root_project: Option<Project>) -> Self {
let start_time = Instant::now();
let session_id = SessionId::new(); let session_id = SessionId::new();
let message_queue = MessageQueue::new(); let message_queue = MessageQueue::new();
@@ -80,6 +87,7 @@ impl<F: ImfsFetcher + Send + 'static> ServeSession<F> {
); );
Self { Self {
start_time,
session_id, session_id,
root_project, root_project,
tree, tree,
@@ -107,6 +115,16 @@ impl<F: ImfsFetcher> ServeSession<F> {
self.session_id self.session_id
} }
pub fn project_name(&self) -> Option<&str> {
self.root_project
.as_ref()
.map(|project| project.name.as_str())
}
pub fn start_time(&self) -> Instant {
self.start_time
}
pub fn serve_place_ids(&self) -> Option<&HashSet<u64>> { pub fn serve_place_ids(&self) -> Option<&HashSet<u64>> {
self.root_project self.root_project
.as_ref() .as_ref()

View File

@@ -1,6 +1,6 @@
//! Defines the HTTP-based UI. These endpoints generally return HTML and SVG. //! Defines the HTTP-based UI. These endpoints generally return HTML and SVG.
use std::sync::Arc; use std::{sync::Arc, time::Duration};
use futures::{future, Future}; use futures::{future, Future};
use hyper::{header, service::Service, Body, Method, Request, Response, StatusCode}; use hyper::{header, service::Service, Body, Method, Request, Response, StatusCode};
@@ -15,10 +15,11 @@ use crate::{
}, },
}; };
static LOGO: &[u8] = include_bytes!("../../assets/logo-512.png");
static ICON: &[u8] = include_bytes!("../../assets/icon-32.png");
static HOME_CSS: &str = include_str!("../../assets/index.css"); static HOME_CSS: &str = include_str!("../../assets/index.css");
pub struct UiService<F> { pub struct UiService<F> {
#[allow(unused)] // TODO: Fill out interface service
serve_session: Arc<ServeSession<F>>, serve_session: Arc<ServeSession<F>>,
} }
@@ -31,6 +32,8 @@ impl<F: ImfsFetcher> Service for UiService<F> {
fn call(&mut self, request: Request<Self::ReqBody>) -> Self::Future { fn call(&mut self, request: Request<Self::ReqBody>) -> Self::Future {
let response = match (request.method(), request.uri().path()) { let response = match (request.method(), request.uri().path()) {
(&Method::GET, "/") => self.handle_home(), (&Method::GET, "/") => self.handle_home(),
(&Method::GET, "/logo.png") => self.handle_logo(),
(&Method::GET, "/icon.png") => self.handle_icon(),
(&Method::GET, "/visualize/rbx") => self.handle_visualize_rbx(), (&Method::GET, "/visualize/rbx") => self.handle_visualize_rbx(),
(&Method::GET, "/visualize/imfs") => self.handle_visualize_imfs(), (&Method::GET, "/visualize/imfs") => self.handle_visualize_imfs(),
(_method, path) => { (_method, path) => {
@@ -50,28 +53,74 @@ impl<F: ImfsFetcher> UiService<F> {
UiService { serve_session } UiService { serve_session }
} }
fn handle_logo(&self) -> Response<Body> {
Response::builder()
.header(header::CONTENT_TYPE, "image/png")
.body(Body::from(LOGO))
.unwrap()
}
fn handle_icon(&self) -> Response<Body> {
Response::builder()
.header(header::CONTENT_TYPE, "image/png")
.body(Body::from(ICON))
.unwrap()
}
fn handle_home(&self) -> Response<Body> { fn handle_home(&self) -> Response<Body> {
let project_name = self.serve_session.project_name().unwrap_or("<unnamed>");
let uptime = {
let elapsed = self.serve_session.start_time().elapsed();
// Round off all of our sub-second precision to make timestamps
// nicer.
let just_nanos = Duration::from_nanos(elapsed.subsec_nanos() as u64);
let elapsed = elapsed - just_nanos;
humantime::format_duration(elapsed).to_string()
};
let page = html! { let page = html! {
<html> <html>
<head> <head>
<title>"Rojo"</title> <title>"Rojo Live Server"</title>
<link rel="icon" type="image/png" sizes="32x32" href="/icon.png" />
<style> <style>
{ ritz::UnescapedText::new(HOME_CSS) } { ritz::UnescapedText::new(HOME_CSS) }
</style> </style>
</head> </head>
<body> <body>
<div class="main"> <main class="main">
<h1 class="title"> <header class="header">
"Rojo Live Sync is up and running!" <img class="main-logo" src="/logo.png" />
</h1> <div class="stats">
<h2 class="subtitle"> <span class="stat">
"Version " { SERVER_VERSION } <span class="stat-name">"Server Version: "</span>
</h2> <span class="stat-value">{ SERVER_VERSION }</span>
<a class="docs" href="https://rojo.space/docs"> </span>
"Rojo Documentation" <span class="stat">
</a> <span class="stat-name">"Project: "</span>
</div> <span class="stat-value">{ project_name }</span>
</span>
<span class="stat">
<span class="stat-name">"Server Uptime: "</span>
<span class="stat-value">{ uptime.to_string() }</span>
</span>
</div>
</header>
<div class="button-list">
<a class="button" href="https://rojo.space/docs">
"Rojo Documentation"
</a>
<a class="button" href="/visualize/imfs">
"View in-memory filesystem state"
</a>
<a class="button" href="/visualize/rbx">
"View instance tree state"
</a>
</div>
</main>
</body> </body>
</html> </html>
}; };