Merge pull request 'Integrate golgi into peach_web' (#81) from golgi_integration into main
Reviewed-on: #81
This commit is contained in:
commit
10049f0bc6
File diff suppressed because it is too large
Load Diff
|
@ -35,7 +35,11 @@ travis-ci = { repository = "peachcloud/peach-web", branch = "master" }
|
|||
maintenance = { status = "actively-developed" }
|
||||
|
||||
[dependencies]
|
||||
base64 = "0.13.0"
|
||||
dirs = "4.0.0"
|
||||
env_logger = "0.8"
|
||||
#golgi = "0.1.0"
|
||||
golgi = { path = "../../../playground/rust/golgi" }
|
||||
lazy_static = "1.4.0"
|
||||
log = "0.4"
|
||||
nest = "1.0.0"
|
||||
|
@ -45,6 +49,7 @@ 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"
|
||||
temporary = "0.6.4"
|
||||
tera = { version = "1.12.1", features = ["builtins"] }
|
||||
xdg = "2.2.0"
|
||||
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
pub mod dns;
|
||||
pub mod network;
|
||||
pub mod scuttlebutt;
|
||||
|
|
|
@ -0,0 +1,588 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use golgi::{api::friends::RelationshipQuery, blobs, messages::SsbMessageValue, Sbot};
|
||||
use peach_lib::sbot::{SbotConfig, SbotStatus};
|
||||
use rocket::{futures::TryStreamExt, serde::Serialize};
|
||||
|
||||
use crate::{error::PeachWebError, utils};
|
||||
|
||||
// HELPER FUNCTIONS
|
||||
|
||||
pub async fn init_sbot_with_config(
|
||||
sbot_config: &Option<SbotConfig>,
|
||||
) -> Result<Sbot, PeachWebError> {
|
||||
// initialise sbot connection with ip:port and shscap from config file
|
||||
let 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?
|
||||
}
|
||||
None => Sbot::init(None, None).await?,
|
||||
};
|
||||
|
||||
Ok(sbot_client)
|
||||
}
|
||||
|
||||
// CONTEXT STRUCTS AND BUILDERS
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct StatusContext {
|
||||
pub back: Option<String>,
|
||||
pub flash_name: Option<String>,
|
||||
pub flash_msg: Option<String>,
|
||||
pub title: Option<String>,
|
||||
pub theme: Option<String>,
|
||||
pub sbot_config: Option<SbotConfig>,
|
||||
pub sbot_status: Option<SbotStatus>,
|
||||
// latest sequence number for the local log
|
||||
pub latest_seq: Option<u64>,
|
||||
}
|
||||
|
||||
impl StatusContext {
|
||||
pub fn default() -> Self {
|
||||
StatusContext {
|
||||
back: Some("/".to_string()),
|
||||
flash_name: None,
|
||||
flash_msg: None,
|
||||
title: Some("Scuttlebutt Status".to_string()),
|
||||
theme: None,
|
||||
sbot_config: None,
|
||||
sbot_status: None,
|
||||
latest_seq: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn build() -> Result<Self, PeachWebError> {
|
||||
let mut context = Self::default();
|
||||
|
||||
// retrieve current ui theme
|
||||
context.theme = Some(utils::get_theme());
|
||||
|
||||
// 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();
|
||||
|
||||
let mut sbot_client = init_sbot_with_config(&sbot_config).await?;
|
||||
|
||||
// retrieve the local id
|
||||
let id = sbot_client.whoami().await?;
|
||||
|
||||
let history_stream = sbot_client.create_history_stream(id).await?;
|
||||
let mut msgs: Vec<SsbMessageValue> = history_stream.try_collect().await?;
|
||||
|
||||
// reverse the list of messages so we can easily reference the latest one
|
||||
msgs.reverse();
|
||||
|
||||
// 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_status = Some(sbot_status);
|
||||
|
||||
Ok(context)
|
||||
}
|
||||
}
|
||||
|
||||
// peers who are blocked by the local account
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct BlocksContext {
|
||||
pub back: Option<String>,
|
||||
pub flash_name: Option<String>,
|
||||
pub flash_msg: Option<String>,
|
||||
pub title: Option<String>,
|
||||
pub theme: Option<String>,
|
||||
pub sbot_config: Option<SbotConfig>,
|
||||
pub sbot_status: Option<SbotStatus>,
|
||||
pub peers: Option<Vec<HashMap<String, String>>>,
|
||||
}
|
||||
|
||||
impl BlocksContext {
|
||||
pub fn default() -> Self {
|
||||
BlocksContext {
|
||||
back: Some("/scuttlebutt/peers".to_string()),
|
||||
flash_name: None,
|
||||
flash_msg: None,
|
||||
title: Some("Blocks".to_string()),
|
||||
theme: None,
|
||||
sbot_config: None,
|
||||
sbot_status: None,
|
||||
peers: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn build() -> Result<Self, PeachWebError> {
|
||||
let mut context = Self::default();
|
||||
|
||||
// retrieve current ui theme
|
||||
context.theme = Some(utils::get_theme());
|
||||
|
||||
// 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();
|
||||
|
||||
let mut sbot_client = init_sbot_with_config(&sbot_config).await?;
|
||||
|
||||
let blocks = sbot_client.get_blocks().await?;
|
||||
|
||||
// we'll use this to store the profile info for each peer who follows us
|
||||
let mut peer_info = Vec::new();
|
||||
|
||||
if !blocks.is_empty() {
|
||||
for peer in blocks.iter() {
|
||||
// trim whitespace (including newline characters) and
|
||||
// remove the inverted-commas around the id
|
||||
let key = peer.trim().replace('"', "");
|
||||
// retrieve the profile info for the given peer
|
||||
let mut info = sbot_client.get_profile_info(&key).await?;
|
||||
// insert the public key of the peer into the info hashmap
|
||||
info.insert("id".to_string(), key.to_string());
|
||||
// we do not even attempt to find the blob for a blocked peer,
|
||||
// since it may be vulgar to cause distress to the local peer.
|
||||
info.insert("blob_exists".to_string(), "false".to_string());
|
||||
// push profile info to peer_list vec
|
||||
peer_info.push(info)
|
||||
}
|
||||
|
||||
context.peers = Some(peer_info)
|
||||
}
|
||||
} 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, peer data cannot be retrieved. Visit the Scuttlebutt settings menu to start the Sbot and then try again".to_string());
|
||||
}
|
||||
|
||||
context.sbot_status = Some(sbot_status);
|
||||
|
||||
Ok(context)
|
||||
}
|
||||
}
|
||||
|
||||
// peers who are followed by the local account
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct FollowsContext {
|
||||
pub back: Option<String>,
|
||||
pub flash_name: Option<String>,
|
||||
pub flash_msg: Option<String>,
|
||||
pub title: Option<String>,
|
||||
pub theme: Option<String>,
|
||||
pub sbot_config: Option<SbotConfig>,
|
||||
pub sbot_status: Option<SbotStatus>,
|
||||
pub peers: Option<Vec<HashMap<String, String>>>,
|
||||
}
|
||||
|
||||
impl FollowsContext {
|
||||
pub fn default() -> Self {
|
||||
FollowsContext {
|
||||
back: Some("/scuttlebutt/peers".to_string()),
|
||||
flash_name: None,
|
||||
flash_msg: None,
|
||||
title: Some("Follows".to_string()),
|
||||
theme: None,
|
||||
sbot_config: None,
|
||||
sbot_status: None,
|
||||
peers: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn build() -> Result<Self, PeachWebError> {
|
||||
let mut context = Self::default();
|
||||
|
||||
// retrieve current ui theme
|
||||
context.theme = Some(utils::get_theme());
|
||||
|
||||
// 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();
|
||||
|
||||
let mut sbot_client = init_sbot_with_config(&sbot_config).await?;
|
||||
|
||||
let follows = sbot_client.get_follows().await?;
|
||||
|
||||
// we'll use this to store the profile info for each peer who follows us
|
||||
let mut peer_info = Vec::new();
|
||||
|
||||
if !follows.is_empty() {
|
||||
for peer in follows.iter() {
|
||||
// trim whitespace (including newline characters) and
|
||||
// remove the inverted-commas around the id
|
||||
let key = peer.trim().replace('"', "");
|
||||
// retrieve the profile info for the given peer
|
||||
let mut info = sbot_client.get_profile_info(&key).await?;
|
||||
// insert the public key of the peer into the info hashmap
|
||||
info.insert("id".to_string(), key.to_string());
|
||||
// retrieve the profile image blob id for the given peer
|
||||
if let Some(blob_id) = info.get("image") {
|
||||
// look-up the path for the image blob
|
||||
if let Ok(blob_path) = blobs::get_blob_path(&blob_id) {
|
||||
// insert the image blob path of the peer into the info hashmap
|
||||
info.insert("blob_path".to_string(), blob_path.to_string());
|
||||
// check if the blob is in the blobstore
|
||||
// set a flag in the info hashmap
|
||||
match utils::blob_is_stored_locally(&blob_path).await {
|
||||
Ok(exists) if exists == true => {
|
||||
info.insert("blob_exists".to_string(), "true".to_string())
|
||||
}
|
||||
_ => info.insert("blob_exists".to_string(), "false".to_string()),
|
||||
};
|
||||
}
|
||||
}
|
||||
// push profile info to peer_list vec
|
||||
peer_info.push(info)
|
||||
}
|
||||
|
||||
context.peers = Some(peer_info)
|
||||
}
|
||||
} 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, peer data cannot be retrieved. Visit the Scuttlebutt settings menu to start the Sbot and then try again".to_string());
|
||||
}
|
||||
|
||||
context.sbot_status = Some(sbot_status);
|
||||
|
||||
Ok(context)
|
||||
}
|
||||
}
|
||||
|
||||
// peers who follow and are followed by the local account (friends)
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct FriendsContext {
|
||||
pub back: Option<String>,
|
||||
pub flash_name: Option<String>,
|
||||
pub flash_msg: Option<String>,
|
||||
pub title: Option<String>,
|
||||
pub theme: Option<String>,
|
||||
pub sbot_config: Option<SbotConfig>,
|
||||
pub sbot_status: Option<SbotStatus>,
|
||||
pub peers: Option<Vec<HashMap<String, String>>>,
|
||||
}
|
||||
|
||||
impl FriendsContext {
|
||||
pub fn default() -> Self {
|
||||
FriendsContext {
|
||||
back: Some("/scuttlebutt/peers".to_string()),
|
||||
flash_name: None,
|
||||
flash_msg: None,
|
||||
title: Some("Friends".to_string()),
|
||||
theme: None,
|
||||
sbot_config: None,
|
||||
sbot_status: None,
|
||||
peers: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn build() -> Result<Self, PeachWebError> {
|
||||
let mut context = Self::default();
|
||||
|
||||
// retrieve current ui theme
|
||||
context.theme = Some(utils::get_theme());
|
||||
|
||||
// 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();
|
||||
|
||||
let mut sbot_client = init_sbot_with_config(&sbot_config).await?;
|
||||
|
||||
let local_id = sbot_client.whoami().await?;
|
||||
|
||||
let follows = sbot_client.get_follows().await?;
|
||||
|
||||
// we'll use this to store the profile info for each peer who follows us
|
||||
let mut peer_info = Vec::new();
|
||||
|
||||
if !follows.is_empty() {
|
||||
for peer in follows.iter() {
|
||||
// trim whitespace (including newline characters) and
|
||||
// remove the inverted-commas around the id
|
||||
let peer_id = peer.trim().replace('"', "");
|
||||
// retrieve the profile info for the given peer
|
||||
let mut info = sbot_client.get_profile_info(&peer_id).await?;
|
||||
// insert the public key of the peer into the info hashmap
|
||||
info.insert("id".to_string(), peer_id.to_string());
|
||||
// retrieve the profile image blob id for the given peer
|
||||
if let Some(blob_id) = info.get("image") {
|
||||
// look-up the path for the image blob
|
||||
if let Ok(blob_path) = blobs::get_blob_path(&blob_id) {
|
||||
// insert the image blob path of the peer into the info hashmap
|
||||
info.insert("blob_path".to_string(), blob_path.to_string());
|
||||
// check if the blob is in the blobstore
|
||||
// set a flag in the info hashmap
|
||||
match utils::blob_is_stored_locally(&blob_path).await {
|
||||
Ok(exists) if exists == true => {
|
||||
info.insert("blob_exists".to_string(), "true".to_string())
|
||||
}
|
||||
_ => info.insert("blob_exists".to_string(), "false".to_string()),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// check if the peer follows us (making us friends)
|
||||
let follow_query = RelationshipQuery {
|
||||
source: peer_id.to_string(),
|
||||
dest: local_id.clone(),
|
||||
};
|
||||
|
||||
// query follow state
|
||||
match sbot_client.friends_is_following(follow_query).await {
|
||||
Ok(following) if following == "true" => {
|
||||
// only push profile info to peer_list vec if they follow us
|
||||
peer_info.push(info)
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
}
|
||||
|
||||
context.peers = Some(peer_info)
|
||||
}
|
||||
} 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, peer data cannot be retrieved. Visit the Scuttlebutt settings menu to start the Sbot and then try again".to_string());
|
||||
}
|
||||
|
||||
context.sbot_status = Some(sbot_status);
|
||||
|
||||
Ok(context)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct ProfileContext {
|
||||
pub back: Option<String>,
|
||||
pub flash_name: Option<String>,
|
||||
pub flash_msg: Option<String>,
|
||||
pub title: Option<String>,
|
||||
pub theme: Option<String>,
|
||||
pub sbot_config: Option<SbotConfig>,
|
||||
pub sbot_status: Option<SbotStatus>,
|
||||
// is this the local profile or the profile of a peer?
|
||||
pub is_local_profile: bool,
|
||||
// an ssb_id which may or may not be the local public key
|
||||
pub id: Option<String>,
|
||||
pub name: Option<String>,
|
||||
pub description: Option<String>,
|
||||
pub image: Option<String>,
|
||||
// the path to the blob defined in the `image` field (aka the profile picture)
|
||||
pub blob_path: Option<String>,
|
||||
// whether or not the blob exists in the blobstore (ie. is saved on disk)
|
||||
pub blob_exists: bool,
|
||||
// relationship state (if the profile being viewed is not for the local public key)
|
||||
pub following: Option<bool>,
|
||||
pub blocking: Option<bool>,
|
||||
}
|
||||
|
||||
impl ProfileContext {
|
||||
pub fn default() -> Self {
|
||||
ProfileContext {
|
||||
back: Some("/".to_string()),
|
||||
flash_name: None,
|
||||
flash_msg: None,
|
||||
title: Some("Profile".to_string()),
|
||||
theme: None,
|
||||
sbot_config: None,
|
||||
sbot_status: None,
|
||||
is_local_profile: true,
|
||||
id: None,
|
||||
name: None,
|
||||
description: None,
|
||||
image: None,
|
||||
blob_path: None,
|
||||
blob_exists: false,
|
||||
following: None,
|
||||
blocking: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn build(ssb_id: Option<String>) -> Result<Self, PeachWebError> {
|
||||
let mut context = Self::default();
|
||||
|
||||
// retrieve current ui theme
|
||||
context.theme = Some(utils::get_theme());
|
||||
|
||||
// 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();
|
||||
|
||||
let mut sbot_client = init_sbot_with_config(&sbot_config).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;
|
||||
|
||||
// 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
|
||||
};
|
||||
|
||||
// TODO: add relationship state context if not local profile
|
||||
// ie. lookup is_following and is_blocking, set context accordingly
|
||||
|
||||
// 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,
|
||||
}
|
||||
}
|
||||
} 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());
|
||||
}
|
||||
|
||||
context.sbot_status = Some(sbot_status);
|
||||
|
||||
Ok(context)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct PrivateContext {
|
||||
pub back: Option<String>,
|
||||
pub flash_name: Option<String>,
|
||||
pub flash_msg: Option<String>,
|
||||
pub title: Option<String>,
|
||||
pub theme: Option<String>,
|
||||
pub sbot_config: Option<SbotConfig>,
|
||||
pub sbot_status: Option<SbotStatus>,
|
||||
// local peer id (whoami)
|
||||
pub id: Option<String>,
|
||||
// id of the peer being messaged
|
||||
pub recipient_id: Option<String>,
|
||||
}
|
||||
|
||||
impl PrivateContext {
|
||||
pub fn default() -> Self {
|
||||
PrivateContext {
|
||||
back: Some("/".to_string()),
|
||||
flash_name: None,
|
||||
flash_msg: None,
|
||||
title: Some("Private Messages".to_string()),
|
||||
theme: None,
|
||||
sbot_config: None,
|
||||
sbot_status: None,
|
||||
id: None,
|
||||
recipient_id: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn build(recipient_id: Option<String>) -> Result<Self, PeachWebError> {
|
||||
let mut context = Self::default();
|
||||
|
||||
// retrieve current ui theme
|
||||
context.theme = Some(utils::get_theme());
|
||||
|
||||
// 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();
|
||||
|
||||
let mut sbot_client = init_sbot_with_config(&sbot_config).await?;
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
context.sbot_status = Some(sbot_status);
|
||||
|
||||
Ok(context)
|
||||
}
|
||||
}
|
|
@ -1,5 +1,8 @@
|
|||
//! Custom error type representing all possible error variants for peach-web.
|
||||
|
||||
use std::io::Error as IoError;
|
||||
|
||||
use golgi::GolgiError;
|
||||
use peach_lib::error::PeachError;
|
||||
use peach_lib::{serde_json, serde_yaml};
|
||||
use serde_json::error::Error as JsonError;
|
||||
|
@ -8,19 +11,27 @@ use serde_yaml::Error as YamlError;
|
|||
/// Custom error type encapsulating all possible errors for the web application.
|
||||
#[derive(Debug)]
|
||||
pub enum PeachWebError {
|
||||
Json(JsonError),
|
||||
Yaml(YamlError),
|
||||
FailedToRegisterDynDomain(String),
|
||||
Golgi(GolgiError),
|
||||
HomeDir,
|
||||
Io(IoError),
|
||||
Json(JsonError),
|
||||
OsString,
|
||||
PeachLib { source: PeachError, msg: String },
|
||||
Yaml(YamlError),
|
||||
}
|
||||
|
||||
impl std::error::Error for PeachWebError {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
match *self {
|
||||
PeachWebError::Json(ref source) => Some(source),
|
||||
PeachWebError::Yaml(ref source) => Some(source),
|
||||
PeachWebError::FailedToRegisterDynDomain(_) => None,
|
||||
PeachWebError::Golgi(ref source) => Some(source),
|
||||
PeachWebError::HomeDir => None,
|
||||
PeachWebError::Io(ref source) => Some(source),
|
||||
PeachWebError::Json(ref source) => Some(source),
|
||||
PeachWebError::OsString => None,
|
||||
PeachWebError::PeachLib { ref source, .. } => Some(source),
|
||||
PeachWebError::Yaml(ref source) => Some(source),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,28 +39,44 @@ impl std::error::Error for PeachWebError {
|
|||
impl std::fmt::Display for PeachWebError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match *self {
|
||||
PeachWebError::Json(ref source) => write!(f, "Serde JSON error: {}", source),
|
||||
PeachWebError::Yaml(ref source) => write!(f, "Serde YAML error: {}", source),
|
||||
PeachWebError::FailedToRegisterDynDomain(ref msg) => {
|
||||
write!(f, "DYN DNS error: {}", msg)
|
||||
}
|
||||
PeachWebError::Golgi(ref source) => write!(f, "Golgi error: {}", source),
|
||||
PeachWebError::HomeDir => write!(
|
||||
f,
|
||||
"Filesystem error: failed to determine home directory path"
|
||||
),
|
||||
PeachWebError::Io(ref source) => write!(f, "IO error: {}", source),
|
||||
PeachWebError::Json(ref source) => write!(f, "Serde JSON error: {}", source),
|
||||
PeachWebError::OsString => write!(
|
||||
f,
|
||||
"Filesystem error: failed to convert OsString to String for go-ssb directory path"
|
||||
),
|
||||
PeachWebError::PeachLib { ref source, .. } => write!(f, "{}", source),
|
||||
PeachWebError::Yaml(ref source) => write!(f, "Serde YAML error: {}", source),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<GolgiError> for PeachWebError {
|
||||
fn from(err: GolgiError) -> PeachWebError {
|
||||
PeachWebError::Golgi(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IoError> for PeachWebError {
|
||||
fn from(err: IoError) -> PeachWebError {
|
||||
PeachWebError::Io(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<JsonError> for PeachWebError {
|
||||
fn from(err: JsonError) -> PeachWebError {
|
||||
PeachWebError::Json(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<YamlError> for PeachWebError {
|
||||
fn from(err: YamlError) -> PeachWebError {
|
||||
PeachWebError::Yaml(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PeachError> for PeachWebError {
|
||||
fn from(err: PeachError) -> PeachWebError {
|
||||
PeachWebError::PeachLib {
|
||||
|
@ -58,3 +85,9 @@ impl From<PeachError> for PeachWebError {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<YamlError> for PeachWebError {
|
||||
fn from(err: YamlError) -> PeachWebError {
|
||||
PeachWebError::Yaml(err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,8 +22,6 @@
|
|||
//! `Serialize`. The fields of the context object are available in the context
|
||||
//! of the template to be rendered.
|
||||
|
||||
#![feature(proc_macro_hygiene, decl_macro)]
|
||||
|
||||
mod context;
|
||||
pub mod error;
|
||||
mod router;
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
use rocket::{catchers, fs::FileServer, routes, Build, Rocket};
|
||||
use rocket_dyn_templates::Template;
|
||||
|
||||
use crate::routes::{
|
||||
authentication::*,
|
||||
catchers::*,
|
||||
index::*,
|
||||
scuttlebutt::*,
|
||||
settings::{admin::*, dns::*, menu::*, network::*, scuttlebutt::*, theme::*},
|
||||
status::{device::*, network::*, scuttlebutt::*},
|
||||
use crate::{
|
||||
routes::{
|
||||
authentication::*,
|
||||
catchers::*,
|
||||
index::*,
|
||||
scuttlebutt::*,
|
||||
settings::{admin::*, dns::*, menu::*, network::*, scuttlebutt::*, theme::*},
|
||||
status::{device::*, network::*, scuttlebutt::*},
|
||||
},
|
||||
utils,
|
||||
};
|
||||
|
||||
/// Create a Rocket instance and mount PeachPub routes, fileserver and
|
||||
|
@ -15,6 +18,10 @@ use crate::routes::{
|
|||
/// settings and status routes related to networking and the device (memory,
|
||||
/// hard disk, CPU etc.).
|
||||
pub fn mount_peachpub_routes(rocket: Rocket<Build>) -> Rocket<Build> {
|
||||
// set the `.ssb-go` path in order to mount the blob fileserver
|
||||
let ssb_path = utils::get_go_ssb_path().expect("define ssb-go dir path");
|
||||
let blobstore = format!("{}/blobs/sha256", ssb_path);
|
||||
|
||||
rocket
|
||||
.mount(
|
||||
"/",
|
||||
|
@ -62,12 +69,29 @@ pub fn mount_peachpub_routes(rocket: Rocket<Build>) -> Rocket<Build> {
|
|||
.mount(
|
||||
"/scuttlebutt",
|
||||
routes![
|
||||
peers, friends, follows, followers, blocks, profile, private, follow, unfollow,
|
||||
block, publish,
|
||||
invites,
|
||||
create_invite,
|
||||
peers,
|
||||
search,
|
||||
search_post,
|
||||
friends,
|
||||
follows,
|
||||
blocks,
|
||||
profile,
|
||||
update_profile,
|
||||
update_profile_post,
|
||||
private,
|
||||
private_post,
|
||||
follow,
|
||||
unfollow,
|
||||
block,
|
||||
unblock,
|
||||
publish,
|
||||
],
|
||||
)
|
||||
.mount("/status", routes![scuttlebutt_status])
|
||||
.mount("/", FileServer::from("static"))
|
||||
.mount("/blob", FileServer::from(blobstore).rank(-1))
|
||||
.register("/", catchers![not_found, internal_error, forbidden])
|
||||
.attach(Template::fairing())
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,40 +1,37 @@
|
|||
use peach_lib::sbot::{SbotConfig, SbotStatus};
|
||||
use rocket::{get, State};
|
||||
use rocket_dyn_templates::{tera::Context, Template};
|
||||
use rocket_dyn_templates::Template;
|
||||
|
||||
use crate::routes::authentication::Authenticated;
|
||||
use crate::utils;
|
||||
use crate::RocketConfig;
|
||||
use crate::{context::scuttlebutt::StatusContext, RocketConfig};
|
||||
|
||||
// HELPERS AND ROUTES FOR /status/scuttlebutt
|
||||
|
||||
#[get("/scuttlebutt")]
|
||||
pub fn scuttlebutt_status(_auth: Authenticated, config: &State<RocketConfig>) -> Template {
|
||||
// retrieve current ui theme
|
||||
let theme = utils::get_theme();
|
||||
pub async fn scuttlebutt_status(_auth: Authenticated, config: &State<RocketConfig>) -> Template {
|
||||
let context = StatusContext::build().await;
|
||||
|
||||
// retrieve go-sbot systemd process status
|
||||
let sbot_status = SbotStatus::read().ok();
|
||||
|
||||
// retrieve go-sbot configuration parameters
|
||||
let sbot_config = SbotConfig::read().ok();
|
||||
|
||||
let mut context = Context::new();
|
||||
context.insert("theme", &theme);
|
||||
context.insert("sbot_status", &sbot_status);
|
||||
context.insert("sbot_config", &sbot_config);
|
||||
context.insert("flash_name", &None::<()>);
|
||||
context.insert("flash_msg", &None::<()>);
|
||||
context.insert("title", &Some("Scuttlebutt Status"));
|
||||
|
||||
// define back arrow url based on mode
|
||||
if config.standalone_mode {
|
||||
let back = if config.standalone_mode {
|
||||
// return to home page
|
||||
context.insert("back", &Some("/"));
|
||||
Some("/".to_string())
|
||||
} else {
|
||||
// return to status menu
|
||||
context.insert("back", &Some("/status"));
|
||||
}
|
||||
Some("/status".to_string())
|
||||
};
|
||||
|
||||
Template::render("status/scuttlebutt", &context.into_json())
|
||||
match context {
|
||||
Ok(mut context) => {
|
||||
// define back arrow url based on mode
|
||||
context.back = back;
|
||||
|
||||
Template::render("status/scuttlebutt", &context)
|
||||
}
|
||||
Err(_) => {
|
||||
let mut context = StatusContext::default();
|
||||
|
||||
// define back arrow url based on mode
|
||||
context.back = back;
|
||||
|
||||
Template::render("status/scuttlebutt", &context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,88 @@
|
|||
pub mod monitor;
|
||||
|
||||
use log::info;
|
||||
use rocket::response::{Redirect, Responder};
|
||||
use rocket::serde::Serialize;
|
||||
use rocket_dyn_templates::Template;
|
||||
use std::io::prelude::*;
|
||||
use std::{fs, fs::File, path::Path};
|
||||
|
||||
use crate::THEME;
|
||||
use dirs;
|
||||
use golgi::blobs;
|
||||
use log::info;
|
||||
use peach_lib::sbot::SbotConfig;
|
||||
use rocket::{
|
||||
fs::TempFile,
|
||||
response::{Redirect, Responder},
|
||||
serde::Serialize,
|
||||
};
|
||||
use rocket_dyn_templates::Template;
|
||||
use temporary::Directory;
|
||||
|
||||
use crate::{error::PeachWebError, THEME};
|
||||
|
||||
// FILEPATH FUNCTIONS
|
||||
|
||||
// return the path of the ssb-go directory
|
||||
pub fn get_go_ssb_path() -> Result<String, PeachWebError> {
|
||||
let go_ssb_path = match SbotConfig::read() {
|
||||
Ok(conf) => conf.repo,
|
||||
// return the default path if unable to read `config.toml`
|
||||
Err(_) => {
|
||||
// determine the home directory
|
||||
let mut home_path = dirs::home_dir().ok_or_else(|| PeachWebError::HomeDir)?;
|
||||
// add the go-ssb subdirectory
|
||||
home_path.push(".ssb-go");
|
||||
// convert the PathBuf to a String
|
||||
home_path
|
||||
.into_os_string()
|
||||
.into_string()
|
||||
.map_err(|_| PeachWebError::OsString)?
|
||||
}
|
||||
};
|
||||
|
||||
Ok(go_ssb_path)
|
||||
}
|
||||
|
||||
// check whether a blob is in the blobstore
|
||||
pub async fn blob_is_stored_locally(blob_path: &str) -> Result<bool, PeachWebError> {
|
||||
let go_ssb_path = get_go_ssb_path()?;
|
||||
let complete_path = format!("{}/blobs/sha256/{}", go_ssb_path, blob_path);
|
||||
let blob_exists_locally = Path::new(&complete_path).exists();
|
||||
|
||||
Ok(blob_exists_locally)
|
||||
}
|
||||
|
||||
// take the path to a file, add it to the blobstore and return the blob id
|
||||
pub async fn write_blob_to_store(file: &mut TempFile<'_>) -> Result<String, PeachWebError> {
|
||||
// create temporary directory and path
|
||||
let temp_dir = Directory::new("blob")?;
|
||||
// we performed a `file.name().is_some()` check before calling `write_blob_to_store`
|
||||
// so it should be safe to do a simple unwrap here
|
||||
let filename = file.name().expect("retrieving filename from uploaded file");
|
||||
let temp_path = temp_dir.join(filename);
|
||||
|
||||
// write file to temporary path
|
||||
file.persist_to(&temp_path).await?;
|
||||
|
||||
// open the file and read it into a buffer
|
||||
let mut file = File::open(&temp_path)?;
|
||||
let mut buffer = Vec::new();
|
||||
file.read_to_end(&mut buffer)?;
|
||||
|
||||
// hash the bytes representing the file
|
||||
let (hex_hash, blob_id) = blobs::hash_blob(&buffer)?;
|
||||
|
||||
// define the blobstore path and blob filename
|
||||
let (blob_dir, blob_filename) = hex_hash.split_at(2);
|
||||
let go_ssb_path = get_go_ssb_path()?;
|
||||
let blobstore_sub_dir = format!("{}/blobs/sha256/{}", go_ssb_path, blob_dir);
|
||||
|
||||
// create the blobstore sub-directory
|
||||
fs::create_dir_all(&blobstore_sub_dir)?;
|
||||
|
||||
// copy the file to the blobstore
|
||||
let blob_path = format!("{}/{}", blobstore_sub_dir, blob_filename);
|
||||
fs::copy(temp_path, blob_path)?;
|
||||
|
||||
Ok(blob_id)
|
||||
}
|
||||
|
||||
// THEME FUNCTIONS
|
||||
|
||||
|
|
|
@ -277,6 +277,18 @@ body {
|
|||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.capsule-profile {
|
||||
margin-left: 1rem;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 600px) {
|
||||
.capsule-profile {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 600px) {
|
||||
.capsule-container {
|
||||
margin-left: 0;
|
||||
|
@ -728,6 +740,7 @@ form {
|
|||
height: 7rem;
|
||||
overflow: auto;
|
||||
resize: vertical;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.alert-input {
|
||||
|
@ -753,7 +766,7 @@ form {
|
|||
font-family: var(--sans-serif);
|
||||
font-size: var(--font-size-7);
|
||||
display: block;
|
||||
/* margin-bottom: 2px; */
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.label-medium {
|
||||
|
|
Before Width: | Height: | Size: 957 B After Width: | Height: | Size: 957 B |
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
|
@ -0,0 +1,20 @@
|
|||
{%- extends "nav" -%}
|
||||
{%- block card %}
|
||||
<!-- SCUTTLEBUTT INVITE FORM -->
|
||||
<div class="card center">
|
||||
<form id="invites" class="center" action="/scuttlebutt/invites" method="post">
|
||||
<div class="center" style="width: 80%;">
|
||||
<label for="inviteUses" class="label-small font-gray" title="Number of times the invite code can be reused">USES</label>
|
||||
<input type="number" id="inviteUses" name="uses" min="1" max="150" size="3" value="1">
|
||||
{% if invite_code %}
|
||||
<p class="card-text" style="margin-top: 1rem; user-select: all;" title="Invite code">{{ invite_code }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<!-- BUTTONS -->
|
||||
<input id="createInvite" class="button button-primary center" style="margin-top: 1rem;" type="submit" title="Create a new invite code" value="Create">
|
||||
<a id="cancel" class="button button-secondary center" href="/scuttlebutt/peers" title="Cancel">Cancel</a>
|
||||
</form>
|
||||
<!-- FLASH MESSAGE -->
|
||||
{% include "snippets/flash_message" %}
|
||||
</div>
|
||||
{%- endblock card -%}
|
|
@ -1,10 +0,0 @@
|
|||
{%- extends "nav" -%}
|
||||
{%- block card %}
|
||||
<!-- SCUTTLEBUTT MESSAGES -->
|
||||
<div class="card center">
|
||||
<div class="capsule capsule-container border-ssb">
|
||||
<!-- FLASH MESSAGE -->
|
||||
{% include "snippets/flash_message" %}
|
||||
</div>
|
||||
</div>
|
||||
{%- endblock card -%}
|
|
@ -5,10 +5,11 @@
|
|||
<div class="card-container">
|
||||
<!-- BUTTONS -->
|
||||
<div id="buttons">
|
||||
<a id="friends" class="button button-primary center" href="/scuttlebutt/friends" title="List Friends">Friends</a>
|
||||
<a id="follows" class="button button-primary center" href="/scuttlebutt/follows" title="List Follows">Follows</a>
|
||||
<a id="followers" class="button button-primary center" href="/scuttlebutt/followers" title="List Followers">Followers</a>
|
||||
<a id="blocks" class="button button-primary center" href="/scuttlebutt/blocks" title="List Blocks">Blocks</a>
|
||||
<a id="search" class="button button-primary center" href="/scuttlebutt/search" title="Search for a peer">Search</a>
|
||||
<a id="friends" class="button button-primary center" href="/scuttlebutt/friends" title="List friends">Friends</a>
|
||||
<a id="follows" class="button button-primary center" href="/scuttlebutt/follows" title="List follows">Follows</a>
|
||||
<a id="blocks" class="button button-primary center" href="/scuttlebutt/blocks" title="List blocks">Blocks</a>
|
||||
<a id="invites" class="button button-primary center" href="/scuttlebutt/invites" title="Create invites">Invites</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,16 +1,24 @@
|
|||
{%- extends "nav" -%}
|
||||
{%- block card %}
|
||||
<div class="card center">
|
||||
{%- if peers %}
|
||||
<ul class="list">
|
||||
<!-- for peer in peers -->
|
||||
{%- for peer in peers %}
|
||||
<li>
|
||||
<a class="list-item link light-bg" href="/scuttlebutt/profile?public_key=pub_key">
|
||||
<img id="peerImage" class="icon list-icon" src="{ image_path }" alt="{ peer_name }'s profile image">
|
||||
<p id="peerName" class="list-text">{ name }</p>
|
||||
<label class="label-small label-ellipsis list-label font-gray" for="peerName" title="{ peer_name }'s Public Key">{ public_key }</label>
|
||||
<a class="list-item link light-bg" href="/scuttlebutt/profile?public_key={{ peer['id'] }}">
|
||||
{%- if peer['blob_path'] and peer['blob_exists'] == "true" %}
|
||||
<img id="peerImage" class="icon list-icon" src="/blob/{{ peer['blob_path'] }}" alt="{{ peer['name'] }}'s profile image">
|
||||
{%- else %}
|
||||
<img id="peerImage" class="icon list-icon" src="/icons/user.svg" alt="Placeholder profile image">
|
||||
{%- endif %}
|
||||
<p id="peerName" class="font-normal list-text">{{ peer['name'] }}</p>
|
||||
<label class="label-small label-ellipsis list-label font-gray" for="peerName" title="{{ peer['name'] }}'s Public Key">{{ peer['id'] }}</label>
|
||||
</a>
|
||||
</li>
|
||||
<!-- end for loop -->
|
||||
{%- endfor %}
|
||||
</ul>
|
||||
{%- else %}
|
||||
<p>No follows found</p>
|
||||
{%- endif %}
|
||||
</div>
|
||||
{%- endblock card -%}
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
{%- extends "nav" -%}
|
||||
{%- block card %}
|
||||
<!-- SCUTTLEBUTT PRIVATE MESSAGE FORM -->
|
||||
<div class="card center">
|
||||
{# only render the private message elements if the sbot is active #}
|
||||
{%- if sbot_status and sbot_status.state == "active" %}
|
||||
<form id="sbotConfig" class="center" action="/scuttlebutt/private" method="post">
|
||||
<div class="center" style="display: flex; flex-direction: column; margin-bottom: 1rem;" title="Public key (ID) of the peer being written to">
|
||||
<label for="publicKey" class="label-small font-gray">PUBLIC KEY</label>
|
||||
<input type="text" id="publicKey" name="recipient" placeholder="@xYz...=.ed25519" {% if recipient_id %}value="{{ recipient_id }}"{% else %}autofocus{% endif %}>
|
||||
</div>
|
||||
<!-- input for message contents -->
|
||||
<textarea id="privatePost" class="center input message-input" name="text" title="Compose a private message" placeholder="Write a private message..."{% if recipient_id %} autofocus{% endif %}></textarea>
|
||||
<!-- hidden input field to pass the public key of the local peer -->
|
||||
<input type="hidden" id="localId" name="id" value="{{ id }}">
|
||||
<!-- BUTTONS -->
|
||||
<input id="publish" class="button button-primary center" type="submit" style="margin-top: 1rem;" title="Publish private message to peer" value="Publish">
|
||||
</form>
|
||||
{%- endif %}
|
||||
<!-- FLASH MESSAGE -->
|
||||
{% include "snippets/flash_message" %}
|
||||
</div>
|
||||
{%- endblock card -%}
|
|
@ -2,31 +2,71 @@
|
|||
{%- block card %}
|
||||
<!-- USER PROFILE -->
|
||||
<div class="card center">
|
||||
{# only render the profile info elements if the sbot is active #}
|
||||
{%- if sbot_status and sbot_status.state == "active" %}
|
||||
<!-- PROFILE INFO BOX -->
|
||||
<div class="capsule capsule-profile" title="Scuttlebutt account profile information">
|
||||
<div class="capsule capsule-profile border-ssb" title="Scuttlebutt account profile information">
|
||||
{% if is_local_profile %}
|
||||
<!-- edit profile button -->
|
||||
<img id="editProfile" class="icon-small icon-active nav-icon-right" src="/icons/pencil.svg" alt="Profile picture">
|
||||
<a class="nav-icon-right" href="/scuttlebutt/profile/update" title="Edit your profile">
|
||||
<img id="editProfile" class="icon-small icon-active" src="/icons/pencil.svg" alt="Edit">
|
||||
</a>
|
||||
{% endif %}
|
||||
<!-- PROFILE BIO -->
|
||||
<!-- profile picture -->
|
||||
<img id="profilePicture" class="icon-large" src="{ image_path }" alt="Profile picture">
|
||||
{# only try to render profile pic if we have the blob #}
|
||||
{%- if blob_path and blob_exists == true %}
|
||||
<img id="profilePicture" class="icon-large" src="/blob/{{ blob_path }}" title="Profile picture" alt="Profile picture">
|
||||
{% else %}
|
||||
{# render a placeholder profile picture (icon) #}
|
||||
<img id="profilePicture" class="icon" src="/icons/user.svg" title="Profile picture" alt="Profile picture">
|
||||
{% endif %}
|
||||
<!-- name, public key & description -->
|
||||
<p id="profileName" class="card-text" title="Name">{ name }</p>
|
||||
<label class="label-small label-ellipsis font-gray" style="user-select: all;" for="profileName" title="Public Key">{ public_key }</label>
|
||||
<p id="profileDescription" style="margin-top: 1rem" class="card-text" title="Description">{ description }</p>
|
||||
<p id="profileName" class="card-text" title="Name">{{ name }}</p>
|
||||
<label class="label-small label-ellipsis font-gray" style="user-select: all;" for="profileName" title="Public Key">{{ id }}</label>
|
||||
<p id="profileDescription" style="margin-top: 1rem" class="card-text" title="Description">{{ description }}</p>
|
||||
</div>
|
||||
{% if is_local_profile %}
|
||||
<!-- PUBLIC POST FORM -->
|
||||
<form id="postForm" class="center" action="/scuttlebutt/post" method="post">
|
||||
<form id="postForm" class="center" action="/scuttlebutt/publish" method="post">
|
||||
<!-- input for message contents -->
|
||||
<textarea id="publicPost" class="center input message-input" title="Compose Public Post"></textarea>
|
||||
<textarea id="publicPost" class="center input message-input" name="text" title="Compose Public Post" placeholder="Write a public post..."></textarea>
|
||||
<input id="publishPost" class="button button-primary center" title="Publish" type="submit" value="Publish">
|
||||
</form>
|
||||
{% else %}
|
||||
<!-- BUTTONS -->
|
||||
<!-- TODO: each of these buttons needs to be a form with a public key -->
|
||||
<div id="buttons" style="margin-top: 2rem;">
|
||||
<a id="followPeer" class="button button-primary center" href="/scuttlebutt/follow" title="Follow Peer">Follow</a>
|
||||
<a id="blockPeer" class="button button-warning center" href="/scuttlebutt/block" title="Block Peer">Block</a>
|
||||
<a id="privateMessage" class="button button-primary center" href="/scuttlebutt/private_message" title="Private Message">Private Message</a>
|
||||
{% if following == false %}
|
||||
<form id="followForm" action="/scuttlebutt/follow" method="post">
|
||||
<input type="hidden" id="publicKey" name="public_key" value="{{ id }}">
|
||||
<input id="followPeer" class="button button-primary center" type="submit" title="Follow Peer" value="Follow">
|
||||
</form>
|
||||
{% elif following == true %}
|
||||
<form id="unfollowForm" action="/scuttlebutt/unfollow" method="post">
|
||||
<input type="hidden" id="publicKey" name="public_key" value="{{ id }}">
|
||||
<input id="unfollowPeer" class="button button-primary center" type="submit" title="Unfollow Peer" value="Unfollow">
|
||||
</form>
|
||||
{% else %}
|
||||
<p>Unable to determine follow state</p>
|
||||
{% endif %}
|
||||
{% if blocking == false %}
|
||||
<form id="blockForm" action="/scuttlebutt/block" method="post">
|
||||
<input type="hidden" id="publicKey" name="public_key" value="{{ id }}">
|
||||
<input id="blockPeer" class="button button-primary center" type="submit" title="Block Peer" value="Block">
|
||||
</form>
|
||||
{% elif blocking == true %}
|
||||
<form id="unblockForm" action="/scuttlebutt/unblock" method="post">
|
||||
<input type="hidden" id="publicKey" name="public_key" value="{{ id }}">
|
||||
<input id="unblockPeer" class="button button-primary center" type="submit" title="Unblock Peer" value="Unblock">
|
||||
</form>
|
||||
{% else %}
|
||||
<p>Unable to determine block state</p>
|
||||
{% endif %}
|
||||
<a id="privateMessage" class="button button-primary center" href="/scuttlebutt/private?public_key={{ id }}" title="Private Message">Send Private Message</a>
|
||||
</div>
|
||||
{%- endif %}
|
||||
{%- endif %}
|
||||
<!-- FLASH MESSAGE -->
|
||||
{% include "snippets/flash_message" %}
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
{%- extends "nav" -%}
|
||||
{%- block card %}
|
||||
<!-- PEER SEARCH FORM -->
|
||||
<div class="card center">
|
||||
<form id="sbotConfig" class="center" action="/scuttlebutt/search" method="post">
|
||||
<div class="center" style="display: flex; flex-direction: column; margin-bottom: 2rem;" title="Public key (ID) of a peer">
|
||||
<label for="publicKey" class="label-small font-gray">PUBLIC KEY</label>
|
||||
<input type="text" id="publicKey" name="public_key" placeholder="@xYz...=.ed25519" autofocus>
|
||||
</div>
|
||||
<!-- BUTTONS -->
|
||||
<input id="search" class="button button-primary center" type="submit" title="Search for peer" value="Search">
|
||||
</form>
|
||||
<!-- FLASH MESSAGE -->
|
||||
{% include "snippets/flash_message" %}
|
||||
</div>
|
||||
{%- endblock card -%}
|
|
@ -0,0 +1,27 @@
|
|||
{%- extends "nav" -%}
|
||||
{%- block card %}
|
||||
{# ASSIGN VARIABLES #}
|
||||
{# ---------------- #}
|
||||
<!-- SSB PROFILE UPDATE FORM -->
|
||||
<div class="card center">
|
||||
<form id="profileInfo" class="center" enctype="multipart/form-data" action="/scuttlebutt/profile/update" method="post">
|
||||
<div style="display: flex; flex-direction: column">
|
||||
<label for="name" class="label-small font-gray">NAME</label>
|
||||
<input style="margin-bottom: 1rem;" type="text" id="name" name="new_name" placeholder="Choose a name for your profile..." value="{{ name }}">
|
||||
<label for="description" class="label-small font-gray">DESCRIPTION</label>
|
||||
<textarea id="description" class="message-input" style="margin-bottom: 1rem;" name="new_description" placeholder="Write a description for your profile...">{{ description }}</textarea>
|
||||
<label for="image" class="label-small font-gray">IMAGE</label>
|
||||
<input type="file" id="fileInput" name="image">
|
||||
</div>
|
||||
<input type="hidden" name="id" value="{{ id }}">
|
||||
<input type="hidden" name="current_name" value="{{ name }}">
|
||||
<input type="hidden" name="current_description" value="{{ description }}">
|
||||
<div id="buttonDiv" style="margin-top: 2rem;">
|
||||
<input id="updateProfile" class="button button-primary center" title="Publish" type="submit" value="Publish">
|
||||
<a class="button button-secondary center" href="/scuttlebutt/profile" title="Cancel">Cancel</a>
|
||||
</div>
|
||||
</form>
|
||||
<!-- FLASH MESSAGE -->
|
||||
{% include "snippets/flash_message" %}
|
||||
</div>
|
||||
{%- endblock card -%}
|
|
@ -5,6 +5,9 @@
|
|||
{%- elif flash_msg and flash_name == "info" %}
|
||||
<!-- display info message -->
|
||||
<div class="capsule center-text flash-message font-normal border-info">{{ flash_msg }}.</div>
|
||||
{%- elif flash_msg and flash_name == "warning" %}
|
||||
<!-- display warning message -->
|
||||
<div class="capsule center-text flash-message font-normal border-warning">{{ flash_msg }}.</div>
|
||||
{%- elif flash_msg and flash_name == "error" %}
|
||||
<!-- display error message -->
|
||||
<div class="capsule center-text flash-message font-normal border-danger">{{ flash_msg }}.</div>
|
||||
|
|
|
@ -3,12 +3,12 @@
|
|||
{# ASSIGN VARIABLES #}
|
||||
{# ---------------- #}
|
||||
{%- if sbot_status.memory -%}
|
||||
{% set mem = sbot_status.memory / 1024 / 1024 | round -%}
|
||||
{% set mem = sbot_status.memory / 1024 / 1024 | round | int -%}
|
||||
{%- else -%}
|
||||
{% set mem = "X" -%}
|
||||
{%- endif -%}
|
||||
{%- if sbot_status.blobstore -%}
|
||||
{% set blobs = sbot_status.blobstore / 1024 / 1024 | round -%}
|
||||
{% set blobs = sbot_status.blobstore / 1024 / 1024 | round | int -%}
|
||||
{%- else -%}
|
||||
{% set blobs = "X" -%}
|
||||
{%- endif -%}
|
||||
|
@ -52,33 +52,13 @@
|
|||
</div>
|
||||
<hr style="color: var(--light-gray);">
|
||||
<div id="middleSection" style="margin-top: 1rem;">
|
||||
<div id="ssbSocialCounts" class="center" style="display: flex; justify-content: space-between; width: 90%;">
|
||||
<div style="display: flex; align-items: last baseline;">
|
||||
<label class="card-text" style="margin-right: 2px;">21</label>
|
||||
<label class="label-small font-gray">Friends</label>
|
||||
</div>
|
||||
<div style="display: flex; align-items: last baseline;">
|
||||
<label class="card-text" style="margin-right: 2px;">5</label>
|
||||
<label class="label-small font-gray">Follows</label>
|
||||
</div>
|
||||
<div style="display: flex; align-items: last baseline;">
|
||||
<label class="card-text" style="margin-right: 2px;">38</label>
|
||||
<label class="label-small font-gray">Followers</label>
|
||||
</div>
|
||||
<div style="display: flex; align-items: last baseline;">
|
||||
<label class="card-text" style="margin-right: 2px;">2</label>
|
||||
<label class="label-small font-gray">Blocks</label>
|
||||
<div id="sbotInfo" class="center" style="display: flex; justify-content: space-between; width: 90%;">
|
||||
<div class="center" style="display: flex; align-items: last baseline;">
|
||||
<label class="card-text" style="margin-right: 5px;">{{ latest_seq }}</label>
|
||||
<label class="label-small font-gray">MESSAGES IN LOCAL DATABASE</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--
|
||||
not implemented yet:
|
||||
<p class="card-text">Latest sequence number: </p>
|
||||
<p class="card-text">Network key: </p>
|
||||
<p>Blobstore size: </p>
|
||||
<p>Last time you visited this page, latest sequence was x ... now it's y</p>
|
||||
<p>Number of follows / followers / friends / blocks</p>
|
||||
-->
|
||||
<hr style="color: var(--light-gray);">
|
||||
<!-- THREE-ACROSS STACK -->
|
||||
<div class="three-grid card-container" style="margin-top: 1rem;">
|
||||
|
|
Loading…
Reference in New Issue