home template is working

This commit is contained in:
glyph 2022-03-10 11:09:26 +02:00
parent b7cf3c1aab
commit 23d6870f77
11 changed files with 700 additions and 1333 deletions

1644
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -38,21 +38,13 @@ maintenance = { status = "actively-developed" }
base64 = "0.13.0"
dirs = "4.0.0"
env_logger = "0.8"
#golgi = "0.1.0"
golgi = { path = "/home/glyph/Projects/playground/rust/golgi" }
lazy_static = "1.4.0"
log = "0.4"
nest = "1.0.0"
maud = "0.23.0"
peach-lib = { path = "../peach-lib" }
peach-network = { path = "../peach-network", features = ["serde_support"] }
peach-stats = { path = "../peach-stats", features = ["serde_support"] }
rocket = { version = "0.5.0-rc.1", features = ["json", "secrets"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
peach-network = { path = "../peach-network" }
peach-stats = { path = "../peach-stats" }
rouille = "3.5.0"
temporary = "0.6.4"
tera = { version = "1.12.1", features = ["builtins"] }
xdg = "2.2.0"
[dependencies.rocket_dyn_templates]
version = "0.1.0-rc.1"
features = ["tera"]

View File

@ -0,0 +1,19 @@
go slow and steady.
optimise for few dependencies and short compilation times.
we do not need to be super fast or feature-rich.
[ architecture ]
- use the one-file-per-route patten
[ tasks ]
- write the nav and base templates
- get the homepage loading properly
- route handler
- template
- file loading (static assets)

View File

@ -1,3 +1,3 @@
pub mod dns;
pub mod network;
pub mod scuttlebutt;
//pub mod dns;
//pub mod network;
//pub mod scuttlebutt;

View File

@ -8,39 +8,34 @@
//! ## Design
//!
//! `peach-web` is written primarily in Rust and presents a web interface for
//! interacting with the device. The stack currently consists of Rocket (Rust
//! web framework), Tera (Rust template engine inspired by Jinja2 and the Django
//! template language), HTML, CSS and JavaScript. Additional functionality is
//! provided by JSON-RPC clients for the `peach-network` and `peach-stats`
//! microservices.
//!
//! HTML is rendered server-side. Request handlers call JSON-RPC microservices
//! and serve HTML and assets. A JSON API is exposed for remote calls and
//! dynamic client-side content updates via vanilla JavaScript following
//! unobstructive design principles. Each Tera template is passed a context
//! object. In the case of Rust, this object is a `struct` and must implement
//! `Serialize`. The fields of the context object are available in the context
//! of the template to be rendered.
//! interacting with the device. The stack currently consists of Rouille (Rust
//! micro-web-framework), Maud (an HTML template engine for Rust), HTML and
//! CSS.
mod context;
//mod context;
pub mod error;
mod router;
pub mod routes;
#[cfg(test)]
mod tests;
//mod router;
//pub mod routes;
//#[cfg(test)]
//mod tests;
mod templates;
pub mod utils;
use std::{process, sync::RwLock};
use std::sync::RwLock;
use lazy_static::lazy_static;
use log::{debug, error, info};
use peach_lib::{config_manager, config_manager::YAML_PATH as PEACH_CONFIG};
use rocket::{fairing::AdHoc, serde::Deserialize, Build, Rocket};
//use log::{debug, error, info};
use log::info;
//use peach_lib::{config_manager, config_manager::YAML_PATH as PEACH_CONFIG};
//use rocket::{fairing::AdHoc, serde::Deserialize, Build, Rocket};
use rouille::{router, Response};
use utils::Theme;
pub type BoxError = Box<dyn std::error::Error>;
/*
/// Application configuration parameters.
/// These values are extracted from Rocket's default configuration provider:
/// `Config::figment()`. As such, the values are drawn from `Rocket.toml` or
@ -52,14 +47,16 @@ pub struct RocketConfig {
disable_auth: bool,
standalone_mode: bool,
}
*/
lazy_static! {
static ref THEME: RwLock<Theme> = RwLock::new(Theme::Light);
}
static WLAN_IFACE: &str = "wlan0";
static AP_IFACE: &str = "ap0";
//static WLAN_IFACE: &str = "wlan0";
//static AP_IFACE: &str = "ap0";
/*
pub fn init_rocket() -> Rocket<Build> {
info!("Initializing Rocket");
// build a basic rocket instance
@ -84,13 +81,16 @@ pub fn init_rocket() -> Rocket<Build> {
info!("Attaching application configuration to managed state");
mounted_rocket.attach(AdHoc::config::<RocketConfig>())
}
*/
/// Launch the peach-web rocket server.
#[rocket::main]
async fn main() {
const HOSTNAME_AND_PORT: &str = "localhost:8000";
/// Launch the peach-web server.
fn main() {
// initialize logger
env_logger::init();
/*
// check if /var/lib/peachcloud/config.yml exists
if !std::path::Path::new(PEACH_CONFIG).exists() {
info!("PeachCloud configuration file not found; loading default values");
@ -102,14 +102,29 @@ async fn main() {
// this ensures a config file is created if it does not already exist
config_manager::save_peach_config(config).expect("peachcloud configuration saving failed");
}
*/
// initialize rocket
let rocket = init_rocket();
info!("Launching web server...");
// launch rocket
info!("Launching Rocket");
if let Err(e) = rocket.launch().await {
error!("Error in Rocket application: {}", e);
process::exit(1);
}
// the `start_server` starts listening forever on the given address.
rouille::start_server(HOSTNAME_AND_PORT, move |request| {
info!("Now listening on {}", HOSTNAME_AND_PORT);
// static file server
// matches on assets in the `static` directory
let response = rouille::match_assets(&request, "static");
if response.is_success() {
return response;
}
router!(request,
(GET) (/) => {
Response::html(templates::home::build())
},
// The code block is called if none of the other blocks matches the request.
// We return an empty response with a 404 status code.
_ => Response::empty_404()
)
});
}

View File

@ -1,6 +1,6 @@
pub mod authentication;
pub mod catchers;
//pub mod authentication;
//pub mod catchers;
pub mod index;
pub mod scuttlebutt;
pub mod settings;
pub mod status;
//pub mod scuttlebutt;
//pub mod settings;
//pub mod status;

View File

@ -0,0 +1,23 @@
use maud::{html, PreEscaped, DOCTYPE};
/// Base template builder.
///
/// Takes an HTML body as input and splices it into the base template.
pub fn build(body: PreEscaped<String>) -> PreEscaped<String> {
html! {
(DOCTYPE)
html lang="en" data-theme="light";
head {
meta charset="utf-8";
meta name="description" content="PeachCloud web interface";
meta name="author" content="glyph and notplants";
meta name="viewport" content="width=devide-width, initial-scale=1.0";
link rel="stylesheet" href="/css/peachcloud.css";
link rel="stylesheet" href="/css/_variables.css";
title { "PeachCloud" }
}
body {
(body)
}
}
}

View File

@ -0,0 +1,118 @@
use maud::{html, PreEscaped};
use peach_lib::sbot::SbotStatus;
use crate::templates;
/// Read the state of the go-sbot process and define status-related
/// elements accordingly.
fn render_status_elements<'a>() -> (&'a str, &'a str, &'a str) {
// retrieve go-sbot systemd process status
let sbot_status = SbotStatus::read();
// conditionally render the center circle class, center circle text and
// status circle class color based on the go-sbot process state
if let Ok(status) = sbot_status {
if status.state == Some("active".to_string()) {
(
"circle circle-large circle-success",
"^_^",
"circle circle-small border-circle-small border-success",
)
} else if status.state == Some("inactive".to_string()) {
(
"circle circle-large circle-warning",
"z_z",
"circle circle-small border-circle-small border-warning",
)
} else {
(
"circle circle-large circle-danger",
"x_x",
"circle circle-small border-circle-small border-danger",
)
}
} else {
(
"circle circle-large circle-danger",
"x_x",
"circle circle-small border-circle-small border-danger",
)
}
}
/// Home template builder.
pub fn build<'a>() -> PreEscaped<String> {
let (center_circle_class, center_circle_text, status_circle_class) = render_status_elements();
// render the home template html
let home_template = html! {
(PreEscaped("<!-- RADIAL MENU -->"))
div class="grid" {
(PreEscaped("<!-- top-left -->"))
(PreEscaped("<!-- PEERS LINK AND ICON -->"))
a class="top-left" href="/scuttlebutt/peers" title="Scuttlebutt Peers" {
div class="circle circle-small border-circle-small border-ssb" {
img class="icon-medium" src="/icons/users.svg";
}
}
(PreEscaped("<!-- top-middle -->"))
(PreEscaped("<!-- CURRENT USER LINK AND ICON -->"))
a class="top-middle" href="/scuttlebutt/profile" title="Profile" {
div class="circle circle-small border-circle-small border-ssb" {
img class="icon-medium" src="/icons/user.svg";
}
}
(PreEscaped("<!-- top-right -->"))
(PreEscaped("<!-- MESSAGES LINK AND ICON -->"))
a class="top-right" href="/scuttlebutt/private" title="Private Messages" {
div class="circle circle-small border-circle-small border-ssb" {
img class="icon-medium" src="/icons/envelope.svg";
}
}
(PreEscaped("<!-- middle -->"))
a class="middle" {
div class=(center_circle_class) {
p style="font-size: 4rem; color: var(--near-black);" {
(center_circle_text)
}
}
}
(PreEscaped("<!-- bottom-left -->"))
(PreEscaped("<!-- SYSTEM STATUS LINK AND ICON -->"))
a class="bottom-left" href="/status/scuttlebutt" title="Status" {
div class=(status_circle_class) {
img class="icon-medium" src="/icons/heart-pulse.svg";
}
}
/*
TODO: render the path of the status circle button based on the mode
{%- if standalone_mode == true -%}
<a class="bottom-left" href="/status/scuttlebutt" title="Status">
{% else -%}
<a class="bottom-left" href="/status" title="Status">
{%- endif -%}
*/
(PreEscaped("<!-- bottom-middle -->"))
(PreEscaped("<!-- PEACHCLOUD GUIDEBOOK LINK AND ICON -->"))
a class="bottom-middle" href="/guide" title="Guide" {
div class="circle circle-small border-circle-small border-info" {
img class="icon-medium" src="/icons/book.svg";
}
}
(PreEscaped("<!-- bottom-right -->"))
(PreEscaped("<!-- SYSTEM SETTINGS LINK AND ICON -->"))
a class="bottom-right" href="/settings" title="Settings" {
div class="circle circle-small border-circle-small border-settings" {
img class="icon-medium" src="/icons/cog.svg";
}
}
}
};
// wrap the nav bars around the home template content
// title is "" and back button link is `None` because this is the homepage
let body = templates::nav::build(home_template, "", None);
// render the base template with the provided body
templates::base::build(body)
}

View File

@ -0,0 +1,3 @@
mod base;
pub mod home;
pub mod nav;

View File

@ -0,0 +1,59 @@
use maud::{html, PreEscaped};
use crate::utils;
/// Navigation template builder.
///
/// Takes the main HTML content as input and splices it into the navigation template.
pub fn build(main: PreEscaped<String>, title: &str, back: Option<&str>) -> PreEscaped<String> {
// retrieve the current theme value
let theme = utils::get_theme();
// conditionally render the hermies icon and theme-switcher icon with correct link
let (hermies, switcher) = match theme.as_str() {
// if we're using the dark theme, render light icons and "light" query param
"dark" => (
"/icons/hermies_hex_light.svg",
html! {
a class="nav-item" href="/theme?theme=light" {
img class="icon-medium nav-icon-right icon-active" title="Toggle theme" src="/icons/sun.png" alt="Sun";
}
},
),
// otherwise, assume we're using light mode
_ => (
"/icons/hermies_hex.svg",
html! {
a class="nav-item" href="/theme?theme=dark" {
img class="icon-medium nav-icon-right icon-active" title="Toggle theme" src="/icons/moon.png" alt="Moon";
}
},
),
};
html! {
(PreEscaped("<!-- Top navigation bar -->"))
nav class="nav-bar" {
a class="nav-item" href=[back] title="Back" {
img class="icon-medium nav-icon-left icon-active" src="/icons/back.svg" alt="Back";
}
h1 class="nav-title" { (title) }
a class="nav-item" id="logoutButton" href="/logout" title="Logout" {
img class="icon-medium nav-icon-right icon-active" src="/icons/enter.svg" alt="Enter";
}
}
(PreEscaped("<!-- Main content container -->"))
main { (main) }
(PreEscaped("<!-- Bottom navigation bar -->"))
nav class="nav-bar" {
a class="nav-item" href="https://scuttlebutt.nz/" {
img class="icon-medium nav-icon-left" title="Scuttlebutt Website" src=(hermies) alt="Secure Scuttlebutt";
}
a class="nav-item" href="/" {
img class="icon nav-icon-left" src="/icons/peach-icon.png" alt="PeachCloud" title="Home";
}
// render the pre-defined theme-switcher icon
(switcher)
}
}
}

View File

@ -1,3 +1,30 @@
use log::info;
use crate::THEME;
// THEME FUNCTIONS
#[derive(Debug, Copy, Clone)]
pub enum Theme {
Light,
Dark,
}
pub fn get_theme() -> String {
let current_theme = THEME.read().unwrap();
match *current_theme {
Theme::Dark => "dark".to_string(),
_ => "light".to_string(),
}
}
pub fn set_theme(theme: Theme) {
info!("set ui theme to: {:?}", theme);
let mut writable_theme = THEME.write().unwrap();
*writable_theme = theme;
}
/*
pub mod monitor;
use std::io::prelude::*;
@ -84,28 +111,6 @@ pub async fn write_blob_to_store(file: &mut TempFile<'_>) -> Result<String, Peac
Ok(blob_id)
}
// THEME FUNCTIONS
#[derive(Debug, Copy, Clone)]
pub enum Theme {
Light,
Dark,
}
pub fn get_theme() -> String {
let current_theme = THEME.read().unwrap();
match *current_theme {
Theme::Dark => "dark".to_string(),
_ => "light".to_string(),
}
}
pub fn set_theme(theme: Theme) {
info!("set ui theme to: {:?}", theme);
let mut writable_theme = THEME.write().unwrap();
*writable_theme = theme;
}
// HELPER FUNCTIONS
#[derive(Debug, Serialize)]
@ -122,3 +127,4 @@ pub enum TemplateOrRedirect {
Template(Template),
Redirect(Redirect),
}
*/