diff --git a/Cargo.lock b/Cargo.lock index cce9243..fd9babd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -364,6 +364,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "ammonia" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89eac85170f4b3fb3dc5e442c1cfb036cb8eecf9dbbd431a161ffad15d90ea3b" +dependencies = [ + "html5ever", + "lazy_static", + "maplit", + "markup5ever_rcdom", + "matches", + "tendril", + "url", +] + [[package]] name = "ansi_term" version = "0.11.0" @@ -930,6 +945,16 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" +[[package]] +name = "futf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c9c1ce3fa9336301af935ab852c437817d14cd33690446569392e65170aac3b" +dependencies = [ + "mac", + "new_debug_unreachable", +] + [[package]] name = "futures" version = "0.3.4" @@ -1118,6 +1143,20 @@ dependencies = [ "winapi 0.3.8", ] +[[package]] +name = "html5ever" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aafcf38a1a36118242d29b92e1b08ef84e67e4a5ed06e0a80be20e6a32bfed6b" +dependencies = [ + "log", + "mac", + "markup5ever", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "http" version = "0.2.0" @@ -1339,6 +1378,47 @@ dependencies = [ "linked-hash-map 0.5.2", ] +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + +[[package]] +name = "markup5ever" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aae38d669396ca9b707bfc3db254bc382ddb94f57cc5c235f34623a669a01dab" +dependencies = [ + "log", + "phf", + "phf_codegen", + "serde 1.0.105", + "serde_derive", + "serde_json", + "string_cache", + "string_cache_codegen", + "tendril", +] + +[[package]] +name = "markup5ever_rcdom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f015da43bcd8d4f144559a3423f4591d69b8ce0652c905374da7205df336ae2b" +dependencies = [ + "html5ever", + "markup5ever", + "tendril", + "xml5ever", +] + [[package]] name = "match_cfg" version = "0.1.0" @@ -1437,6 +1517,12 @@ dependencies = [ "winapi 0.3.8", ] +[[package]] +name = "new_debug_unreachable" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" + [[package]] name = "nodrop" version = "0.1.14" @@ -1600,6 +1686,26 @@ dependencies = [ "phf_shared", ] +[[package]] +name = "phf_codegen" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" +dependencies = [ + "phf_shared", + "rand", +] + [[package]] name = "phf_shared" version = "0.8.0" @@ -1680,6 +1786,12 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + [[package]] name = "pretty_env_logger" version = "0.4.0" @@ -1763,6 +1875,7 @@ dependencies = [ "rand_chacha", "rand_core", "rand_hc", + "rand_pcg", ] [[package]] @@ -1793,6 +1906,15 @@ dependencies = [ "rand_core", ] +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core", +] + [[package]] name = "redox_syscall" version = "0.1.56" @@ -1826,12 +1948,14 @@ dependencies = [ "actix-rt", "actix-web", "actix-webfinger", + "ammonia", "anyhow", "async-trait", "background-jobs", "background-jobs-core", "base64 0.12.0", "bb8-postgres", + "bytes", "config", "dotenv", "env_logger", @@ -2246,6 +2370,31 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" +[[package]] +name = "string_cache" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2940c75beb4e3bf3a494cef919a747a2cb81e52571e212bfbd185074add7208a" +dependencies = [ + "lazy_static", + "new_debug_unreachable", + "phf_shared", + "precomputed-hash", + "serde 1.0.105", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f24c8e5e19d22a726626f1a5e16fe15b132dcf21d10177fa5a45ce7962996b97" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", +] + [[package]] name = "stringprep" version = "0.1.2" @@ -2332,6 +2481,17 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "tendril" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707feda9f2582d5d680d733e38755547a3e8fb471e7ba11452ecfd9ce93a5d3b" +dependencies = [ + "futf", + "mac", + "utf-8", +] + [[package]] name = "termcolor" version = "1.1.0" @@ -2636,6 +2796,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "utf-8" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7" + [[package]] name = "uuid" version = "0.8.1" @@ -2815,6 +2981,18 @@ dependencies = [ "winapi-build", ] +[[package]] +name = "xml5ever" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b1b52e6e8614d4a58b8e70cf51ec0cc21b256ad8206708bcff8139b5bbd6a59" +dependencies = [ + "log", + "mac", + "markup5ever", + "time 0.1.42", +] + [[package]] name = "yaml-rust" version = "0.4.3" diff --git a/Cargo.toml b/Cargo.toml index 7854da9..ee5e057 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,9 +19,11 @@ actix-rt = "1.0.0" actix-web = { version = "3.0.0-alpha.1", features = ["rustls"] } actix-webfinger = "0.3.0-alpha.3" activitystreams = "0.5.0-alpha.11" +ammonia = "3.1.0" async-trait = "0.1.24" background-jobs = { version = "0.8.0-alpha.0", git = "https://git.asonix.dog/Aardwolf/background-jobs", default-features = false, features = ["background-jobs-actix"] } background-jobs-core = { version = "0.7.0", git = "https://git.asonix.dog/Aardwolf/background-jobs" } +bytes = "0.5.4" base64 = "0.12" bb8-postgres = { version = "0.4.0", features = ["with-serde_json-1", "with-uuid-0_8", "with-chrono-0_4"] } config = "0.10.1" diff --git a/scss/index.scss b/scss/index.scss index f89982a..624a322 100644 --- a/scss/index.scss +++ b/scss/index.scss @@ -7,32 +7,69 @@ body { padding-bottom: 96px; } +ul { + margin: 0; + padding: 0; + list-style: none; +} + body, body * { box-sizing: border-box; } header { - padding: 32px 0; background-color: #333; color: #f5f5f5; - text-align: center; + + .header-text { + max-width: 700px; + margin: auto; + padding: 24px 0; + } h1 { margin: 0px; } + + p { + margin: 0; + margin-top: 8px; + font-style: italic; + } } section { - padding: 24px; background-color: #fff; border: 1px solid #e5e5e5; + box-shadow: 0 0 3px rgba(0, 0, 0, 0.1); border-radius: 3px; margin: 32px auto 0; max-width: 700px; + padding-bottom: 32px; + + > p:first-child { + margin-top: 0; + } h3 { - margin-top: 0px; + padding: 24px; + margin: 0px; + } + + li { + padding-top: 24px; + padding-bottom: 24px; + border-top: 1px solid #e5e5e5; + } + + .padded { + padding: 0 24px; + } + + .joining { + padding: 24px; + border-top: 1px solid #e5e5e5; } } @@ -57,6 +94,15 @@ pre { } } +a { + &, + &:focus, + &:hover, + &:active { + color: #ea7fbc; + } +} + footer { background-color: #333; color: #f5f5f5; @@ -67,16 +113,78 @@ footer { right: 0; text-align: center; - a { - &, - &:focus, - &:hover, - &:active { - color: #ea7fbc; - } - } - p { margin: 0; } } + +.instance, +.info { + h4 { + font-size: 20px; + margin: 0; + } + + .instance-info { + background-color: #f5f5f5; + border-top: 1px solid #e5e5e5; + border-bottom: 1px solid #e5e5e5; + padding: 32px; + + .instance-description { + margin: 0; + margin-bottom: 24px; + } + } + + a { + text-decoration: none; + } +} + +.admin { + margin-top: 32px; + display: flex; + align-items: center; + background-color: #fff; + border: 1px solid #e5e5e5; + border-radius: 3px; + box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.1); + + .display-name { + font-weight: 600; + font-size: 16px; + margin: 0; + } + + .username { + font-size: 14px; + color: #777; + margin: 0; + margin-top: 8px; + } +} + +.avatar { + width: 80px; + height: 80px; + + img { + width: 100%; + border-radius: 40px; + border: 1px solid #333; + background-color: #f5f5f5; + box-shadow: 0px 0px 3px rgba(0, 0, 0, 0.1); + } +} + +@media(max-width: 700px) { + header .header-text { + padding: 24px; + } + + section { + border-left: none; + border-right: none; + } +} diff --git a/src/config.rs b/src/config.rs index edeacb4..820f70d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -28,6 +28,7 @@ pub enum UrlKind { Inbox, Index, MainKey, + Media(Uuid), NodeInfo, Outbox, } @@ -136,6 +137,7 @@ impl Config { UrlKind::Inbox => format!("{}://{}/inbox", scheme, self.hostname), UrlKind::Index => format!("{}://{}/", scheme, self.hostname), UrlKind::MainKey => format!("{}://{}/actor#main-key", scheme, self.hostname), + UrlKind::Media(uuid) => format!("{}://{}/media/{}", scheme, self.hostname, uuid), UrlKind::NodeInfo => format!("{}://{}/nodeinfo/2.0.json", scheme, self.hostname), UrlKind::Outbox => format!("{}://{}/outbox", scheme, self.hostname), } diff --git a/src/data/media.rs b/src/data/media.rs new file mode 100644 index 0000000..e854ec4 --- /dev/null +++ b/src/data/media.rs @@ -0,0 +1,60 @@ +use activitystreams::primitives::XsdAnyUri; +use bytes::Bytes; +use lru::LruCache; +use std::{collections::HashMap, sync::Arc, time::Duration}; +use tokio::sync::{Mutex, RwLock}; +use ttl_cache::TtlCache; +use uuid::Uuid; + +static MEDIA_DURATION: Duration = Duration::from_secs(60 * 60 * 24 * 2); + +#[derive(Clone)] +pub struct Media { + inverse: Arc>>, + url_cache: Arc>>, + byte_cache: Arc>>, +} + +impl Media { + pub fn new() -> Self { + Media { + inverse: Arc::new(Mutex::new(HashMap::new())), + url_cache: Arc::new(Mutex::new(LruCache::new(128))), + byte_cache: Arc::new(RwLock::new(TtlCache::new(128))), + } + } + + pub async fn get_uuid(&self, url: &XsdAnyUri) -> Option { + let uuid = self.inverse.lock().await.get(url).cloned()?; + + if self.url_cache.lock().await.contains(&uuid) { + return Some(uuid); + } + + self.inverse.lock().await.remove(url); + + None + } + + pub async fn get_url(&self, uuid: Uuid) -> Option { + self.url_cache.lock().await.get(&uuid).cloned() + } + + pub async fn get_bytes(&self, uuid: Uuid) -> Option<(String, Bytes)> { + self.byte_cache.read().await.get(&uuid).cloned() + } + + pub async fn store_url(&self, url: &XsdAnyUri) -> Uuid { + let uuid = Uuid::new_v4(); + self.inverse.lock().await.insert(url.clone(), uuid); + self.url_cache.lock().await.put(uuid, url.clone()); + uuid + } + + pub async fn store_bytes(&self, uuid: Uuid, content_type: String, bytes: Bytes) { + self.byte_cache + .write() + .await + .insert(uuid, (content_type, bytes), MEDIA_DURATION); + } +} diff --git a/src/data/mod.rs b/src/data/mod.rs index 9af1f01..dd373bf 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -1,9 +1,11 @@ mod actor; +mod media; mod node; mod state; pub use self::{ actor::{Actor, ActorCache}, - node::{Node, NodeCache}, + media::Media, + node::{Contact, Info, Instance, Node, NodeCache}, state::State, }; diff --git a/src/error.rs b/src/error.rs index b9aff00..4515681 100644 --- a/src/error.rs +++ b/src/error.rs @@ -70,6 +70,9 @@ pub enum MyError { #[error("Hosts don't match, {0}, {1}")] HostMismatch(String, String), + #[error("Invalid or missing content type")] + ContentType, + #[error("Couldn't flush buffer")] FlushBuffer, diff --git a/src/jobs/instance.rs b/src/jobs/instance.rs index a74e327..8de0185 100644 --- a/src/jobs/instance.rs +++ b/src/jobs/instance.rs @@ -1,4 +1,4 @@ -use crate::jobs::JobState; +use crate::{config::UrlKind, jobs::JobState}; use activitystreams::primitives::XsdAnyUri; use anyhow::Error; use background_jobs::{Job, Processor}; @@ -44,7 +44,14 @@ impl QueryInstance { instance.description }; - if let Some(contact) = instance.contact { + if let Some(mut contact) = instance.contact { + if let Some(uuid) = state.media.get_uuid(&contact.avatar).await { + contact.avatar = state.config.generate_url(UrlKind::Media(uuid)).parse()?; + } else { + let uuid = state.media.store_url(&contact.avatar).await; + contact.avatar = state.config.generate_url(UrlKind::Media(uuid)).parse()?; + } + state .node_cache .set_contact( @@ -57,6 +64,8 @@ impl QueryInstance { .await?; } + let description = ammonia::clean(&description); + state .node_cache .set_instance( diff --git a/src/jobs/mod.rs b/src/jobs/mod.rs index 19cc788..802a0f4 100644 --- a/src/jobs/mod.rs +++ b/src/jobs/mod.rs @@ -9,7 +9,8 @@ pub use self::{ }; use crate::{ - data::{ActorCache, NodeCache, State}, + config::Config, + data::{ActorCache, Media, NodeCache, State}, db::Db, error::MyError, jobs::{ @@ -33,17 +34,31 @@ pub fn create_server(db: Db) -> JobServer { JobServer::new(shared) } -pub fn create_workers(state: State, actors: ActorCache, job_server: JobServer) { +pub fn create_workers( + state: State, + actors: ActorCache, + job_server: JobServer, + media: Media, + config: Config, +) { let remote_handle = job_server.remote.clone(); - WorkerConfig::new(move || JobState::new(state.clone(), actors.clone(), job_server.clone())) - .register(DeliverProcessor) - .register(DeliverManyProcessor) - .register(NodeinfoProcessor) - .register(InstanceProcessor) - .register(ListenersProcessor) - .set_processor_count("default", 4) - .start(remote_handle); + WorkerConfig::new(move || { + JobState::new( + state.clone(), + actors.clone(), + job_server.clone(), + media.clone(), + config.clone(), + ) + }) + .register(DeliverProcessor) + .register(DeliverManyProcessor) + .register(NodeinfoProcessor) + .register(InstanceProcessor) + .register(ListenersProcessor) + .set_processor_count("default", 4) + .start(remote_handle); } #[derive(Clone)] @@ -51,6 +66,8 @@ pub struct JobState { requests: Requests, state: State, actors: ActorCache, + config: Config, + media: Media, node_cache: NodeCache, job_server: JobServer, } @@ -61,11 +78,19 @@ pub struct JobServer { } impl JobState { - fn new(state: State, actors: ActorCache, job_server: JobServer) -> Self { + fn new( + state: State, + actors: ActorCache, + job_server: JobServer, + media: Media, + config: Config, + ) -> Self { JobState { requests: state.requests(), node_cache: state.node_cache(), actors, + config, + media, state, job_server, } diff --git a/src/main.rs b/src/main.rs index ca39a7f..799d046 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,7 +16,7 @@ mod routes; use self::{ args::Args, config::Config, - data::{ActorCache, State}, + data::{ActorCache, Media, State}, db::Db, jobs::{create_server, create_workers}, middleware::RelayResolver, @@ -62,6 +62,7 @@ async fn main() -> Result<(), anyhow::Error> { return Ok(()); } + let media = Media::new(); let state = State::hydrate(config.clone(), &db).await?; let actors = ActorCache::new(db.clone()); let job_server = create_server(db.clone()); @@ -84,9 +85,11 @@ async fn main() -> Result<(), anyhow::Error> { let state = state.clone(); let actors = actors.clone(); let job_server = job_server.clone(); + let media = media.clone(); + let config = config.clone(); Arbiter::new().exec_fn(move || { - create_workers(state, actors, job_server); + create_workers(state, actors, job_server, media, config); }); } actix_rt::signal::ctrl_c().await?; @@ -98,7 +101,13 @@ async fn main() -> Result<(), anyhow::Error> { let bind_address = config.bind_address(); HttpServer::new(move || { if !no_jobs { - create_workers(state.clone(), actors.clone(), job_server.clone()); + create_workers( + state.clone(), + actors.clone(), + job_server.clone(), + media.clone(), + config.clone(), + ); } App::new() @@ -109,7 +118,9 @@ async fn main() -> Result<(), anyhow::Error> { .data(actors.clone()) .data(config.clone()) .data(job_server.clone()) + .data(media.clone()) .service(web::resource("/").route(web::get().to(index))) + .service(web::resource("/media/{path}").route(web::get().to(routes::media))) .service( web::resource("/inbox") .wrap(config.digest_middleware()) diff --git a/src/requests.rs b/src/requests.rs index 569c366..c78b252 100644 --- a/src/requests.rs +++ b/src/requests.rs @@ -1,6 +1,7 @@ use crate::error::MyError; use activitystreams::primitives::XsdAnyUri; use actix_web::client::Client; +use bytes::Bytes; use http_signature_normalization_actix::prelude::*; use log::error; use rsa::{hash::Hashes, padding::PaddingScheme, RSAPrivateKey}; @@ -63,6 +64,55 @@ impl Requests { }) } + pub async fn fetch_bytes(&self, url: &str) -> Result<(String, Bytes), MyError> { + let mut res = self + .client + .get(url) + .header("Accept", "application/activity+json") + .header("User-Agent", self.user_agent.as_str()) + .signature(&self.config, &self.key_id, |signing_string| { + self.sign(signing_string) + })? + .send() + .await + .map_err(|e| { + error!("Couldn't send request to {}, {}", url, e); + MyError::SendRequest + })?; + + let content_type = if let Some(content_type) = res.headers().get("content-type") { + if let Ok(s) = content_type.to_str() { + s.to_owned() + } else { + return Err(MyError::ContentType); + } + } else { + return Err(MyError::ContentType); + }; + + if !res.status().is_success() { + if let Ok(bytes) = res.body().await { + if let Ok(s) = String::from_utf8(bytes.as_ref().to_vec()) { + if !s.is_empty() { + error!("Response, {}", s); + } + } + } + + return Err(MyError::Status(res.status())); + } + + let bytes = match res.body().limit(1024 * 1024 * 4).await { + Err(e) => { + error!("Coudn't fetch json from {}, {}", url, e); + return Err(MyError::ReceiveResponse); + } + Ok(bytes) => bytes, + }; + + Ok((content_type, bytes)) + } + pub async fn deliver(&self, inbox: XsdAnyUri, item: &T) -> Result<(), MyError> where T: serde::ser::Serialize, diff --git a/src/routes/index.rs b/src/routes/index.rs index 470d63b..6d9e564 100644 --- a/src/routes/index.rs +++ b/src/routes/index.rs @@ -8,7 +8,6 @@ pub async fn route( config: web::Data, ) -> Result { let nodes = state.node_cache().nodes().await; - let mut buf = BufWriter::new(Vec::new()); crate::templates::index(&mut buf, &nodes, &config)?; diff --git a/src/routes/media.rs b/src/routes/media.rs new file mode 100644 index 0000000..64b4b43 --- /dev/null +++ b/src/routes/media.rs @@ -0,0 +1,27 @@ +use crate::{data::Media, error::MyError, requests::Requests}; +use actix_web::{web, HttpResponse}; +use uuid::Uuid; + +pub async fn route( + media: web::Data, + requests: web::Data, + uuid: web::Path, +) -> Result { + let uuid = uuid.into_inner(); + + if let Some((content_type, bytes)) = media.get_bytes(uuid).await { + return Ok(HttpResponse::Ok().content_type(content_type).body(bytes)); + } + + if let Some(url) = media.get_url(uuid).await { + let (content_type, bytes) = requests.fetch_bytes(url.as_str()).await?; + + media + .store_bytes(uuid, content_type.clone(), bytes.clone()) + .await; + + return Ok(HttpResponse::Ok().content_type(content_type).body(bytes)); + } + + Ok(HttpResponse::NotFound().finish()) +} diff --git a/src/routes/mod.rs b/src/routes/mod.rs index 75b95bf..67e5aa1 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -1,6 +1,7 @@ mod actor; mod inbox; mod index; +mod media; mod nodeinfo; mod statics; @@ -8,6 +9,7 @@ pub use self::{ actor::route as actor, inbox::route as inbox, index::route as index, + media::route as media, nodeinfo::{route as nodeinfo, well_known as nodeinfo_meta}, statics::route as statics, }; diff --git a/templates/admin.rs.html b/templates/admin.rs.html new file mode 100644 index 0000000..d88e047 --- /dev/null +++ b/templates/admin.rs.html @@ -0,0 +1,15 @@ +@use crate::data::Contact; + +@(contact: &Contact) + +
+
+
+ @contact.display_name's avatar +
+
+
+

@contact.display_name

+

@@@contact.username

+
+
diff --git a/templates/index.rs.html b/templates/index.rs.html index becfef1..96f0f99 100644 --- a/templates/index.rs.html +++ b/templates/index.rs.html @@ -1,4 +1,8 @@ -@use crate::{config::{Config, UrlKind}, templates::statics::index_css, data::Node}; +@use crate::{ + config::{Config, UrlKind}, + data::Node, + templates::{info, instance, statics::index_css}, +}; @(nodes: &[Node], config: &Config) @@ -11,28 +15,29 @@
-

Welcome to @config.software_name() on @config.hostname()

+
+

@config.software_name()

+

on @config.hostname()

+
-

Connected Servers:

+

Connected Servers

@if nodes.is_empty() {

There are no connected servers at this time.

} else {
    @for node in nodes { - @if let Some(domain) = node.base.as_url().domain() { + @if let Some(inst) = node.instance.as_ref() {
  • -

    @domain

    - @if let Some(info) = node.info.as_ref() { -

    Running @info.software version @info.version

    - @if info.reg { -

    Registration is open

    - } else { -

    Registration is closed

    - } - } + @:instance(inst, node.info.as_ref().map(|info| { info.software.as_ref() }), node.contact.as_ref(), &node.base)
  • + } else { + @if let Some(inf) = node.info.as_ref() { +
  • + @:info(&inf, &node.base) +
  • + } } }
@@ -40,27 +45,29 @@

Joining

-

- If you are the admin of a server that supports activitypub relays, you can add - this relay to your server. -

-

Mastodon

-

- Mastodon admins can add this relay by adding -

@config.generate_url(UrlKind::Inbox)
in their relay settings. -

-

Pleroma

-

- Pleroma admins can add this relay by adding -

@config.generate_url(UrlKind::Actor)
- to their relay settings (I don't actually know how pleroma handles adding - relays, is it still a mix command?). -

-

Others

-

- Consult the documentation for your server. It's likely that it follows either - Mastodon or Pleroma's relay formatting. -

+
+

+ If you are the admin of a server that supports activitypub relays, you can add + this relay to your server. +

+

Mastodon

+

+ Mastodon admins can add this relay by adding +

@config.generate_url(UrlKind::Inbox)
in their relay settings. +

+

Pleroma

+

+ Pleroma admins can add this relay by adding +

@config.generate_url(UrlKind::Actor)
+ to their relay settings (I don't actually know how pleroma handles adding + relays, is it still a mix command?). +

+

Others

+

+ Consult the documentation for your server. It's likely that it follows either + Mastodon or Pleroma's relay formatting. +

+