forked from mirrors/relay
Add media cache, improve default styles
This commit is contained in:
parent
0a42450801
commit
9ada30626b
18 changed files with 613 additions and 65 deletions
178
Cargo.lock
generated
178
Cargo.lock
generated
|
@ -364,6 +364,21 @@ dependencies = [
|
||||||
"memchr",
|
"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]]
|
[[package]]
|
||||||
name = "ansi_term"
|
name = "ansi_term"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
|
@ -930,6 +945,16 @@ version = "0.3.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
|
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]]
|
[[package]]
|
||||||
name = "futures"
|
name = "futures"
|
||||||
version = "0.3.4"
|
version = "0.3.4"
|
||||||
|
@ -1118,6 +1143,20 @@ dependencies = [
|
||||||
"winapi 0.3.8",
|
"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]]
|
[[package]]
|
||||||
name = "http"
|
name = "http"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
@ -1339,6 +1378,47 @@ dependencies = [
|
||||||
"linked-hash-map 0.5.2",
|
"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]]
|
[[package]]
|
||||||
name = "match_cfg"
|
name = "match_cfg"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -1437,6 +1517,12 @@ dependencies = [
|
||||||
"winapi 0.3.8",
|
"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]]
|
[[package]]
|
||||||
name = "nodrop"
|
name = "nodrop"
|
||||||
version = "0.1.14"
|
version = "0.1.14"
|
||||||
|
@ -1600,6 +1686,26 @@ dependencies = [
|
||||||
"phf_shared",
|
"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]]
|
[[package]]
|
||||||
name = "phf_shared"
|
name = "phf_shared"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
|
@ -1680,6 +1786,12 @@ version = "0.2.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b"
|
checksum = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "precomputed-hash"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pretty_env_logger"
|
name = "pretty_env_logger"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
|
@ -1763,6 +1875,7 @@ dependencies = [
|
||||||
"rand_chacha",
|
"rand_chacha",
|
||||||
"rand_core",
|
"rand_core",
|
||||||
"rand_hc",
|
"rand_hc",
|
||||||
|
"rand_pcg",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1793,6 +1906,15 @@ dependencies = [
|
||||||
"rand_core",
|
"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]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.1.56"
|
version = "0.1.56"
|
||||||
|
@ -1826,12 +1948,14 @@ dependencies = [
|
||||||
"actix-rt",
|
"actix-rt",
|
||||||
"actix-web",
|
"actix-web",
|
||||||
"actix-webfinger",
|
"actix-webfinger",
|
||||||
|
"ammonia",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"background-jobs",
|
"background-jobs",
|
||||||
"background-jobs-core",
|
"background-jobs-core",
|
||||||
"base64 0.12.0",
|
"base64 0.12.0",
|
||||||
"bb8-postgres",
|
"bb8-postgres",
|
||||||
|
"bytes",
|
||||||
"config",
|
"config",
|
||||||
"dotenv",
|
"dotenv",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
|
@ -2246,6 +2370,31 @@ version = "0.1.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0"
|
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]]
|
[[package]]
|
||||||
name = "stringprep"
|
name = "stringprep"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
|
@ -2332,6 +2481,17 @@ dependencies = [
|
||||||
"unicode-xid",
|
"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]]
|
[[package]]
|
||||||
name = "termcolor"
|
name = "termcolor"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
|
@ -2636,6 +2796,12 @@ dependencies = [
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utf-8"
|
||||||
|
version = "0.7.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "0.8.1"
|
version = "0.8.1"
|
||||||
|
@ -2815,6 +2981,18 @@ dependencies = [
|
||||||
"winapi-build",
|
"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]]
|
[[package]]
|
||||||
name = "yaml-rust"
|
name = "yaml-rust"
|
||||||
version = "0.4.3"
|
version = "0.4.3"
|
||||||
|
|
|
@ -19,9 +19,11 @@ actix-rt = "1.0.0"
|
||||||
actix-web = { version = "3.0.0-alpha.1", features = ["rustls"] }
|
actix-web = { version = "3.0.0-alpha.1", features = ["rustls"] }
|
||||||
actix-webfinger = "0.3.0-alpha.3"
|
actix-webfinger = "0.3.0-alpha.3"
|
||||||
activitystreams = "0.5.0-alpha.11"
|
activitystreams = "0.5.0-alpha.11"
|
||||||
|
ammonia = "3.1.0"
|
||||||
async-trait = "0.1.24"
|
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 = { 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" }
|
background-jobs-core = { version = "0.7.0", git = "https://git.asonix.dog/Aardwolf/background-jobs" }
|
||||||
|
bytes = "0.5.4"
|
||||||
base64 = "0.12"
|
base64 = "0.12"
|
||||||
bb8-postgres = { version = "0.4.0", features = ["with-serde_json-1", "with-uuid-0_8", "with-chrono-0_4"] }
|
bb8-postgres = { version = "0.4.0", features = ["with-serde_json-1", "with-uuid-0_8", "with-chrono-0_4"] }
|
||||||
config = "0.10.1"
|
config = "0.10.1"
|
||||||
|
|
134
scss/index.scss
134
scss/index.scss
|
@ -7,32 +7,69 @@ body {
|
||||||
padding-bottom: 96px;
|
padding-bottom: 96px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
body,
|
body,
|
||||||
body * {
|
body * {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
header {
|
header {
|
||||||
padding: 32px 0;
|
|
||||||
background-color: #333;
|
background-color: #333;
|
||||||
color: #f5f5f5;
|
color: #f5f5f5;
|
||||||
text-align: center;
|
|
||||||
|
.header-text {
|
||||||
|
max-width: 700px;
|
||||||
|
margin: auto;
|
||||||
|
padding: 24px 0;
|
||||||
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
margin-top: 8px;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
section {
|
section {
|
||||||
padding: 24px;
|
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
border: 1px solid #e5e5e5;
|
border: 1px solid #e5e5e5;
|
||||||
|
box-shadow: 0 0 3px rgba(0, 0, 0, 0.1);
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
margin: 32px auto 0;
|
margin: 32px auto 0;
|
||||||
max-width: 700px;
|
max-width: 700px;
|
||||||
|
padding-bottom: 32px;
|
||||||
|
|
||||||
|
> p:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
h3 {
|
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 {
|
footer {
|
||||||
background-color: #333;
|
background-color: #333;
|
||||||
color: #f5f5f5;
|
color: #f5f5f5;
|
||||||
|
@ -67,16 +113,78 @@ footer {
|
||||||
right: 0;
|
right: 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
a {
|
|
||||||
&,
|
|
||||||
&:focus,
|
|
||||||
&:hover,
|
|
||||||
&:active {
|
|
||||||
color: #ea7fbc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
p {
|
||||||
margin: 0;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ pub enum UrlKind {
|
||||||
Inbox,
|
Inbox,
|
||||||
Index,
|
Index,
|
||||||
MainKey,
|
MainKey,
|
||||||
|
Media(Uuid),
|
||||||
NodeInfo,
|
NodeInfo,
|
||||||
Outbox,
|
Outbox,
|
||||||
}
|
}
|
||||||
|
@ -136,6 +137,7 @@ impl Config {
|
||||||
UrlKind::Inbox => format!("{}://{}/inbox", scheme, self.hostname),
|
UrlKind::Inbox => format!("{}://{}/inbox", scheme, self.hostname),
|
||||||
UrlKind::Index => format!("{}://{}/", scheme, self.hostname),
|
UrlKind::Index => format!("{}://{}/", scheme, self.hostname),
|
||||||
UrlKind::MainKey => format!("{}://{}/actor#main-key", 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::NodeInfo => format!("{}://{}/nodeinfo/2.0.json", scheme, self.hostname),
|
||||||
UrlKind::Outbox => format!("{}://{}/outbox", scheme, self.hostname),
|
UrlKind::Outbox => format!("{}://{}/outbox", scheme, self.hostname),
|
||||||
}
|
}
|
||||||
|
|
60
src/data/media.rs
Normal file
60
src/data/media.rs
Normal file
|
@ -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<Mutex<HashMap<XsdAnyUri, Uuid>>>,
|
||||||
|
url_cache: Arc<Mutex<LruCache<Uuid, XsdAnyUri>>>,
|
||||||
|
byte_cache: Arc<RwLock<TtlCache<Uuid, (String, Bytes)>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<Uuid> {
|
||||||
|
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<XsdAnyUri> {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,11 @@
|
||||||
mod actor;
|
mod actor;
|
||||||
|
mod media;
|
||||||
mod node;
|
mod node;
|
||||||
mod state;
|
mod state;
|
||||||
|
|
||||||
pub use self::{
|
pub use self::{
|
||||||
actor::{Actor, ActorCache},
|
actor::{Actor, ActorCache},
|
||||||
node::{Node, NodeCache},
|
media::Media,
|
||||||
|
node::{Contact, Info, Instance, Node, NodeCache},
|
||||||
state::State,
|
state::State,
|
||||||
};
|
};
|
||||||
|
|
|
@ -70,6 +70,9 @@ pub enum MyError {
|
||||||
#[error("Hosts don't match, {0}, {1}")]
|
#[error("Hosts don't match, {0}, {1}")]
|
||||||
HostMismatch(String, String),
|
HostMismatch(String, String),
|
||||||
|
|
||||||
|
#[error("Invalid or missing content type")]
|
||||||
|
ContentType,
|
||||||
|
|
||||||
#[error("Couldn't flush buffer")]
|
#[error("Couldn't flush buffer")]
|
||||||
FlushBuffer,
|
FlushBuffer,
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::jobs::JobState;
|
use crate::{config::UrlKind, jobs::JobState};
|
||||||
use activitystreams::primitives::XsdAnyUri;
|
use activitystreams::primitives::XsdAnyUri;
|
||||||
use anyhow::Error;
|
use anyhow::Error;
|
||||||
use background_jobs::{Job, Processor};
|
use background_jobs::{Job, Processor};
|
||||||
|
@ -44,7 +44,14 @@ impl QueryInstance {
|
||||||
instance.description
|
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
|
state
|
||||||
.node_cache
|
.node_cache
|
||||||
.set_contact(
|
.set_contact(
|
||||||
|
@ -57,6 +64,8 @@ impl QueryInstance {
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let description = ammonia::clean(&description);
|
||||||
|
|
||||||
state
|
state
|
||||||
.node_cache
|
.node_cache
|
||||||
.set_instance(
|
.set_instance(
|
||||||
|
|
|
@ -9,7 +9,8 @@ pub use self::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
data::{ActorCache, NodeCache, State},
|
config::Config,
|
||||||
|
data::{ActorCache, Media, NodeCache, State},
|
||||||
db::Db,
|
db::Db,
|
||||||
error::MyError,
|
error::MyError,
|
||||||
jobs::{
|
jobs::{
|
||||||
|
@ -33,10 +34,24 @@ pub fn create_server(db: Db) -> JobServer {
|
||||||
JobServer::new(shared)
|
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();
|
let remote_handle = job_server.remote.clone();
|
||||||
|
|
||||||
WorkerConfig::new(move || JobState::new(state.clone(), actors.clone(), job_server.clone()))
|
WorkerConfig::new(move || {
|
||||||
|
JobState::new(
|
||||||
|
state.clone(),
|
||||||
|
actors.clone(),
|
||||||
|
job_server.clone(),
|
||||||
|
media.clone(),
|
||||||
|
config.clone(),
|
||||||
|
)
|
||||||
|
})
|
||||||
.register(DeliverProcessor)
|
.register(DeliverProcessor)
|
||||||
.register(DeliverManyProcessor)
|
.register(DeliverManyProcessor)
|
||||||
.register(NodeinfoProcessor)
|
.register(NodeinfoProcessor)
|
||||||
|
@ -51,6 +66,8 @@ pub struct JobState {
|
||||||
requests: Requests,
|
requests: Requests,
|
||||||
state: State,
|
state: State,
|
||||||
actors: ActorCache,
|
actors: ActorCache,
|
||||||
|
config: Config,
|
||||||
|
media: Media,
|
||||||
node_cache: NodeCache,
|
node_cache: NodeCache,
|
||||||
job_server: JobServer,
|
job_server: JobServer,
|
||||||
}
|
}
|
||||||
|
@ -61,11 +78,19 @@ pub struct JobServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl JobState {
|
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 {
|
JobState {
|
||||||
requests: state.requests(),
|
requests: state.requests(),
|
||||||
node_cache: state.node_cache(),
|
node_cache: state.node_cache(),
|
||||||
actors,
|
actors,
|
||||||
|
config,
|
||||||
|
media,
|
||||||
state,
|
state,
|
||||||
job_server,
|
job_server,
|
||||||
}
|
}
|
||||||
|
|
17
src/main.rs
17
src/main.rs
|
@ -16,7 +16,7 @@ mod routes;
|
||||||
use self::{
|
use self::{
|
||||||
args::Args,
|
args::Args,
|
||||||
config::Config,
|
config::Config,
|
||||||
data::{ActorCache, State},
|
data::{ActorCache, Media, State},
|
||||||
db::Db,
|
db::Db,
|
||||||
jobs::{create_server, create_workers},
|
jobs::{create_server, create_workers},
|
||||||
middleware::RelayResolver,
|
middleware::RelayResolver,
|
||||||
|
@ -62,6 +62,7 @@ async fn main() -> Result<(), anyhow::Error> {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let media = Media::new();
|
||||||
let state = State::hydrate(config.clone(), &db).await?;
|
let state = State::hydrate(config.clone(), &db).await?;
|
||||||
let actors = ActorCache::new(db.clone());
|
let actors = ActorCache::new(db.clone());
|
||||||
let job_server = create_server(db.clone());
|
let job_server = create_server(db.clone());
|
||||||
|
@ -84,9 +85,11 @@ async fn main() -> Result<(), anyhow::Error> {
|
||||||
let state = state.clone();
|
let state = state.clone();
|
||||||
let actors = actors.clone();
|
let actors = actors.clone();
|
||||||
let job_server = job_server.clone();
|
let job_server = job_server.clone();
|
||||||
|
let media = media.clone();
|
||||||
|
let config = config.clone();
|
||||||
|
|
||||||
Arbiter::new().exec_fn(move || {
|
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?;
|
actix_rt::signal::ctrl_c().await?;
|
||||||
|
@ -98,7 +101,13 @@ async fn main() -> Result<(), anyhow::Error> {
|
||||||
let bind_address = config.bind_address();
|
let bind_address = config.bind_address();
|
||||||
HttpServer::new(move || {
|
HttpServer::new(move || {
|
||||||
if !no_jobs {
|
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()
|
App::new()
|
||||||
|
@ -109,7 +118,9 @@ async fn main() -> Result<(), anyhow::Error> {
|
||||||
.data(actors.clone())
|
.data(actors.clone())
|
||||||
.data(config.clone())
|
.data(config.clone())
|
||||||
.data(job_server.clone())
|
.data(job_server.clone())
|
||||||
|
.data(media.clone())
|
||||||
.service(web::resource("/").route(web::get().to(index)))
|
.service(web::resource("/").route(web::get().to(index)))
|
||||||
|
.service(web::resource("/media/{path}").route(web::get().to(routes::media)))
|
||||||
.service(
|
.service(
|
||||||
web::resource("/inbox")
|
web::resource("/inbox")
|
||||||
.wrap(config.digest_middleware())
|
.wrap(config.digest_middleware())
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::error::MyError;
|
use crate::error::MyError;
|
||||||
use activitystreams::primitives::XsdAnyUri;
|
use activitystreams::primitives::XsdAnyUri;
|
||||||
use actix_web::client::Client;
|
use actix_web::client::Client;
|
||||||
|
use bytes::Bytes;
|
||||||
use http_signature_normalization_actix::prelude::*;
|
use http_signature_normalization_actix::prelude::*;
|
||||||
use log::error;
|
use log::error;
|
||||||
use rsa::{hash::Hashes, padding::PaddingScheme, RSAPrivateKey};
|
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<T>(&self, inbox: XsdAnyUri, item: &T) -> Result<(), MyError>
|
pub async fn deliver<T>(&self, inbox: XsdAnyUri, item: &T) -> Result<(), MyError>
|
||||||
where
|
where
|
||||||
T: serde::ser::Serialize,
|
T: serde::ser::Serialize,
|
||||||
|
|
|
@ -8,7 +8,6 @@ pub async fn route(
|
||||||
config: web::Data<Config>,
|
config: web::Data<Config>,
|
||||||
) -> Result<HttpResponse, MyError> {
|
) -> Result<HttpResponse, MyError> {
|
||||||
let nodes = state.node_cache().nodes().await;
|
let nodes = state.node_cache().nodes().await;
|
||||||
|
|
||||||
let mut buf = BufWriter::new(Vec::new());
|
let mut buf = BufWriter::new(Vec::new());
|
||||||
|
|
||||||
crate::templates::index(&mut buf, &nodes, &config)?;
|
crate::templates::index(&mut buf, &nodes, &config)?;
|
||||||
|
|
27
src/routes/media.rs
Normal file
27
src/routes/media.rs
Normal file
|
@ -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<Media>,
|
||||||
|
requests: web::Data<Requests>,
|
||||||
|
uuid: web::Path<Uuid>,
|
||||||
|
) -> Result<HttpResponse, MyError> {
|
||||||
|
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())
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
mod actor;
|
mod actor;
|
||||||
mod inbox;
|
mod inbox;
|
||||||
mod index;
|
mod index;
|
||||||
|
mod media;
|
||||||
mod nodeinfo;
|
mod nodeinfo;
|
||||||
mod statics;
|
mod statics;
|
||||||
|
|
||||||
|
@ -8,6 +9,7 @@ pub use self::{
|
||||||
actor::route as actor,
|
actor::route as actor,
|
||||||
inbox::route as inbox,
|
inbox::route as inbox,
|
||||||
index::route as index,
|
index::route as index,
|
||||||
|
media::route as media,
|
||||||
nodeinfo::{route as nodeinfo, well_known as nodeinfo_meta},
|
nodeinfo::{route as nodeinfo, well_known as nodeinfo_meta},
|
||||||
statics::route as statics,
|
statics::route as statics,
|
||||||
};
|
};
|
||||||
|
|
15
templates/admin.rs.html
Normal file
15
templates/admin.rs.html
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
@use crate::data::Contact;
|
||||||
|
|
||||||
|
@(contact: &Contact)
|
||||||
|
|
||||||
|
<div class="admin">
|
||||||
|
<div class="left">
|
||||||
|
<figure class="avatar">
|
||||||
|
<img src="@contact.avatar" alt="@contact.display_name's avatar">
|
||||||
|
</figure>
|
||||||
|
</div>
|
||||||
|
<div class="right">
|
||||||
|
<p class="display-name"><a href="@contact.url">@contact.display_name</a></p>
|
||||||
|
<p class="username">@@@contact.username</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -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)
|
@(nodes: &[Node], config: &Config)
|
||||||
|
|
||||||
|
@ -11,28 +15,29 @@
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
<h1>Welcome to @config.software_name() on @config.hostname()</h1>
|
<div class="header-text">
|
||||||
|
<h1>@config.software_name()</h1>
|
||||||
|
<p>on @config.hostname()</p>
|
||||||
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<main>
|
<main>
|
||||||
<section>
|
<section>
|
||||||
<h3>Connected Servers:</h3>
|
<h3>Connected Servers</h3>
|
||||||
@if nodes.is_empty() {
|
@if nodes.is_empty() {
|
||||||
<p>There are no connected servers at this time.</p>
|
<p>There are no connected servers at this time.</p>
|
||||||
} else {
|
} else {
|
||||||
<ul>
|
<ul>
|
||||||
@for node in nodes {
|
@for node in nodes {
|
||||||
@if let Some(domain) = node.base.as_url().domain() {
|
@if let Some(inst) = node.instance.as_ref() {
|
||||||
<li>
|
<li>
|
||||||
<p><a href="@node.base">@domain</a></p>
|
@:instance(inst, node.info.as_ref().map(|info| { info.software.as_ref() }), node.contact.as_ref(), &node.base)
|
||||||
@if let Some(info) = node.info.as_ref() {
|
|
||||||
<p>Running @info.software version @info.version</p>
|
|
||||||
@if info.reg {
|
|
||||||
<p>Registration is open</p>
|
|
||||||
} else {
|
|
||||||
<p>Registration is closed</p>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</li>
|
</li>
|
||||||
|
} else {
|
||||||
|
@if let Some(inf) = node.info.as_ref() {
|
||||||
|
<li>
|
||||||
|
@:info(&inf, &node.base)
|
||||||
|
</li>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -40,6 +45,7 @@
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<h3>Joining</h3>
|
<h3>Joining</h3>
|
||||||
|
<article class="joining">
|
||||||
<p>
|
<p>
|
||||||
If you are the admin of a server that supports activitypub relays, you can add
|
If you are the admin of a server that supports activitypub relays, you can add
|
||||||
this relay to your server.
|
this relay to your server.
|
||||||
|
@ -61,6 +67,7 @@
|
||||||
Consult the documentation for your server. It's likely that it follows either
|
Consult the documentation for your server. It's likely that it follows either
|
||||||
Mastodon or Pleroma's relay formatting.
|
Mastodon or Pleroma's relay formatting.
|
||||||
</p>
|
</p>
|
||||||
|
</article>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
<footer>
|
<footer>
|
||||||
|
|
16
templates/info.rs.html
Normal file
16
templates/info.rs.html
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
@use crate::data::Info;
|
||||||
|
@use activitystreams::primitives::XsdAnyUri;
|
||||||
|
|
||||||
|
@(info: &Info, base: &XsdAnyUri)
|
||||||
|
|
||||||
|
<article class="info">
|
||||||
|
@if let Some(domain) = base.as_url().domain() {
|
||||||
|
<h4 class="padded"><a href="@base">@domain</a></h4>
|
||||||
|
}
|
||||||
|
<p class="padded">
|
||||||
|
Running @info.software, version @info.version.
|
||||||
|
@if info.reg {
|
||||||
|
Registration is open
|
||||||
|
}
|
||||||
|
</p>
|
||||||
|
</article>
|
32
templates/instance.rs.html
Normal file
32
templates/instance.rs.html
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
@use crate::{data::{Contact, Instance}, templates::admin};
|
||||||
|
@use activitystreams::primitives::XsdAnyUri;
|
||||||
|
|
||||||
|
@(instance: &Instance, software: Option<&str>, contact: Option<&Contact>, base: &XsdAnyUri)
|
||||||
|
|
||||||
|
<article class="instance">
|
||||||
|
<h4 class="padded"><a href="@base">@instance.title</a></h4>
|
||||||
|
<p class="padded">
|
||||||
|
@if let Some(software) = software {
|
||||||
|
Running @software, version @instance.version.
|
||||||
|
}
|
||||||
|
@if instance.reg {
|
||||||
|
<br>Registration is open.
|
||||||
|
@if instance.requires_approval {
|
||||||
|
Accounts must be approved by an admin.
|
||||||
|
}
|
||||||
|
} else{
|
||||||
|
Registration is closed
|
||||||
|
}
|
||||||
|
</p>
|
||||||
|
<div class="instance-info">
|
||||||
|
<h4 class="instance-description">Description:</h4>
|
||||||
|
<div class="description">
|
||||||
|
<div class="please-stay">
|
||||||
|
@Html(&instance.description)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@if let Some(contact) = contact {
|
||||||
|
@:admin(contact)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</article>
|
Loading…
Reference in a new issue