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"
dependencies = [
"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)",
"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)",
@@ -578,7 +578,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "humantime"
version = "1.2.0"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"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)",
"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)",
"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)",
"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)",
@@ -1476,6 +1477,7 @@ dependencies = [
"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)",
"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)",
"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)",
@@ -2194,7 +2196,7 @@ dependencies = [
"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 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-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"

View File

@@ -37,8 +37,8 @@ csv = "1.0"
env_logger = "0.6"
failure = "0.1.3"
futures = "0.1"
humantime = "1.3.0"
hyper = "0.12"
reqwest = "0.9.20"
jod-thread = "0.1.0"
log = "0.4"
maplit = "1.0.1"
@@ -48,9 +48,11 @@ rbx_dom_weak = "1.9.0"
rbx_reflection = "3.1.388"
rbx_xml = "0.11.0"
regex = "1.0"
reqwest = "0.9.20"
ritz = "0.1.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
termcolor = "1.0.5"
uuid = { version = "0.7", features = ["v4", "serde"] }
[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;
padding: 0;
border: none;
text-decoration: inherit;
color: inherit;
font: inherit;
box-sizing: inherit;
}
html {
box-sizing: border-box;
font-family: sans-serif;
text-decoration: none;
height: 100%;
}
@@ -17,27 +23,54 @@ body {
}
.main {
flex: 0 0;
display: flex;
flex-direction: column;
padding: 1rem;
text-align: center;
margin: 0 auto;
width: 100%;
max-width: 60rem;
max-width: 50rem;
background-color: #efefef;
border: 1px solid #666;
border-radius: 4px;
}
.title {
font-size: 2rem;
.header {
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;
}
.subtitle {
font-size: 1.5rem;
font-weight: bold;
.button-list {
flex: 0 0;
display: flex;
justify-content: space-around;
}
.docs {
font-size: 1.3rem;
font-weight: bold;
.button {
display: inline-block;
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 rbx_dom_weak::RbxInstanceProperties;
use termcolor::{BufferWriter, Color, ColorChoice, ColorSpec, WriteColor};
use crate::{
imfs::{Imfs, RealFetcher, WatchMode},
@@ -46,7 +52,7 @@ pub fn serve(options: &ServeOptions) -> Result<(), ServeError> {
})
.unwrap_or(DEFAULT_PORT);
println!("Rojo server listening on port {}", port);
let _ = show_start_message(port);
let mut tree = RojoTree::new(InstancePropertiesWithMeta {
properties: RbxInstanceProperties {
@@ -75,20 +81,36 @@ pub fn serve(options: &ServeOptions) -> Result<(), ServeError> {
server.start(port);
// let receiver = imfs.change_receiver();
Ok(())
}
// while let Ok(change) = receiver.recv() {
// imfs.commit_change(&change)
// .expect("Failed to commit Imfs change");
fn show_start_message(port: u16) -> io::Result<()> {
let mut writer = BufferWriter::stderr(ColorChoice::Auto);
let mut buffer = writer.buffer();
// use notify::DebouncedEvent;
// if let DebouncedEvent::Write(path) = change {
// let contents = imfs.get_contents(path)
// .expect("Failed to read changed path");
writeln!(&mut buffer, "Rojo server listening:")?;
// 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(())
}

View File

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

View File

@@ -1,6 +1,7 @@
use std::{
collections::HashSet,
sync::{Arc, Mutex, MutexGuard},
time::Instant,
};
use crate::{
@@ -20,6 +21,10 @@ use crate::{
/// future. `ServeSession` would be roughly the right interface to expose for
/// those cases.
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.
///
/// 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.
impl<F: ImfsFetcher + Send + 'static> ServeSession<F> {
pub fn new(imfs: Imfs<F>, tree: RojoTree, root_project: Option<Project>) -> Self {
let start_time = Instant::now();
let session_id = SessionId::new();
let message_queue = MessageQueue::new();
@@ -80,6 +87,7 @@ impl<F: ImfsFetcher + Send + 'static> ServeSession<F> {
);
Self {
start_time,
session_id,
root_project,
tree,
@@ -107,6 +115,16 @@ impl<F: ImfsFetcher> ServeSession<F> {
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>> {
self.root_project
.as_ref()

View File

@@ -1,6 +1,6 @@
//! 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 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");
pub struct UiService<F> {
#[allow(unused)] // TODO: Fill out interface service
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 {
let response = match (request.method(), request.uri().path()) {
(&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/imfs") => self.handle_visualize_imfs(),
(_method, path) => {
@@ -50,28 +53,74 @@ impl<F: ImfsFetcher> UiService<F> {
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> {
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! {
<html>
<head>
<title>"Rojo"</title>
<title>"Rojo Live Server"</title>
<link rel="icon" type="image/png" sizes="32x32" href="/icon.png" />
<style>
{ ritz::UnescapedText::new(HOME_CSS) }
</style>
</head>
<body>
<div class="main">
<h1 class="title">
"Rojo Live Sync is up and running!"
</h1>
<h2 class="subtitle">
"Version " { SERVER_VERSION }
</h2>
<a class="docs" href="https://rojo.space/docs">
"Rojo Documentation"
</a>
</div>
<main class="main">
<header class="header">
<img class="main-logo" src="/logo.png" />
<div class="stats">
<span class="stat">
<span class="stat-name">"Server Version: "</span>
<span class="stat-value">{ SERVER_VERSION }</span>
</span>
<span class="stat">
<span class="stat-name">"Project: "</span>
<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>
</html>
};