diff --git a/Cargo.lock b/Cargo.lock index 449c722..a20d689 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1393,6 +1393,7 @@ dependencies = [ [[package]] name = "golgi" version = "0.1.1" +source = "git+https://git.coopcloud.tech/golgi-ssb/golgi.git#77dd75bcd4649b7487069a61e2a8069b49f60a1d" dependencies = [ "async-std", "async-stream 0.3.2", @@ -2068,6 +2069,7 @@ dependencies = [ [[package]] name = "kuska-ssb" version = "0.4.0" +source = "git+https://github.com/Kuska-ssb/ssb#fb7062de606e7c9cae8dd4df402a122db46c1b77" dependencies = [ "async-std", "async-stream 0.2.1", @@ -2817,9 +2819,11 @@ dependencies = [ name = "peach-lib" version = "1.3.2" dependencies = [ + "async-std", "chrono", "dirs 4.0.0", "fslock", + "golgi", "jsonrpc-client-core", "jsonrpc-client-http", "jsonrpc-core 8.0.1", diff --git a/peach-lib/Cargo.toml b/peach-lib/Cargo.toml index 4d4fe38..16ae551 100644 --- a/peach-lib/Cargo.toml +++ b/peach-lib/Cargo.toml @@ -5,9 +5,11 @@ authors = ["Andrew Reid "] edition = "2018" [dependencies] +async-std = "1.10.0" chrono = "0.4.19" dirs = "4.0" fslock="0.1.6" +golgi = { git = "https://git.coopcloud.tech/golgi-ssb/golgi.git" } jsonrpc-client-core = "0.5" jsonrpc-client-http = "0.5" jsonrpc-core = "8.0.1" diff --git a/peach-lib/src/config_manager.rs b/peach-lib/src/config_manager.rs index e3894ab..e29f8d6 100644 --- a/peach-lib/src/config_manager.rs +++ b/peach-lib/src/config_manager.rs @@ -1,12 +1,14 @@ //! Interfaces for writing and reading PeachCloud configurations, stored in yaml. //! -//! Different PeachCloud microservices import peach-lib, so that they can share this interface. +//! Different PeachCloud microservices import peach-lib, so that they can share +//! this interface. //! //! The configuration file is located at: "/var/lib/peachcloud/config.yml" use std::fs; use fslock::LockFile; +use log::debug; use serde::{Deserialize, Serialize}; use crate::error::PeachError; @@ -49,7 +51,7 @@ pub struct PeachConfig { } // helper functions for serializing and deserializing PeachConfig from disc -fn save_peach_config(peach_config: PeachConfig) -> Result { +pub fn save_peach_config(peach_config: PeachConfig) -> Result { // use a file lock to avoid race conditions while saving config let mut lock = LockFile::open(LOCK_FILE_PATH)?; lock.lock()?; @@ -72,6 +74,7 @@ pub fn load_peach_config() -> Result { let peach_config_exists = std::path::Path::new(YAML_PATH).exists(); let peach_config: PeachConfig = if !peach_config_exists { + debug!("Loading peach config: {} does not exist", YAML_PATH); PeachConfig { external_domain: "".to_string(), dyn_domain: "".to_string(), @@ -81,12 +84,14 @@ pub fn load_peach_config() -> Result { dyn_tsig_key_path: "".to_string(), dyn_enabled: false, ssb_admin_ids: Vec::new(), - admin_password_hash: "".to_string(), + // default password is `peach` + admin_password_hash: "146".to_string(), temporary_password_hash: "".to_string(), } } // otherwise we load peach config from disk else { + debug!("Loading peach config: {} exists", YAML_PATH); let contents = fs::read_to_string(YAML_PATH).map_err(|source| PeachError::Read { source, path: YAML_PATH.to_string(), diff --git a/peach-lib/src/error.rs b/peach-lib/src/error.rs index ac4ef97..ba396b1 100644 --- a/peach-lib/src/error.rs +++ b/peach-lib/src/error.rs @@ -61,11 +61,8 @@ pub enum PeachError { /// Represents a failure to parse or compile a regular expression. Regex(regex::Error), - /// Represents a failure to successfully execute an sbot command. - SbotCli { - /// The `stderr` output from the sbot command. - msg: String, - }, + /// Represents a failure to successfully execute an sbot command (via golgi). + Sbot(String), /// Represents a failure to serialize or deserialize JSON. SerdeJson(serde_json::error::Error), @@ -117,7 +114,7 @@ impl std::error::Error for PeachError { PeachError::PasswordNotSet => None, PeachError::Read { ref source, .. } => Some(source), PeachError::Regex(_) => None, - PeachError::SbotCli { .. } => None, + PeachError::Sbot(_) => None, PeachError::SerdeJson(_) => None, PeachError::SerdeYaml(_) => None, PeachError::SsbAdminIdNotFound { .. } => None, @@ -153,22 +150,19 @@ impl std::fmt::Display for PeachError { write!(f, "Date/time parse error: {}", path) } PeachError::PasswordIncorrect => { - write!(f, "Password error: user-supplied password is incorrect") + write!(f, "password is incorrect") } PeachError::PasswordMismatch => { - write!(f, "Password error: user-supplied passwords do not match") + write!(f, "passwords do not match") } PeachError::PasswordNotSet => { - write!( - f, - "Password error: hash value in YAML configuration file is empty" - ) + write!(f, "hash value in YAML configuration file is empty") } PeachError::Read { ref path, .. } => { write!(f, "Read error: {}", path) } PeachError::Regex(ref err) => err.fmt(f), - PeachError::SbotCli { ref msg } => { + PeachError::Sbot(ref msg) => { write!(f, "Sbot error: {}", msg) } PeachError::SerdeJson(ref err) => err.fmt(f), diff --git a/peach-lib/src/password_utils.rs b/peach-lib/src/password_utils.rs index fa28904..d0aff4d 100644 --- a/peach-lib/src/password_utils.rs +++ b/peach-lib/src/password_utils.rs @@ -1,7 +1,10 @@ +use async_std::task; +use golgi::Sbot; +use log::debug; use nanorand::{Rng, WyRand}; use sha3::{Digest, Sha3_256}; -use crate::{config_manager, error::PeachError}; +use crate::{config_manager, error::PeachError, sbot::SbotConfig}; /// Returns Ok(()) if the supplied password is correct, /// and returns Err if the supplied password is incorrect. @@ -102,8 +105,37 @@ using this link: http://peach.local/reset_password", // finally send the message to the admins let peach_config = config_manager::load_peach_config()?; for ssb_admin_id in peach_config.ssb_admin_ids { - // TODO: replace with golgi - //sbot_client::private_message(&msg, &ssb_admin_id)?; + // use golgi to send a private message on scuttlebutt + match task::block_on(publish_private_msg(&msg, &ssb_admin_id)) { + Ok(_) => (), + Err(e) => return Err(PeachError::Sbot(e)), + } } Ok(()) } + +async fn publish_private_msg(msg: &str, recipient: &str) -> Result<(), String> { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); + + let msg = msg.to_string(); + let recipient = vec![recipient.to_string()]; + + // initialise sbot connection with ip:port and shscap from config file + let mut sbot_client = match sbot_config { + // TODO: panics if we pass `Some(conf.shscap)` as second arg + Some(conf) => { + let ip_port = conf.lis.clone(); + Sbot::init(Some(ip_port), None) + .await + .map_err(|e| e.to_string())? + } + None => Sbot::init(None, None).await.map_err(|e| e.to_string())?, + }; + + debug!("Publishing a Scuttlebutt private message with temporary password"); + match sbot_client.publish_private(msg, recipient).await { + Ok(_) => Ok(()), + Err(e) => Err(format!("Failed to publish private message: {}", e)), + } +} diff --git a/peach-web/Cargo.toml b/peach-web/Cargo.toml index 7ba1a43..cfa6d45 100644 --- a/peach-web/Cargo.toml +++ b/peach-web/Cargo.toml @@ -38,8 +38,7 @@ maintenance = { status = "actively-developed" } base64 = "0.13.0" dirs = "4.0.0" env_logger = "0.8" -#golgi = "0.1.0" -golgi = { path = "../../../playground/rust/golgi" } +golgi = { git = "https://git.coopcloud.tech/golgi-ssb/golgi.git" } lazy_static = "1.4.0" log = "0.4" nest = "1.0.0" diff --git a/peach-web/README.md b/peach-web/README.md index b686101..986cc7a 100644 --- a/peach-web/README.md +++ b/peach-web/README.md @@ -1,18 +1,27 @@ # peach-web -[![Build Status](https://travis-ci.com/peachcloud/peach-web.svg?branch=master)](https://travis-ci.com/peachcloud/peach-web) ![Generic badge](https://img.shields.io/badge/version-0.5.0-.svg) +![Generic badge](https://img.shields.io/badge/version-0.5.0-.svg) ## Web Interface for PeachCloud **peach-web** provides a web interface for the PeachCloud device. -Initial development is focused on administration of the device itself, beginning with networking functionality, with SSB-related administration to be integrated at a later stage. +The web interface is primarily designed as a means of managing a Scuttlebutt pub. As such, it exposes the following features: -The peach-web stack currently consists of [Rocket](https://rocket.rs/) (Rust web framework), [Tera](http://tera.netlify.com/) (Rust template engine), HTML and CSS. + - Create a Scuttlebutt profile + - Follow, unfollow, block and unblock peers + - Generate pub invite codes + - Configure the sbot (hops, log directory, LAN discovery etc.) + - Send private messages + - Stop, start and restart the sbot + +Additional features are focused on administration of the device itself. This includes networking functionality and device statistics. + +The peach-web stack currently consists of [Rocket](https://rocket.rs/) (Rust web framework), [Tera](http://tera.netlify.com/) (Rust template engine), HTML and CSS. Scuttlebutt functionality is provided by [golgi](http://golgi.mycelial.technology). _Note: This is a work-in-progress._ -### Setup +## Setup Clone the `peach-workspace` repo: @@ -35,9 +44,9 @@ Run the binary: `./target/release/peach-web` -### Environment +## Environment -**Deployment Profile** +### Deployment Profile The web application deployment profile can be configured with the `ROCKET_ENV` environment variable: @@ -47,17 +56,17 @@ Default configuration parameters are defined in `Rocket.toml`. This file defines Read the [Rocket Environment Configurations docs](https://rocket.rs/v0.5-rc/guide/configuration/#environment-variables) for further information. -**Configuration Mode** +### Configuration Mode The web application can be run with a minimal set of routes and functionality (PeachPub - a simple sbot manager) or with the full-suite of capabilities, including network management and access to device statistics (PeachCloud). The mode is enabled by default (as defined in `Rocket.toml`) but can be overwritten using the `ROCKET_STANDALONE_MODE` environment variable: `true` or `false`. If the variable is unset or the value is incorrectly set, the application defaults to standalone mode. -**Authentication** +### Authentication Authentication is disabled in `debug` mode and enabled by default when running the application in `release` mode. It can be disabled by setting the `ROCKET_DISABLE_AUTH` environment variable to `true`: `export ROCKET_DISABLE_AUTH=true` -**Logging** +### Logging Logging is made available with `env_logger`: @@ -65,7 +74,7 @@ Logging is made available with `env_logger`: Other logging levels include `debug`, `warn` and `error`. -### Debian Packaging +## Debian Packaging A `systemd` service file and Debian maintainer scripts are included in the `debian` directory, allowing `peach-web` to be easily bundled as a Debian package (`.deb`). The `cargo-deb` [crate](https://crates.io/crates/cargo-deb) can be used to achieve this. @@ -97,17 +106,17 @@ Remove configuration files (not removed with `apt-get remove`): `sudo apt-get purge peach-web` -### Design +## Design `peach-web` is built on the Rocket webserver and Tera templating engine. It presents a web interface for interacting with the device. HTML is rendered server-side. Request handlers call `peach-` libraries and serve HTML and assets. 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. -### Configuration +## Configuration Configuration variables are stored in /var/lib/peachcloud/config.yml. Peach-web also updates this file when changes are made to configurations via the web interface. peach-web has no database, so all configurations are stored in this file. -#### Dynamic DNS Configuration +### Dynamic DNS Configuration Most users will want to use the default PeachCloud dynamic dns server. If the config dyn_use_custom_server=false, then default values will be used. @@ -115,6 +124,6 @@ If the config dyn_use_custom_server=true, then a value must also be set for dyn_ This value is the URL of the instance of peach-dyndns-server that requests will be sent to for domain registration. Using a custom value can here can be useful for testing. -### Licensing +## Licensing AGPL-3.0 diff --git a/peach-web/src/context/network.rs b/peach-web/src/context/network.rs index 3ce1ee5..9e3b1b4 100644 --- a/peach-web/src/context/network.rs +++ b/peach-web/src/context/network.rs @@ -3,11 +3,7 @@ use std::collections::HashMap; -use rocket::{ - form::FromForm, - serde::{Deserialize, Serialize}, - UriDisplayQuery, -}; +use rocket::{form::FromForm, serde::Serialize, UriDisplayQuery}; use peach_network::{ network, @@ -36,12 +32,12 @@ pub fn ap_state() -> String { } } -#[derive(Debug, Deserialize, FromForm, UriDisplayQuery)] +#[derive(Debug, FromForm, UriDisplayQuery)] pub struct Ssid { pub ssid: String, } -#[derive(Debug, Deserialize, FromForm)] +#[derive(Debug, FromForm)] pub struct WiFi { pub ssid: String, pub pass: String, diff --git a/peach-web/src/context/scuttlebutt.rs b/peach-web/src/context/scuttlebutt.rs index 9d319a6..9f5ac77 100644 --- a/peach-web/src/context/scuttlebutt.rs +++ b/peach-web/src/context/scuttlebutt.rs @@ -62,11 +62,11 @@ impl StatusContext { // retrieve go-sbot systemd process status let sbot_status = SbotStatus::read()?; + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); + // we only want to try and interact with the sbot if it's active if sbot_status.state == Some("active".to_string()) { - // retrieve latest go-sbot configuration parameters - let sbot_config = SbotConfig::read().ok(); - let mut sbot_client = init_sbot_with_config(&sbot_config).await?; // retrieve the local id @@ -80,14 +80,13 @@ impl StatusContext { // assign the sequence number of the latest msg context.latest_seq = Some(msgs[0].sequence); - - context.sbot_config = sbot_config; } else { // the sbot is not currently active; return a helpful message context.flash_name = Some("warning".to_string()); context.flash_msg = Some("The Sbot is currently inactive. As a result, status data cannot be retrieved. Visit the Scuttlebutt settings menu to start the Sbot and then try again".to_string()); } + context.sbot_config = sbot_config; context.sbot_status = Some(sbot_status); Ok(context) @@ -425,98 +424,88 @@ impl ProfileContext { // retrieve go-sbot systemd process status let sbot_status = SbotStatus::read()?; - // we only want to try and interact with the sbot if it's active - if sbot_status.state == Some("active".to_string()) { - // retrieve latest go-sbot configuration parameters - let sbot_config = SbotConfig::read().ok(); + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); - let mut sbot_client = init_sbot_with_config(&sbot_config).await?; + let mut sbot_client = init_sbot_with_config(&sbot_config).await?; - let local_id = sbot_client.whoami().await?; + let local_id = sbot_client.whoami().await?; - // if an ssb_id has been provided to the context builder, we assume that - // the profile info being retrieved is for a peer (ie. not for our local - // profile) - let id = if ssb_id.is_some() { - // we are not dealing with the local profile - context.is_local_profile = false; + // if an ssb_id has been provided to the context builder, we assume that + // the profile info being retrieved is for a peer (ie. not for our local + // profile) + let id = if ssb_id.is_some() { + // we are not dealing with the local profile + context.is_local_profile = false; - // we're safe to unwrap here because we know it's `Some(id)` - let peer_id = ssb_id.unwrap(); + // we're safe to unwrap here because we know it's `Some(id)` + let peer_id = ssb_id.unwrap(); - // determine relationship between peer and local id - let follow_query = RelationshipQuery { - source: local_id.clone(), - dest: peer_id.clone(), - }; - - // query follow state - context.following = match sbot_client.friends_is_following(follow_query).await { - Ok(following) if following == "true" => Some(true), - Ok(following) if following == "false" => Some(false), - _ => None, - }; - - // TODO: i don't like that we have to instantiate the same query object - // twice. see if we can streamline this in golgi - let block_query = RelationshipQuery { - source: local_id.clone(), - dest: peer_id.clone(), - }; - - // query block state - context.blocking = match sbot_client.friends_is_blocking(block_query).await { - Ok(blocking) if blocking == "true" => Some(true), - Ok(blocking) if blocking == "false" => Some(false), - _ => None, - }; - - peer_id - } else { - // if an ssb_id has not been provided, retrieve the local id using whoami - context.is_local_profile = true; - - local_id + // determine relationship between peer and local id + let follow_query = RelationshipQuery { + source: local_id.clone(), + dest: peer_id.clone(), }; - // TODO: add relationship state context if not local profile - // ie. lookup is_following and is_blocking, set context accordingly + // query follow state + context.following = match sbot_client.friends_is_following(follow_query).await { + Ok(following) if following == "true" => Some(true), + Ok(following) if following == "false" => Some(false), + _ => None, + }; - // retrieve the profile info for the given id - let info = sbot_client.get_profile_info(&id).await?; - // set each context field accordingly - for (key, val) in info { - match key.as_str() { - "name" => context.name = Some(val), - "description" => context.description = Some(val), - "image" => context.image = Some(val), - _ => (), - } - } + // TODO: i don't like that we have to instantiate the same query object + // twice. see if we can streamline this in golgi + let block_query = RelationshipQuery { + source: local_id.clone(), + dest: peer_id.clone(), + }; - // assign the ssb public key to the context - // (could be for the local profile or a peer) - context.id = Some(id); + // query block state + context.blocking = match sbot_client.friends_is_blocking(block_query).await { + Ok(blocking) if blocking == "true" => Some(true), + Ok(blocking) if blocking == "false" => Some(false), + _ => None, + }; - // determine the path to the blob defined by the value of `context.image` - if let Some(ref blob_id) = context.image { - context.blob_path = match blobs::get_blob_path(&blob_id) { - Ok(path) => { - // if we get the path, check if the blob is in the blobstore. - // this allows us to default to a placeholder image in the template - if let Ok(exists) = utils::blob_is_stored_locally(&path).await { - context.blob_exists = exists - }; - - Some(path) - } - Err(_) => None, - } - } + peer_id } else { - // the sbot is not currently active; return a helpful message - context.flash_name = Some("warning".to_string()); - context.flash_msg = Some("The Sbot is currently inactive. As a result, profile data cannot be retrieved. Visit the Scuttlebutt settings menu to start the Sbot and then try again".to_string()); + // if an ssb_id has not been provided, retrieve the local id using whoami + context.is_local_profile = true; + + local_id + }; + + // retrieve the profile info for the given id + let info = sbot_client.get_profile_info(&id).await?; + // set each context field accordingly + for (key, val) in info { + match key.as_str() { + "name" => context.name = Some(val), + "description" => context.description = Some(val), + "image" => context.image = Some(val), + _ => (), + } + } + + // assign the ssb public key to the context + // (could be for the local profile or a peer) + context.id = Some(id); + + // determine the path to the blob defined by the value of `context.image` + if let Some(ref blob_id) = context.image { + context.blob_path = match blobs::get_blob_path(&blob_id) { + Ok(path) => { + // if we get the path, check if the blob is in the blobstore. + // this allows us to default to a placeholder image in the template + if let Ok(exists) = utils::blob_is_stored_locally(&path).await { + context.blob_exists = exists + }; + + Some(path) + } + Err(_) => None, + } } context.sbot_status = Some(sbot_status); @@ -564,22 +553,15 @@ impl PrivateContext { // retrieve go-sbot systemd process status let sbot_status = SbotStatus::read()?; - // we only want to try and interact with the sbot if it's active - if sbot_status.state == Some("active".to_string()) { - // retrieve latest go-sbot configuration parameters - let sbot_config = SbotConfig::read().ok(); + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); - let mut sbot_client = init_sbot_with_config(&sbot_config).await?; + let mut sbot_client = init_sbot_with_config(&sbot_config).await?; - context.recipient_id = recipient_id; + context.recipient_id = recipient_id; - let local_id = sbot_client.whoami().await?; - context.id = Some(local_id); - } else { - // the sbot is not currently active; return a helpful message - context.flash_name = Some("warning".to_string()); - context.flash_msg = Some("The Sbot is currently inactive. As a result, private messages cannot be published. Visit the Scuttlebutt settings menu to start the Sbot and then try again".to_string()); - } + let local_id = sbot_client.whoami().await?; + context.id = Some(local_id); context.sbot_status = Some(sbot_status); diff --git a/peach-web/src/main.rs b/peach-web/src/main.rs index 1e7f1b4..8150710 100644 --- a/peach-web/src/main.rs +++ b/peach-web/src/main.rs @@ -34,6 +34,7 @@ use std::{process, 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 utils::Theme; @@ -90,6 +91,18 @@ async 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"); + // since we're in the intialisation phase, panic if the loading fails + let config = + config_manager::load_peach_config().expect("peachcloud configuration loading failed"); + + info!("Saving default PeachCloud configuration values to file"); + // 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(); diff --git a/peach-web/src/router.rs b/peach-web/src/router.rs index ee43d47..0872af0 100644 --- a/peach-web/src/router.rs +++ b/peach-web/src/router.rs @@ -26,14 +26,11 @@ pub fn mount_peachpub_routes(rocket: Rocket) -> Rocket { .mount( "/", routes![ - help, + guide, home, login, login_post, logout, - reboot_cmd, - shutdown_cmd, - power_menu, settings_menu, set_theme, ], @@ -43,7 +40,6 @@ pub fn mount_peachpub_routes(rocket: Rocket) -> Rocket { routes![ admin_menu, configure_admin, - add_admin, add_admin_post, delete_admin_post, change_password, @@ -101,6 +97,7 @@ pub fn mount_peachpub_routes(rocket: Rocket) -> Rocket { /// required to run a complete PeachCloud build. pub fn mount_peachcloud_routes(rocket: Rocket) -> Rocket { mount_peachpub_routes(rocket) + .mount("/", routes![reboot_cmd, shutdown_cmd, power_menu,]) .mount( "/settings/network", routes![ diff --git a/peach-web/src/routes/authentication.rs b/peach-web/src/routes/authentication.rs index 04df16c..3ff8f2b 100644 --- a/peach-web/src/routes/authentication.rs +++ b/peach-web/src/routes/authentication.rs @@ -6,7 +6,6 @@ use rocket::{ post, request::{self, FlashMessage, FromRequest, Request}, response::{Flash, Redirect}, - serde::Deserialize, }; use rocket_dyn_templates::{tera::Context, Template}; @@ -89,17 +88,14 @@ pub fn login(flash: Option) -> Template { Template::render("login", &context.into_json()) } -#[derive(Debug, Deserialize, FromForm)] +#[derive(Debug, FromForm)] pub struct LoginForm { - pub username: String, pub password: String, } -/// Takes in a LoginForm and returns Ok(()) if username and password -/// are correct to authenticate with peach-web. +/// Takes in a LoginForm and returns Ok(()) if the password is correct. /// -/// Note: currently there is only one user, and the username should always -/// be "admin". +/// Note: there is currently only one user, therefore we don't need a username. pub fn verify_login_form(login_form: LoginForm) -> Result<(), PeachError> { password_utils::verify_password(&login_form.password) } @@ -117,13 +113,14 @@ pub fn login_post(login_form: Form, cookies: &CookieJar<'_>) -> Templ TemplateOrRedirect::Redirect(Redirect::to("/")) } - Err(_) => { + Err(e) => { + let err_msg = format!("Invalid password: {}", e); // if unsuccessful login, render /login page again let mut context = Context::new(); context.insert("back", &Some("/".to_string())); context.insert("title", &Some("Login".to_string())); context.insert("flash_name", &("error".to_string())); - context.insert("flash_msg", &("Invalid password".to_string())); + context.insert("flash_msg", &(err_msg)); TemplateOrRedirect::Template(Template::render("login", &context.into_json())) } @@ -142,7 +139,7 @@ pub fn logout(cookies: &CookieJar<'_>) -> Flash { // HELPERS AND ROUTES FOR /reset_password -#[derive(Debug, Deserialize, FromForm)] +#[derive(Debug, FromForm)] pub struct ResetPasswordForm { pub temporary_password: String, pub new_password1: String, @@ -198,7 +195,7 @@ pub fn reset_password_post(reset_password_form: Form) -> Temp let (flash_name, flash_msg) = match save_reset_password_form(reset_password_form.into_inner()) { Ok(_) => ( "success".to_string(), - "New password is now saved. Return home to login".to_string(), + "New password has been saved. Return home to login".to_string(), ), Err(err) => ( "error".to_string(), @@ -266,9 +263,9 @@ pub fn send_password_reset_post() -> Template { // HELPERS AND ROUTES FOR /settings/change_password -#[derive(Debug, Deserialize, FromForm)] +#[derive(Debug, FromForm)] pub struct PasswordForm { - pub old_password: String, + pub current_password: String, pub new_password1: String, pub new_password2: String, } @@ -277,9 +274,9 @@ pub struct PasswordForm { pub fn save_password_form(password_form: PasswordForm) -> Result<(), PeachWebError> { info!( "change password!: {} {} {}", - password_form.old_password, password_form.new_password1, password_form.new_password2 + password_form.current_password, password_form.new_password1, password_form.new_password2 ); - password_utils::verify_password(&password_form.old_password)?; + password_utils::verify_password(&password_form.current_password)?; // if the previous line did not throw an error, then the old password is correct password_utils::validate_new_passwords( &password_form.new_password1, @@ -321,7 +318,7 @@ pub fn change_password_post(password_form: Form, _auth: Authentica let (flash_name, flash_msg) = match save_password_form(password_form.into_inner()) { Ok(_) => ( "success".to_string(), - "New password is now saved".to_string(), + "New password has been saved".to_string(), ), Err(err) => ( "error".to_string(), diff --git a/peach-web/src/routes/index.rs b/peach-web/src/routes/index.rs index e796c03..45eb21c 100644 --- a/peach-web/src/routes/index.rs +++ b/peach-web/src/routes/index.rs @@ -29,17 +29,17 @@ pub fn home(_auth: Authenticated, config: &State) -> Template { Template::render("home", &context.into_json()) } -// HELPERS AND ROUTES FOR /help +// HELPERS AND ROUTES FOR /guide -#[get("/help")] -pub fn help(flash: Option) -> Template { +#[get("/guide")] +pub fn guide(flash: Option) -> Template { // retrieve current ui theme let theme = utils::get_theme(); let mut context = Context::new(); context.insert("theme", &theme); context.insert("back", &Some("/".to_string())); - context.insert("title", &Some("Help".to_string())); + context.insert("title", &Some("Guide".to_string())); // check to see if there is a flash message to display if let Some(flash) = flash { @@ -47,5 +47,5 @@ pub fn help(flash: Option) -> Template { context.insert("flash_msg", &Some(flash.message().to_string())); }; - Template::render("help", &context.into_json()) + Template::render("guide", &context.into_json()) } diff --git a/peach-web/src/routes/scuttlebutt.rs b/peach-web/src/routes/scuttlebutt.rs index 01aa108..e9c1d4b 100644 --- a/peach-web/src/routes/scuttlebutt.rs +++ b/peach-web/src/routes/scuttlebutt.rs @@ -156,7 +156,7 @@ pub async fn create_invite(invite: Form, _auth: Authenticated) -> Flash< Ok(false) => { return Flash::warning( Redirect::to(url), - "The Sbot is currently inactive. As a result, new posts cannot be published. Visit the Scuttlebutt settings menu to start the Sbot and then try again", + "The Sbot is inactive. New invite codes cannot be generated. Visit the Scuttlebutt settings menu to start the Sbot and then try again", ) } // failed to retrieve go-sbot systemd process status @@ -173,34 +173,53 @@ pub async fn private( flash: Option>, _auth: Authenticated, ) -> Template { - if let Some(ref key) = public_key { - // `url_decode` replaces '+' with ' ', so we need to revert that - public_key = Some(key.replace(' ', "+")); - } + // display a helpful message if the sbot is inactive + if let Ok(false) = is_sbot_active() { + // retrieve current ui theme + let theme = utils::get_theme(); - // build the private context object - let context = PrivateContext::build(public_key).await; + let mut context = Context::new(); + context.insert("theme", &theme); + context.insert("back", &Some("/".to_string())); + context.insert("title", &Some("Private Messages".to_string())); + context.insert( + "unavailable_msg", + &Some("Private messages cannot be published.".to_string()), + ); - match context { - // we were able to build the context without errors - Ok(mut context) => { - // check to see if there is a flash message to display - if let Some(flash) = flash { - // add flash message contents to the context object - context.flash_name = Some(flash.kind().to_string()); - context.flash_msg = Some(flash.message().to_string()); - }; - - Template::render("scuttlebutt/private", &context) + // render the "sbot is inactive" template + return Template::render("scuttlebutt/inactive", &context.into_json()); + // otherwise, build the full context and render the private message template + } else { + if let Some(ref key) = public_key { + // `url_decode` replaces '+' with ' ', so we need to revert that + public_key = Some(key.replace(' ', "+")); } - // an error occurred while building the context - Err(e) => { - // build the default context and pass along the error message - let mut context = PrivateContext::default(); - context.flash_name = Some("error".to_string()); - context.flash_msg = Some(e.to_string()); - Template::render("scuttlebutt/private", &context) + // build the private context object + let context = PrivateContext::build(public_key).await; + + match context { + // we were able to build the context without errors + Ok(mut context) => { + // check to see if there is a flash message to display + if let Some(flash) = flash { + // add flash message contents to the context object + context.flash_name = Some(flash.kind().to_string()); + context.flash_msg = Some(flash.message().to_string()); + }; + + Template::render("scuttlebutt/private", &context) + } + // an error occurred while building the context + Err(e) => { + // build the default context and pass along the error message + let mut context = PrivateContext::default(); + context.flash_name = Some("error".to_string()); + context.flash_msg = Some(e.to_string()); + + Template::render("scuttlebutt/private", &context) + } } } } @@ -256,7 +275,7 @@ pub async fn private_post(private: Form, _auth: Authenticated) -> Flash Ok(false) => { return Flash::warning( Redirect::to(url), - "The Sbot is currently inactive. As a result, new private message cannot be published. Visit the Scuttlebutt settings menu to start the Sbot and then try again", + "The Sbot is inactive. New private message cannot be published. Visit the Scuttlebutt settings menu to start the Sbot and then try again", ); } // failed to retrieve go-sbot systemd process status @@ -335,12 +354,24 @@ pub fn peers(flash: Option, _auth: Authenticated) -> Template { context.insert("title", &Some("Scuttlebutt Peers")); context.insert("back", &Some("/")); - // check to see if there is a flash message to display - if let Some(flash) = flash { - // add flash message contents to the context object - context.insert("flash_name", &Some(flash.kind().to_string())); - context.insert("flash_msg", &Some(flash.message().to_string())); - }; + // display a helpful message if the sbot is inactive + if let Ok(false) = is_sbot_active() { + context.insert( + "unavailable_msg", + &Some("Social lists and interactions are unavailable.".to_string()), + ); + + // render the "sbot is inactive" template + return Template::render("scuttlebutt/inactive", &context.into_json()); + } else { + context.insert("sbot_state", &Some("active".to_string())); + // check to see if there is a flash message to display + if let Some(flash) = flash { + // add flash message contents to the context object + context.insert("flash_name", &Some(flash.kind().to_string())); + context.insert("flash_msg", &Some(flash.message().to_string())); + }; + } Template::render("scuttlebutt/peers", &context.into_json()) } @@ -388,7 +419,7 @@ pub async fn publish(post: Form, _auth: Authenticated) -> Flash Ok(false) => { return Flash::warning( Redirect::to(url), - "The Sbot is currently inactive. As a result, new posts cannot be published. Visit the Scuttlebutt settings menu to start the Sbot and then try again", + "The Sbot is inactive. New posts cannot be published. Visit the Scuttlebutt settings menu to start the Sbot and then try again", ); } Err(e) => return Flash::error(Redirect::to(url), e), @@ -431,7 +462,7 @@ pub async fn follow(peer: Form, _auth: Authenticated) -> Flash { Ok(false) => { return Flash::warning( Redirect::to(url), - "The Sbot is currently inactive. As a result, follow messages cannot be published. Visit the Scuttlebutt settings menu to start the Sbot and then try again", + "The Sbot is inactive. Follow messages cannot be published. Visit the Scuttlebutt settings menu to start the Sbot and then try again", ); } Err(e) => return Flash::error(Redirect::to(url), e), @@ -476,7 +507,7 @@ pub async fn unfollow(peer: Form, _auth: Authenticated) -> Flash Ok(false) => { return Flash::warning( Redirect::to(url), - "The Sbot is currently inactive. As a result, follow messages cannot be published. Visit the Scuttlebutt settings menu to start the Sbot and then try again", + "The Sbot is inactive. Follow messages cannot be published. Visit the Scuttlebutt settings menu to start the Sbot and then try again", ); } Err(e) => return Flash::error(Redirect::to(url), e), @@ -519,7 +550,7 @@ pub async fn block(peer: Form, _auth: Authenticated) -> Flash { Ok(false) => { return Flash::warning( Redirect::to(url), - "The Sbot is currently inactive. As a result, follow messages cannot be published. Visit the Scuttlebutt settings menu to start the Sbot and then try again", + "The Sbot is inactive. Follow messages cannot be published. Visit the Scuttlebutt settings menu to start the Sbot and then try again", ); } Err(e) => return Flash::error(Redirect::to(url), e), @@ -563,7 +594,7 @@ pub async fn unblock(peer: Form, _auth: Authenticated) -> Flash Ok(false) => { return Flash::warning( Redirect::to(url), - "The Sbot is currently inactive. As a result, follow messages cannot be published. Visit the Scuttlebutt settings menu to start the Sbot and then try again", + "The Sbot is inactive. Follow messages cannot be published. Visit the Scuttlebutt settings menu to start the Sbot and then try again", ); } Err(e) => return Flash::error(Redirect::to(url), e), @@ -582,41 +613,59 @@ pub async fn profile( flash: Option>, _auth: Authenticated, ) -> Template { - if let Some(ref key) = public_key { - // `url_decode` replaces '+' with ' ', so we need to revert that - public_key = Some(key.replace(' ', "+")); - } + // display a helpful message if the sbot is inactive + if let Ok(false) = is_sbot_active() { + // retrieve current ui theme + let theme = utils::get_theme(); - // build the profile context object - let context = ProfileContext::build(public_key).await; + let mut context = Context::new(); + context.insert("theme", &theme); + context.insert("back", &Some("/".to_string())); + context.insert("title", &Some("Profile".to_string())); + context.insert( + "unavailable_msg", + &Some("Profile data cannot be retrieved.".to_string()), + ); - match context { - // we were able to build the context without errors - Ok(mut context) => { - // check to see if there is a flash message to display - if let Some(flash) = flash { - // add flash message contents to the context object - context.flash_name = Some(flash.kind().to_string()); - context.flash_msg = Some(flash.message().to_string()); - }; - - Template::render("scuttlebutt/profile", &context) + // render the "sbot is inactive" template + return Template::render("scuttlebutt/inactive", &context.into_json()); + } else { + if let Some(ref key) = public_key { + // `url_decode` replaces '+' with ' ', so we need to revert that + public_key = Some(key.replace(' ', "+")); } - // an error occurred while building the context - Err(e) => { - // build the default context and pass along the error message - let mut context = ProfileContext::default(); - // flash name and msg will be `Some` if the sbot is inactive (in - // that case, they are set by the context builder). - // otherwise, we need to assign the name and returned error msg - // to the flash. - if context.flash_name.is_none() || context.flash_msg.is_none() { - context.flash_name = Some("error".to_string()); - context.flash_msg = Some(e.to_string()); + // build the profile context object + let context = ProfileContext::build(public_key).await; + + match context { + // we were able to build the context without errors + Ok(mut context) => { + // check to see if there is a flash message to display + if let Some(flash) = flash { + // add flash message contents to the context object + context.flash_name = Some(flash.kind().to_string()); + context.flash_msg = Some(flash.message().to_string()); + }; + + Template::render("scuttlebutt/profile", &context) } + // an error occurred while building the context + Err(e) => { + // build the default context and pass along the error message + let mut context = ProfileContext::default(); - Template::render("scuttlebutt/profile", &context) + // flash name and msg will be `Some` if the sbot is inactive (in + // that case, they are set by the context builder). + // otherwise, we need to assign the name and returned error msg + // to the flash. + if context.flash_name.is_none() || context.flash_msg.is_none() { + context.flash_name = Some("error".to_string()); + context.flash_msg = Some(e.to_string()); + } + + Template::render("scuttlebutt/profile", &context) + } } } } @@ -627,31 +676,49 @@ pub async fn profile( /// for the local Scuttlebutt profile. #[get("/profile/update")] pub async fn update_profile(flash: Option>, _auth: Authenticated) -> Template { - // build the profile context object - let context = ProfileContext::build(None).await; + // display a helpful message if the sbot is inactive + if let Ok(false) = is_sbot_active() { + // retrieve current ui theme + let theme = utils::get_theme(); - match context { - // we were able to build the context without errors - Ok(mut context) => { - context.back = Some("/scuttlebutt/profile".to_string()); + let mut context = Context::new(); + context.insert("theme", &theme); + context.insert("back", &Some("/".to_string())); + context.insert("title", &Some("Profile".to_string())); + context.insert( + "unavailable_msg", + &Some("Profile data cannot be retrieved.".to_string()), + ); - // check to see if there is a flash message to display - if let Some(flash) = flash { - // add flash message contents to the context object - context.flash_name = Some(flash.kind().to_string()); - context.flash_msg = Some(flash.message().to_string()); - }; + // render the "sbot is inactive" template + return Template::render("scuttlebutt/inactive", &context.into_json()); + } else { + // build the profile context object + let context = ProfileContext::build(None).await; - Template::render("scuttlebutt/update_profile", &context) - } - // an error occurred while building the context - Err(e) => { - // build the default context and pass along the error message - let mut context = ProfileContext::default(); - context.flash_name = Some("error".to_string()); - context.flash_msg = Some(e.to_string()); + match context { + // we were able to build the context without errors + Ok(mut context) => { + context.back = Some("/scuttlebutt/profile".to_string()); - Template::render("scuttlebutt/update_profile", &context) + // check to see if there is a flash message to display + if let Some(flash) = flash { + // add flash message contents to the context object + context.flash_name = Some(flash.kind().to_string()); + context.flash_msg = Some(flash.message().to_string()); + }; + + Template::render("scuttlebutt/update_profile", &context) + } + // an error occurred while building the context + Err(e) => { + // build the default context and pass along the error message + let mut context = ProfileContext::default(); + context.flash_name = Some("error".to_string()); + context.flash_msg = Some(e.to_string()); + + Template::render("scuttlebutt/update_profile", &context) + } } } } @@ -772,7 +839,7 @@ pub async fn update_profile_post( Ok(false) => { return Flash::warning( Redirect::to(url), - "The Sbot is currently inactive. As a result, profile data cannot be updated. Visit the Scuttlebutt settings menu to start the Sbot and then try again", + "The Sbot is inactive. Profile data cannot be updated. Visit the Scuttlebutt settings menu to start the Sbot and then try again", ); } Err(e) => return Flash::error(Redirect::to(url), e), diff --git a/peach-web/src/routes/settings/admin.rs b/peach-web/src/routes/settings/admin.rs index c2312a3..89b296f 100644 --- a/peach-web/src/routes/settings/admin.rs +++ b/peach-web/src/routes/settings/admin.rs @@ -3,7 +3,6 @@ use rocket::{ get, post, request::FlashMessage, response::{Flash, Redirect}, - serde::Deserialize, uri, }; use rocket_dyn_templates::{tera::Context, Template}; @@ -77,50 +76,31 @@ pub fn configure_admin(flash: Option, _auth: Authenticated) -> Tem // HELPERS AND ROUTES FOR /settings/admin/add -#[derive(Debug, Deserialize, FromForm)] +#[derive(Debug, FromForm)] pub struct AddAdminForm { pub ssb_id: String, } pub fn save_add_admin_form(admin_form: AddAdminForm) -> Result<(), PeachWebError> { let _result = config_manager::add_ssb_admin_id(&admin_form.ssb_id)?; + // if the previous line didn't throw an error then it was a success Ok(()) } -#[get("/add")] -pub fn add_admin(flash: Option, _auth: Authenticated) -> Template { - // retrieve current ui theme - let theme = utils::get_theme(); - - let mut context = Context::new(); - context.insert("theme", &theme); - context.insert("back", &Some("/settings/admin/configure".to_string())); - context.insert("title", &Some("Add Admin".to_string())); - - // check to see if there is a flash message to display - if let Some(flash) = flash { - context.insert("flash_name", &Some(flash.kind().to_string())); - context.insert("flash_msg", &Some(flash.message().to_string())); - }; - - // template_dir is set in Rocket.toml - Template::render("settings/admin/add_admin", &context.into_json()) -} - #[post("/add", data = "")] pub fn add_admin_post(add_admin_form: Form, _auth: Authenticated) -> Flash { let result = save_add_admin_form(add_admin_form.into_inner()); let url = uri!("/settings/admin/configure"); match result { - Ok(_) => Flash::success(Redirect::to(url), "Successfully added new admin"), - Err(_) => Flash::error(Redirect::to(url), "Failed to add new admin"), + Ok(_) => Flash::success(Redirect::to(url), "Added SSB administrator"), + Err(e) => Flash::error(Redirect::to(url), format!("Failed to add new admin: {}", e)), } } // HELPERS AND ROUTES FOR /settings/admin/delete -#[derive(Debug, Deserialize, FromForm)] +#[derive(Debug, FromForm)] pub struct DeleteAdminForm { pub ssb_id: String, } @@ -131,9 +111,12 @@ pub fn delete_admin_post( _auth: Authenticated, ) -> Flash { let result = config_manager::delete_ssb_admin_id(&delete_admin_form.ssb_id); - let url = uri!(configure_admin); + let url = uri!("/settings/admin", configure_admin); match result { - Ok(_) => Flash::success(Redirect::to(url), "Successfully removed admin id"), - Err(_) => Flash::error(Redirect::to(url), "Failed to remove admin id"), + Ok(_) => Flash::success(Redirect::to(url), "Removed SSB administrator"), + Err(e) => Flash::error( + Redirect::to(url), + format!("Failed to remove admin id: {}", e), + ), } } diff --git a/peach-web/src/routes/settings/dns.rs b/peach-web/src/routes/settings/dns.rs index 4d50672..376a115 100644 --- a/peach-web/src/routes/settings/dns.rs +++ b/peach-web/src/routes/settings/dns.rs @@ -3,7 +3,6 @@ use rocket::{ form::{Form, FromForm}, get, post, request::FlashMessage, - serde::Deserialize, }; use rocket_dyn_templates::Template; @@ -19,7 +18,7 @@ use crate::{ utils, }; -#[derive(Debug, Deserialize, FromForm)] +#[derive(Debug, FromForm)] pub struct DnsForm { pub external_domain: String, pub enable_dyndns: bool, diff --git a/peach-web/src/routes/settings/network.rs b/peach-web/src/routes/settings/network.rs index 14f0530..0e67c15 100644 --- a/peach-web/src/routes/settings/network.rs +++ b/peach-web/src/routes/settings/network.rs @@ -4,7 +4,6 @@ use rocket::{ get, post, request::FlashMessage, response::{Flash, Redirect}, - serde::Deserialize, uri, UriDisplayQuery, }; use rocket_dyn_templates::{tera::Context, Template}; @@ -21,12 +20,12 @@ use crate::{ // STRUCTS USED BY NETWORK ROUTES -#[derive(Debug, Deserialize, FromForm, UriDisplayQuery)] +#[derive(Debug, FromForm, UriDisplayQuery)] pub struct Ssid { pub ssid: String, } -#[derive(Debug, Deserialize, FromForm)] +#[derive(Debug, FromForm)] pub struct WiFi { pub ssid: String, pub pass: String, diff --git a/peach-web/src/routes/settings/scuttlebutt.rs b/peach-web/src/routes/settings/scuttlebutt.rs index 8e364d4..54b66f7 100644 --- a/peach-web/src/routes/settings/scuttlebutt.rs +++ b/peach-web/src/routes/settings/scuttlebutt.rs @@ -10,14 +10,13 @@ use rocket::{ get, post, request::FlashMessage, response::{Flash, Redirect}, - serde::Deserialize, }; use rocket_dyn_templates::{tera::Context, Template}; use crate::routes::authentication::Authenticated; use crate::utils; -#[derive(Debug, Deserialize, FromForm)] +#[derive(Debug, FromForm)] pub struct SbotConfigForm { /// Directory path for the log and indexes. repo: String, diff --git a/peach-web/src/tests.rs b/peach-web/src/tests.rs index f635360..3e6f7c6 100644 --- a/peach-web/src/tests.rs +++ b/peach-web/src/tests.rs @@ -476,13 +476,7 @@ fn scuttlebutt_settings_menu_html() { assert_eq!(response.content_type(), Some(ContentType::HTML)); let body = response.into_string().unwrap(); assert!(body.contains("Scuttlebutt Settings")); - assert!(body.contains("Set Network Key")); - assert!(body.contains("Set Replication Hops")); - assert!(body.contains("Remove Blocked Feeds")); - assert!(body.contains("Set Database Directory")); - assert!(body.contains("Check Filesystem")); - assert!(body.contains("Repair Filesystem")); - assert!(body.contains("Restart Sbot")); + assert!(body.contains("Configure Sbot")); } // STATUS HTML ROUTES diff --git a/peach-web/static/css/_variables.css b/peach-web/static/css/_variables.css index 3f5adc3..57411d8 100644 --- a/peach-web/static/css/_variables.css +++ b/peach-web/static/css/_variables.css @@ -170,6 +170,7 @@ --background: var(--moon-gray); --button-background: var(--light-gray); + --button-border: var(--near-black); --circle-background: var(--light-gray); --circle-small-hover-background: var(--white); @@ -180,6 +181,8 @@ --capsule-background: var(--light-gray); + --list-background: var(--light-gray); + --text-color-normal: var(--near-black); --text-color-gray: var(--mid-gray); --text-color-light-gray: var(--moon-gray); @@ -191,20 +194,15 @@ /* DARK THEME VARIABLES */ - /*--background-dark: var(--dark-gray);*/ --background-dark: #222; + --button-background-dark: var(--mid-gray); - /*--capsule-background-dark: var(--light-gray);*/ + --button-border-dark: var(--silver); + --capsule-background-dark: #333; + --circle-background-dark: var(--silver); --circle-small-hover-background-dark: var(--moon-gray); + --list-background-dark: #333; } -/* we need to add shades for each accent colour - * - * --info-100 - * --info-200 - * --info-300 -> var(--blue) - * --info-400 - * --info-500 - */ diff --git a/peach-web/static/css/peachcloud.css b/peach-web/static/css/peachcloud.css index 676d43a..850bb00 100644 --- a/peach-web/static/css/peachcloud.css +++ b/peach-web/static/css/peachcloud.css @@ -186,7 +186,7 @@ body { */ .button { - border: 1px solid var(--near-black); + border: 1px solid var(--button-border); border-radius: var(--border-radius-2); /* Needed to render inputs & buttons of equal width */ -moz-box-sizing: border-box; @@ -199,7 +199,8 @@ body { text-decoration: none; font-size: var(--font-size-5); font-family: var(--sans-serif); - width: 80%; + /* width: 80%; */ + width: 16rem; margin-top: 5px; margin-bottom: 5px; } @@ -316,6 +317,12 @@ body { } } +@media only screen and (min-width: 600px) { + .card-wide { + width: 30rem; + } +} + .card-container { justify-content: center; padding: 0.5rem; @@ -338,7 +345,7 @@ body { color: var(--text-color); margin: 0; font-size: var(--font-size-5); - padding-bottom: 0.3rem; + padding-bottom: 0.5rem; } .container { @@ -607,11 +614,13 @@ html { --background: var(--background); --button-background: var(--button-background); + --button-border: var(--button-border); --button-text-color: var(--text-color-normal); --button-text-hover-color: var(--text-color-normal); --circle-background: var(--circle-background); --circle-small-hover-background: var(--circle-small-hover-background); --icon-color: var(--icon-normal); + --list-background: var(--list-background); --nav-icon-color: var(--nav-icon-color); --text-color: var(--text-color-normal); } @@ -619,12 +628,14 @@ html { html[data-theme='dark'] { --background: var(--background-dark); --button-background: var(--button-background-dark); + --button-border: var(--button-border-dark); --button-text-color: var(--text-color-light); --button-text-hover-color: var(--text-color-normal); --capsule-background: var(--capsule-background-dark); --circle-background: var(--circle-background-dark); --circle-small-hover-background: var(--circle-small-hover-background-dark); --icon-color: var(--icon-light); + --list-background: var(--list-background-dark); --nav-icon-color: var(--nav-icon-color-light); --text-color: var(--text-color-light); --text-color-gray: var(--text-color-light-gray); @@ -720,7 +731,7 @@ form { -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; - margin-top: 0.5rem; + /* margin-top: 0.5rem; */ margin-bottom: 1rem; padding-left: 5px; line-height: 1.5rem; @@ -738,6 +749,7 @@ form { .message-input { height: 7rem; + margin-top: 0.5rem; overflow: auto; resize: vertical; width: 100%; @@ -770,7 +782,6 @@ form { } .label-medium { - color: var(--text-color); font-size: var(--font-size-3); display: block; } @@ -796,7 +807,11 @@ form { .link { text-decoration: none; - /* color: var(--font-near-black); */ + color: var(--text-color); +} + +.link:hover { + text-decoration: underline; } /* @@ -805,14 +820,20 @@ form { .list { padding-left: 0; - margin-left: 0; - max-width: var(--max-width-6); + width: 90%; border: 1px solid var(--light-silver); border-radius: var(--border-radius-2); list-style-type: none; font-family: var(--sans-serif); } +@media only screen and (min-width: 600px) { + .list { + width: 100%; + max-width: var(--max-width-6); + } +} + .list-container { width: var(--max-width-5); } @@ -825,6 +846,7 @@ form { } .list-item { + background-color: var(--list-background); display: grid; padding: 1rem; border-bottom-color: var(--light-silver); diff --git a/peach-web/templates/guide.html.tera b/peach-web/templates/guide.html.tera new file mode 100644 index 0000000..5481b1a --- /dev/null +++ b/peach-web/templates/guide.html.tera @@ -0,0 +1,33 @@ +{%- extends "nav" -%} +{%- block card %} + +
+
+ +
+ Getting started +

The Scuttlebutt server (sbot) will be inactive when you first run PeachCloud. This is to allow configuration parameters to be set before it is activated for the first time. Navigate to the Sbot Configuration page to configure your system. The default configuration will be fine for most usecases.

+

Once the configuration is set, navigate to the Scuttlebutt settings menu to start the sbot. If the server starts successfully, you will see a green smiley face on the home page. If the face is orange and sleeping, that means the sbot is still inactive (ie. the process is not running). If the face is red and dead, that means the sbot failed to start - indicated an error. For now, the best way to gain insight into the problem is to check the systemd log. Open a terminal and enter: systemctl --user status go-sbot.service. The log output may give some clues about the source of the error.

+
+ +
+ Submit a bug report +

Bug reports can be submitted by filing an issue on the peach-workspace git repo. Before filing a report, first check to see if an issue already exists for the bug you've encountered. If not, you're invited to submit a new report; the template will guide you through several questions.

+
+ +
+ Share feedback & request support +

You're invited to share your thoughts and experiences of PeachCloud in the #peachcloud channel on Scuttlebutt. The channel is also a good place to ask for help.

+

Alternatively, we have a Matrix channel for discussion about PeachCloud and you can also reach out to @glyph via email.

+
+ +
+ Contribute to PeachCloud +

PeachCloud is free, open-source software and relies on donations and grants to fund develop. Donations can be made on our Open Collective page.

+

Programmers, designers, artists and writers are also welcome to contribute to the project. Please visit the main PeachCloud git repository to find out more details or contact the team via Scuttlebutt, Matrix or email.

+
+
+ + {% include "snippets/flash_message" %} +
+{%- endblock card -%} diff --git a/peach-web/templates/help.html.tera b/peach-web/templates/help.html.tera deleted file mode 100644 index 0109d1e..0000000 --- a/peach-web/templates/help.html.tera +++ /dev/null @@ -1,10 +0,0 @@ -{%- extends "nav" -%} -{%- block card %} - -
-
- - {% include "snippets/flash_message" %} -
-
-{%- endblock card -%} diff --git a/peach-web/templates/home.html.tera b/peach-web/templates/home.html.tera index 9013c7c..27ff7de 100644 --- a/peach-web/templates/home.html.tera +++ b/peach-web/templates/home.html.tera @@ -52,14 +52,14 @@ - +
- +
diff --git a/peach-web/templates/login.html.tera b/peach-web/templates/login.html.tera index 31d7ccf..6235a96 100644 --- a/peach-web/templates/login.html.tera +++ b/peach-web/templates/login.html.tera @@ -2,22 +2,19 @@ {%- block card %}
-
-
- - + +
- - {% include "snippets/flash_message" %} - -
+
- {%- endblock card -%} diff --git a/peach-web/templates/scuttlebutt/inactive.html.tera b/peach-web/templates/scuttlebutt/inactive.html.tera new file mode 100644 index 0000000..beeb692 --- /dev/null +++ b/peach-web/templates/scuttlebutt/inactive.html.tera @@ -0,0 +1,11 @@ +{%- extends "nav" -%} +{%- block card %} + +
+
+

Sbot Inactive

+

{{ unavailable_msg }}

+

Visit the Scuttlebutt settings menu to start the Sbot and then try again.

+
+
+{%- endblock card -%} diff --git a/peach-web/templates/scuttlebutt/peers.html.tera b/peach-web/templates/scuttlebutt/peers.html.tera index 23d667f..5409bf7 100644 --- a/peach-web/templates/scuttlebutt/peers.html.tera +++ b/peach-web/templates/scuttlebutt/peers.html.tera @@ -2,6 +2,8 @@ {%- block card %}
+ {# only render the peer menu elements if the sbot is active #} + {%- if sbot_state == "active" %}
@@ -12,5 +14,8 @@ Invites
+ {%- endif %} + + {% include "snippets/flash_message" %}
{%- endblock card -%} diff --git a/peach-web/templates/scuttlebutt/peers_list.html.tera b/peach-web/templates/scuttlebutt/peers_list.html.tera index c2dcfb4..0599b8c 100644 --- a/peach-web/templates/scuttlebutt/peers_list.html.tera +++ b/peach-web/templates/scuttlebutt/peers_list.html.tera @@ -1,24 +1,30 @@ {%- extends "nav" -%} {%- block card %}
- {%- if peers %} -
{%- endblock card -%} diff --git a/peach-web/templates/scuttlebutt/private.html.tera b/peach-web/templates/scuttlebutt/private.html.tera index 70177b9..fcb7924 100644 --- a/peach-web/templates/scuttlebutt/private.html.tera +++ b/peach-web/templates/scuttlebutt/private.html.tera @@ -1,7 +1,7 @@ {%- extends "nav" -%} {%- block card %} -
+
{# only render the private message elements if the sbot is active #} {%- if sbot_status and sbot_status.state == "active" %}
diff --git a/peach-web/templates/scuttlebutt/profile.html.tera b/peach-web/templates/scuttlebutt/profile.html.tera index 3d0b4b3..8507de3 100644 --- a/peach-web/templates/scuttlebutt/profile.html.tera +++ b/peach-web/templates/scuttlebutt/profile.html.tera @@ -1,7 +1,7 @@ {%- extends "nav" -%} {%- block card %} -
+
{# only render the profile info elements if the sbot is active #} {%- if sbot_status and sbot_status.state == "active" %} @@ -19,7 +19,7 @@ Profile picture {% else %} {# render a placeholder profile picture (icon) #} - Profile picture + Profile picture {% endif %}

{{ name }}

@@ -38,12 +38,12 @@
{% if following == false %} - + {% elif following == true %} -
+
@@ -51,19 +51,21 @@

Unable to determine follow state

{% endif %} {% if blocking == false %} -
+
{% elif blocking == true %} -
+
{% else %}

Unable to determine block state

{% endif %} - Send Private Message +
+ Send Private Message +
{%- endif %} {%- endif %} diff --git a/peach-web/templates/scuttlebutt/search.html.tera b/peach-web/templates/scuttlebutt/search.html.tera index d5e508d..f4582dc 100644 --- a/peach-web/templates/scuttlebutt/search.html.tera +++ b/peach-web/templates/scuttlebutt/search.html.tera @@ -9,8 +9,8 @@
+ + {% include "snippets/flash_message" %} - - {% include "snippets/flash_message" %}
{%- endblock card -%} diff --git a/peach-web/templates/scuttlebutt/update_profile.html.tera b/peach-web/templates/scuttlebutt/update_profile.html.tera index 00c5199..3637c67 100644 --- a/peach-web/templates/scuttlebutt/update_profile.html.tera +++ b/peach-web/templates/scuttlebutt/update_profile.html.tera @@ -3,7 +3,7 @@ {# ASSIGN VARIABLES #} {# ---------------- #} -
+
@@ -11,7 +11,7 @@ - +
diff --git a/peach-web/templates/settings/admin/add_admin.html.tera b/peach-web/templates/settings/admin/add_admin.html.tera deleted file mode 100644 index 158bad9..0000000 --- a/peach-web/templates/settings/admin/add_admin.html.tera +++ /dev/null @@ -1,17 +0,0 @@ -{%- extends "nav" -%} -{%- block card %} - -
-
- - -
- - Cancel -
- - - {% include "snippets/flash_message" %} -
-
-{%- endblock card -%} diff --git a/peach-web/templates/settings/admin/change_password.html.tera b/peach-web/templates/settings/admin/change_password.html.tera index b3b0749..3308593 100644 --- a/peach-web/templates/settings/admin/change_password.html.tera +++ b/peach-web/templates/settings/admin/change_password.html.tera @@ -3,13 +3,17 @@
- - - - - - -
+
+ + + + + + + + + + Cancel
diff --git a/peach-web/templates/settings/admin/configure_admin.html.tera b/peach-web/templates/settings/admin/configure_admin.html.tera index 0127e1b..fa1843a 100644 --- a/peach-web/templates/settings/admin/configure_admin.html.tera +++ b/peach-web/templates/settings/admin/configure_admin.html.tera @@ -2,25 +2,31 @@ {%- block card %}
-
-

Current Admins

- {% if not ssb_admin_ids %} -
- There are no currently configured admins. -
- {% else %} - {% for admin in ssb_admin_ids %} -
- - - {{ admin }} - +
Administrators are identified and added by their Scuttlebutt public keys. These accounts will be sent private messages on Scuttlebutt when a password reset is requested.
+ {% if not ssb_admin_ids %} +
+ There are no currently configured admins. +
+ {% else %} + {% for admin in ssb_admin_ids %} +
+
+ +

{{ admin }}

+
- {% endfor %} - {% endif %} - Add Admin -
- - {% include "snippets/flash_message" %} + + {% endfor %} + {% endif %} +
+
+ + +
+ + + + {% include "snippets/flash_message" %} +
{%- endblock card -%} diff --git a/peach-web/templates/settings/admin/forgot_password.html.tera b/peach-web/templates/settings/admin/forgot_password.html.tera index f0a254e..9f1e662 100644 --- a/peach-web/templates/settings/admin/forgot_password.html.tera +++ b/peach-web/templates/settings/admin/forgot_password.html.tera @@ -2,14 +2,15 @@ {%- block card %}
-
-

Click the button below to send a new temporary password which can be used to change your device password. -

- The temporary password will be sent in an SSB private message to the admin of this device.

+
+

Click the 'Send Temporary Password' button to send a new temporary password which can be used to change your device password.

+

The temporary password will be sent in an SSB private message to the admin of this device.

+

Once you have the temporary password, click the 'Set New Password' button to reach the password reset page.

-
+
diff --git a/peach-web/templates/settings/admin/menu.html.tera b/peach-web/templates/settings/admin/menu.html.tera index 111cb73..dbd62b5 100644 --- a/peach-web/templates/settings/admin/menu.html.tera +++ b/peach-web/templates/settings/admin/menu.html.tera @@ -4,8 +4,9 @@ {%- endblock card -%} diff --git a/peach-web/templates/settings/admin/reset_password.html.tera b/peach-web/templates/settings/admin/reset_password.html.tera index 0d4d4b6..7fb4bd9 100644 --- a/peach-web/templates/settings/admin/reset_password.html.tera +++ b/peach-web/templates/settings/admin/reset_password.html.tera @@ -2,41 +2,22 @@ {%- block card %}
-
-
-
- - -
- -
- - -
- -
- - -
- -
- -
-
- - {% include "snippets/flash_message" %} -
+
+
+ + + + + + + + + + + +
+
+ + {% include "snippets/flash_message" %}
{%- endblock card -%} diff --git a/peach-web/templates/settings/scuttlebutt/menu.html.tera b/peach-web/templates/settings/scuttlebutt/menu.html.tera index a2f5513..93fb2d3 100644 --- a/peach-web/templates/settings/scuttlebutt/menu.html.tera +++ b/peach-web/templates/settings/scuttlebutt/menu.html.tera @@ -11,8 +11,6 @@ {% else %} Start Sbot {% endif %} - Check Filesystem - Remove Blocked Feeds
{% include "snippets/flash_message" %} diff --git a/peach-web/templates/status/scuttlebutt.html.tera b/peach-web/templates/status/scuttlebutt.html.tera index 998efad..7e1e4d5 100644 --- a/peach-web/templates/status/scuttlebutt.html.tera +++ b/peach-web/templates/status/scuttlebutt.html.tera @@ -5,12 +5,12 @@ {%- if sbot_status.memory -%} {% set mem = sbot_status.memory / 1024 / 1024 | round | int -%} {%- else -%} - {% set mem = "X" -%} + {% set mem = "0" -%} {%- endif -%} {%- if sbot_status.blobstore -%} {% set blobs = sbot_status.blobstore / 1024 / 1024 | round | int -%} {%- else -%} - {% set blobs = "X" -%} + {% set blobs = "0" -%} {%- endif -%}
@@ -54,8 +54,12 @@
+ {% if sbot_status.state == "active" %} + {% else %} + + {% endif %}
@@ -65,22 +69,22 @@
- +
- - + +
- +