Implement instance info endpoint (JSON, YAML, TXT) (#685)
Co-authored-by: Daniel Valentine <daniel@vielle.ws> Co-authored-by: spikecodes <19519553+spikecodes@users.noreply.github.com>
This commit is contained in:
parent
7efa26e811
commit
8be5fdee2d
38
Cargo.lock
generated
38
Cargo.lock
generated
@ -150,6 +150,12 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "build_html"
|
||||||
|
version = "2.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f3ef018b44d829e1b3364b4969059c098743595ec57a7eed176fbc9d909ac217"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.12.0"
|
version = "3.12.0"
|
||||||
@ -671,6 +677,7 @@ version = "0.27.2"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"askama",
|
"askama",
|
||||||
"brotli",
|
"brotli",
|
||||||
|
"build_html",
|
||||||
"cached",
|
"cached",
|
||||||
"clap",
|
"clap",
|
||||||
"cookie",
|
"cookie",
|
||||||
@ -687,6 +694,7 @@ dependencies = [
|
|||||||
"sealed_test",
|
"sealed_test",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"serde_yaml",
|
||||||
"time",
|
"time",
|
||||||
"tokio",
|
"tokio",
|
||||||
"toml",
|
"toml",
|
||||||
@ -782,6 +790,15 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num_threads"
|
||||||
|
version = "0.1.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.17.0"
|
version = "1.17.0"
|
||||||
@ -1165,6 +1182,19 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_yaml"
|
||||||
|
version = "0.9.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "92b5b431e8907b50339b51223b97d102db8d987ced36f6e4d03621db9316c834"
|
||||||
|
dependencies = [
|
||||||
|
"indexmap",
|
||||||
|
"itoa",
|
||||||
|
"ryu",
|
||||||
|
"serde",
|
||||||
|
"unsafe-libyaml",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sha2"
|
name = "sha2"
|
||||||
version = "0.10.6"
|
version = "0.10.6"
|
||||||
@ -1274,6 +1304,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376"
|
checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
|
"libc",
|
||||||
|
"num_threads",
|
||||||
"serde",
|
"serde",
|
||||||
"time-core",
|
"time-core",
|
||||||
"time-macros",
|
"time-macros",
|
||||||
@ -1442,6 +1474,12 @@ dependencies = [
|
|||||||
"tinyvec",
|
"tinyvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unsafe-libyaml"
|
||||||
|
version = "0.2.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bc7ed8ba44ca06be78ea1ad2c3682a43349126c8818054231ee6f4748012aed2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "untrusted"
|
name = "untrusted"
|
||||||
version = "0.7.1"
|
version = "0.7.1"
|
||||||
|
@ -21,13 +21,15 @@ percent-encoding = "2.2.0"
|
|||||||
route-recognizer = "0.3.1"
|
route-recognizer = "0.3.1"
|
||||||
serde_json = "1.0.91"
|
serde_json = "1.0.91"
|
||||||
tokio = { version = "1.24.2", features = ["full"] }
|
tokio = { version = "1.24.2", features = ["full"] }
|
||||||
time = "0.3.17"
|
time = { version = "0.3.17", features = ["local-offset"] }
|
||||||
url = "2.3.1"
|
url = "2.3.1"
|
||||||
rust-embed = { version = "6.4.2", features = ["include-exclude"] }
|
rust-embed = { version = "6.4.2", features = ["include-exclude"] }
|
||||||
libflate = "1.2.0"
|
libflate = "1.2.0"
|
||||||
brotli = { version = "3.3.4", features = ["std"] }
|
brotli = { version = "3.3.4", features = ["std"] }
|
||||||
toml = "0.5.10"
|
toml = "0.5.10"
|
||||||
once_cell = "1.17.0"
|
once_cell = "1.17.0"
|
||||||
|
serde_yaml = "0.9.16"
|
||||||
|
build_html = "2.2.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
lipsum = "0.8.2"
|
lipsum = "0.8.2"
|
||||||
|
@ -190,6 +190,7 @@ Assign a default value for each instance-specific setting by passing environment
|
|||||||
|Name|Possible values|Default value|Description|
|
|Name|Possible values|Default value|Description|
|
||||||
|-|-|-|-|
|
|-|-|-|-|
|
||||||
| `SFW_ONLY` | `["on", "off"]` | `off` | Enables SFW-only mode for the instance, i.e. all NSFW content is filtered. |
|
| `SFW_ONLY` | `["on", "off"]` | `off` | Enables SFW-only mode for the instance, i.e. all NSFW content is filtered. |
|
||||||
|
| `BANNER` | String | (empty) | Allows the server to set a banner to be displayed. Currently this is displayed on the instance info page. |
|
||||||
|
|
||||||
## Default User Settings
|
## Default User Settings
|
||||||
|
|
||||||
@ -208,6 +209,7 @@ Assign a default value for each user-modifiable setting by passing environment v
|
|||||||
| `USE_HLS` | `["on", "off"]` | `off` |
|
| `USE_HLS` | `["on", "off"]` | `off` |
|
||||||
| `HIDE_HLS_NOTIFICATION` | `["on", "off"]` | `off` |
|
| `HIDE_HLS_NOTIFICATION` | `["on", "off"]` | `off` |
|
||||||
| `AUTOPLAY_VIDEOS` | `["on", "off"]` | `off` |
|
| `AUTOPLAY_VIDEOS` | `["on", "off"]` | `off` |
|
||||||
|
| `HIDE_AWARDS` | `["on", "off"]` | `off`
|
||||||
|
|
||||||
You can also configure Libreddit with a configuration file. An example `libreddit.toml` can be found below:
|
You can also configure Libreddit with a configuration file. An example `libreddit.toml` can be found below:
|
||||||
|
|
||||||
|
6
app.json
6
app.json
@ -43,6 +43,12 @@
|
|||||||
},
|
},
|
||||||
"LIBREDDIT_SFW_ONLY": {
|
"LIBREDDIT_SFW_ONLY": {
|
||||||
"required": false
|
"required": false
|
||||||
|
},
|
||||||
|
"LIBREDDIT_DEFAULT_HIDE_AWARDS": {
|
||||||
|
"required": false
|
||||||
|
}
|
||||||
|
"LIBREDDIT_BANNER": {
|
||||||
|
"required": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
20
build.rs
Normal file
20
build.rs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
use std::{
|
||||||
|
os::unix::process::ExitStatusExt,
|
||||||
|
process::{Command, ExitStatus, Output},
|
||||||
|
};
|
||||||
|
fn main() {
|
||||||
|
let output = String::from_utf8(
|
||||||
|
Command::new("git")
|
||||||
|
.args(["rev-parse", "HEAD"])
|
||||||
|
.output()
|
||||||
|
.unwrap_or(Output {
|
||||||
|
stdout: vec![],
|
||||||
|
stderr: vec![],
|
||||||
|
status: ExitStatus::from_raw(0),
|
||||||
|
})
|
||||||
|
.stdout,
|
||||||
|
)
|
||||||
|
.unwrap_or_default();
|
||||||
|
let git_hash = if output == String::default() { "dev".into() } else { output };
|
||||||
|
println!("cargo:rustc-env=GIT_HASH={git_hash}");
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{env::var, fs::read_to_string};
|
use std::{env::var, fs::read_to_string};
|
||||||
|
|
||||||
// Waiting for https://github.com/rust-lang/rust/issues/74465 to land, so we
|
// Waiting for https://github.com/rust-lang/rust/issues/74465 to land, so we
|
||||||
@ -6,44 +7,53 @@ use std::{env::var, fs::read_to_string};
|
|||||||
//
|
//
|
||||||
// This is the local static that is initialized at runtime (technically at
|
// This is the local static that is initialized at runtime (technically at
|
||||||
// first request) and contains the instance settings.
|
// first request) and contains the instance settings.
|
||||||
static CONFIG: Lazy<Config> = Lazy::new(Config::load);
|
pub(crate) static CONFIG: Lazy<Config> = Lazy::new(Config::load);
|
||||||
|
|
||||||
/// Stores the configuration parsed from the environment variables and the
|
/// Stores the configuration parsed from the environment variables and the
|
||||||
/// config file. `Config::Default()` contains None for each setting.
|
/// config file. `Config::Default()` contains None for each setting.
|
||||||
#[derive(Default, serde::Deserialize)]
|
/// When adding more config settings, add it to `Config::load`,
|
||||||
|
/// `get_setting_from_config`, both below, as well as
|
||||||
|
/// instance_info::InstanceInfo.to_string(), README.md and app.json.
|
||||||
|
#[derive(Default, Serialize, Deserialize, Clone)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
#[serde(rename = "LIBREDDIT_SFW_ONLY")]
|
#[serde(rename = "LIBREDDIT_SFW_ONLY")]
|
||||||
sfw_only: Option<String>,
|
pub(crate) sfw_only: Option<String>,
|
||||||
|
|
||||||
#[serde(rename = "LIBREDDIT_DEFAULT_THEME")]
|
#[serde(rename = "LIBREDDIT_DEFAULT_THEME")]
|
||||||
default_theme: Option<String>,
|
pub(crate) default_theme: Option<String>,
|
||||||
|
|
||||||
#[serde(rename = "LIBREDDIT_DEFAULT_FRONT_PAGE")]
|
#[serde(rename = "LIBREDDIT_DEFAULT_FRONT_PAGE")]
|
||||||
default_front_page: Option<String>,
|
pub(crate) default_front_page: Option<String>,
|
||||||
|
|
||||||
#[serde(rename = "LIBREDDIT_DEFAULT_LAYOUT")]
|
#[serde(rename = "LIBREDDIT_DEFAULT_LAYOUT")]
|
||||||
default_layout: Option<String>,
|
pub(crate) default_layout: Option<String>,
|
||||||
|
|
||||||
#[serde(rename = "LIBREDDIT_DEFAULT_WIDE")]
|
#[serde(rename = "LIBREDDIT_DEFAULT_WIDE")]
|
||||||
default_wide: Option<String>,
|
pub(crate) default_wide: Option<String>,
|
||||||
|
|
||||||
#[serde(rename = "LIBREDDIT_DEFAULT_COMMENT_SORT")]
|
#[serde(rename = "LIBREDDIT_DEFAULT_COMMENT_SORT")]
|
||||||
default_comment_sort: Option<String>,
|
pub(crate) default_comment_sort: Option<String>,
|
||||||
|
|
||||||
#[serde(rename = "LIBREDDIT_DEFAULT_POST_SORT")]
|
#[serde(rename = "LIBREDDIT_DEFAULT_POST_SORT")]
|
||||||
default_post_sort: Option<String>,
|
pub(crate) default_post_sort: Option<String>,
|
||||||
|
|
||||||
#[serde(rename = "LIBREDDIT_DEFAULT_SHOW_NSFW")]
|
#[serde(rename = "LIBREDDIT_DEFAULT_SHOW_NSFW")]
|
||||||
default_show_nsfw: Option<String>,
|
pub(crate) default_show_nsfw: Option<String>,
|
||||||
|
|
||||||
#[serde(rename = "LIBREDDIT_DEFAULT_BLUR_NSFW")]
|
#[serde(rename = "LIBREDDIT_DEFAULT_BLUR_NSFW")]
|
||||||
default_blur_nsfw: Option<String>,
|
pub(crate) default_blur_nsfw: Option<String>,
|
||||||
|
|
||||||
#[serde(rename = "LIBREDDIT_DEFAULT_USE_HLS")]
|
#[serde(rename = "LIBREDDIT_DEFAULT_USE_HLS")]
|
||||||
default_use_hls: Option<String>,
|
pub(crate) default_use_hls: Option<String>,
|
||||||
|
|
||||||
#[serde(rename = "LIBREDDIT_DEFAULT_HIDE_HLS_NOTIFICATION")]
|
#[serde(rename = "LIBREDDIT_DEFAULT_HIDE_HLS_NOTIFICATION")]
|
||||||
default_hide_hls_notification: Option<String>,
|
pub(crate) default_hide_hls_notification: Option<String>,
|
||||||
|
|
||||||
|
#[serde(rename = "LIBREDDIT_DEFAULT_HIDE_AWARDS")]
|
||||||
|
pub(crate) default_hide_awards: Option<String>,
|
||||||
|
|
||||||
|
#[serde(rename = "LIBREDDIT_BANNER")]
|
||||||
|
pub(crate) banner: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
@ -70,6 +80,8 @@ impl Config {
|
|||||||
default_blur_nsfw: parse("LIBREDDIT_DEFAULT_BLUR_NSFW"),
|
default_blur_nsfw: parse("LIBREDDIT_DEFAULT_BLUR_NSFW"),
|
||||||
default_use_hls: parse("LIBREDDIT_DEFAULT_USE_HLS"),
|
default_use_hls: parse("LIBREDDIT_DEFAULT_USE_HLS"),
|
||||||
default_hide_hls_notification: parse("LIBREDDIT_DEFAULT_HIDE_HLS"),
|
default_hide_hls_notification: parse("LIBREDDIT_DEFAULT_HIDE_HLS"),
|
||||||
|
default_hide_awards: parse("LIBREDDIT_DEFAULT_HIDE_AWARDS"),
|
||||||
|
banner: parse("LIBREDDIT_BANNER"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -87,6 +99,8 @@ fn get_setting_from_config(name: &str, config: &Config) -> Option<String> {
|
|||||||
"LIBREDDIT_DEFAULT_USE_HLS" => config.default_use_hls.clone(),
|
"LIBREDDIT_DEFAULT_USE_HLS" => config.default_use_hls.clone(),
|
||||||
"LIBREDDIT_DEFAULT_HIDE_HLS_NOTIFICATION" => config.default_hide_hls_notification.clone(),
|
"LIBREDDIT_DEFAULT_HIDE_HLS_NOTIFICATION" => config.default_hide_hls_notification.clone(),
|
||||||
"LIBREDDIT_DEFAULT_WIDE" => config.default_wide.clone(),
|
"LIBREDDIT_DEFAULT_WIDE" => config.default_wide.clone(),
|
||||||
|
"LIBREDDIT_DEFAULT_HIDE_AWARDS" => config.default_hide_awards.clone(),
|
||||||
|
"LIBREDDIT_BANNER" => config.banner.clone(),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
205
src/instance_info.rs
Normal file
205
src/instance_info.rs
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
use crate::{
|
||||||
|
config::{Config, CONFIG},
|
||||||
|
server::RequestExt,
|
||||||
|
utils::{ErrorTemplate, Preferences},
|
||||||
|
};
|
||||||
|
use askama::Template;
|
||||||
|
use build_html::{Container, Html, HtmlContainer, Table};
|
||||||
|
use hyper::{http::Error, Body, Request, Response};
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
|
// This is the local static that is intialized at runtime (technically at
|
||||||
|
// the first request to the info endpoint) and contains the data
|
||||||
|
// retrieved from the info endpoint.
|
||||||
|
pub(crate) static INSTANCE_INFO: Lazy<InstanceInfo> = Lazy::new(InstanceInfo::new);
|
||||||
|
|
||||||
|
/// Handles instance info endpoint
|
||||||
|
pub async fn instance_info(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||||
|
// This will retrieve the extension given, or create a new string - which will
|
||||||
|
// simply become the last option, an HTML page.
|
||||||
|
let extension = req.param("extension").unwrap_or(String::new());
|
||||||
|
let response = match extension.as_str() {
|
||||||
|
"yaml" | "yml" => info_yaml(),
|
||||||
|
"txt" => info_txt(),
|
||||||
|
"json" => info_json(),
|
||||||
|
"html" | "" => info_html(req),
|
||||||
|
_ => {
|
||||||
|
let error = ErrorTemplate {
|
||||||
|
msg: "Error: Invalid info extension".into(),
|
||||||
|
prefs: Preferences::new(&req),
|
||||||
|
url: req.uri().to_string(),
|
||||||
|
}
|
||||||
|
.render()
|
||||||
|
.unwrap();
|
||||||
|
Response::builder().status(404).header("content-type", "text/html; charset=utf-8").body(error.into())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
response.map_err(|err| format!("{err}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn info_json() -> Result<Response<Body>, Error> {
|
||||||
|
if let Ok(body) = serde_json::to_string(&*INSTANCE_INFO) {
|
||||||
|
Response::builder().status(200).header("content-type", "application/json").body(body.into())
|
||||||
|
} else {
|
||||||
|
Response::builder()
|
||||||
|
.status(500)
|
||||||
|
.header("content-type", "text/plain")
|
||||||
|
.body(Body::from("Error serializing JSON"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn info_yaml() -> Result<Response<Body>, Error> {
|
||||||
|
if let Ok(body) = serde_yaml::to_string(&*INSTANCE_INFO) {
|
||||||
|
// We can use `application/yaml` as media type, though there is no guarantee
|
||||||
|
// that browsers will honor it. But we'll do it anyway. See:
|
||||||
|
// https://github.com/ietf-wg-httpapi/mediatypes/blob/main/draft-ietf-httpapi-yaml-mediatypes.md#media-type-applicationyaml-application-yaml
|
||||||
|
Response::builder().status(200).header("content-type", "application/yaml").body(body.into())
|
||||||
|
} else {
|
||||||
|
Response::builder()
|
||||||
|
.status(500)
|
||||||
|
.header("content-type", "text/plain")
|
||||||
|
.body(Body::from("Error serializing YAML."))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn info_txt() -> Result<Response<Body>, Error> {
|
||||||
|
Response::builder()
|
||||||
|
.status(200)
|
||||||
|
.header("content-type", "text/plain")
|
||||||
|
.body(Body::from(INSTANCE_INFO.to_string(StringType::Raw)))
|
||||||
|
}
|
||||||
|
fn info_html(req: Request<Body>) -> Result<Response<Body>, Error> {
|
||||||
|
let message = MessageTemplate {
|
||||||
|
title: String::from("Instance information"),
|
||||||
|
body: INSTANCE_INFO.to_string(StringType::Html),
|
||||||
|
prefs: Preferences::new(&req),
|
||||||
|
url: req.uri().to_string(),
|
||||||
|
}
|
||||||
|
.render()
|
||||||
|
.unwrap();
|
||||||
|
Response::builder().status(200).header("content-type", "text/html; charset=utf8").body(Body::from(message))
|
||||||
|
}
|
||||||
|
#[derive(Serialize, Deserialize, Default)]
|
||||||
|
pub(crate) struct InstanceInfo {
|
||||||
|
crate_version: String,
|
||||||
|
git_commit: String,
|
||||||
|
deploy_date: String,
|
||||||
|
compile_mode: String,
|
||||||
|
deploy_unix_ts: i64,
|
||||||
|
config: Config,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InstanceInfo {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
crate_version: env!("CARGO_PKG_VERSION").to_string(),
|
||||||
|
git_commit: env!("GIT_HASH").to_string(),
|
||||||
|
deploy_date: OffsetDateTime::now_local().unwrap_or_else(|_| OffsetDateTime::now_utc()).to_string(),
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
compile_mode: "Debug".into(),
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
compile_mode: "Release".into(),
|
||||||
|
deploy_unix_ts: OffsetDateTime::now_local().unwrap_or_else(|_| OffsetDateTime::now_utc()).unix_timestamp(),
|
||||||
|
config: CONFIG.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn to_table(&self) -> String {
|
||||||
|
let mut container = Container::default();
|
||||||
|
let convert = |o: &Option<String>| -> String { o.clone().unwrap_or("<span class=\"unset\"><i>Unset</i></span>".to_owned()) };
|
||||||
|
if let Some(banner) = &self.config.banner {
|
||||||
|
container.add_header(3, "Instance banner");
|
||||||
|
container.add_raw("<br />");
|
||||||
|
container.add_paragraph(banner);
|
||||||
|
container.add_raw("<br />");
|
||||||
|
}
|
||||||
|
container.add_table(
|
||||||
|
Table::from([
|
||||||
|
["Crate version", &self.crate_version],
|
||||||
|
["Git commit", &self.git_commit],
|
||||||
|
["Deploy date", &self.deploy_date],
|
||||||
|
["Deploy timestamp", &self.deploy_unix_ts.to_string()],
|
||||||
|
["Compile mode", &self.compile_mode],
|
||||||
|
["SFW only", &convert(&self.config.sfw_only)],
|
||||||
|
])
|
||||||
|
.with_header_row(["Settings"]),
|
||||||
|
);
|
||||||
|
container.add_raw("<br />");
|
||||||
|
container.add_table(
|
||||||
|
Table::from([
|
||||||
|
["Hide awards", &convert(&self.config.default_hide_awards)],
|
||||||
|
["Theme", &convert(&self.config.default_theme)],
|
||||||
|
["Front page", &convert(&self.config.default_front_page)],
|
||||||
|
["Layout", &convert(&self.config.default_layout)],
|
||||||
|
["Wide", &convert(&self.config.default_wide)],
|
||||||
|
["Comment sort", &convert(&self.config.default_comment_sort)],
|
||||||
|
["Post sort", &convert(&self.config.default_post_sort)],
|
||||||
|
["Show NSFW", &convert(&self.config.default_show_nsfw)],
|
||||||
|
["Blur NSFW", &convert(&self.config.default_blur_nsfw)],
|
||||||
|
["Use HLS", &convert(&self.config.default_use_hls)],
|
||||||
|
["Hide HLS notification", &convert(&self.config.default_hide_hls_notification)],
|
||||||
|
])
|
||||||
|
.with_header_row(["Default preferences"]),
|
||||||
|
);
|
||||||
|
container.to_html_string().replace("<th>", "<th colspan=\"2\">")
|
||||||
|
}
|
||||||
|
fn to_string(&self, string_type: StringType) -> String {
|
||||||
|
match string_type {
|
||||||
|
StringType::Raw => {
|
||||||
|
format!(
|
||||||
|
"Crate version: {}\n
|
||||||
|
Git commit: {}\n
|
||||||
|
Deploy date: {}\n
|
||||||
|
Deploy timestamp: {}\n
|
||||||
|
Compile mode: {}\n
|
||||||
|
Config:\n
|
||||||
|
Banner: {:?}\n
|
||||||
|
Hide awards: {:?}\n
|
||||||
|
SFW only: {:?}\n
|
||||||
|
Default theme: {:?}\n
|
||||||
|
Default front page: {:?}\n
|
||||||
|
Default layout: {:?}\n
|
||||||
|
Default wide: {:?}\n
|
||||||
|
Default comment sort: {:?}\n
|
||||||
|
Default post sort: {:?}\n
|
||||||
|
Default show NSFW: {:?}\n
|
||||||
|
Default blur NSFW: {:?}\n
|
||||||
|
Default use HLS: {:?}\n
|
||||||
|
Default hide HLS notification: {:?}\n",
|
||||||
|
self.crate_version,
|
||||||
|
self.git_commit,
|
||||||
|
self.deploy_date,
|
||||||
|
self.deploy_unix_ts,
|
||||||
|
self.compile_mode,
|
||||||
|
self.config.banner,
|
||||||
|
self.config.default_hide_awards,
|
||||||
|
self.config.sfw_only,
|
||||||
|
self.config.default_theme,
|
||||||
|
self.config.default_front_page,
|
||||||
|
self.config.default_layout,
|
||||||
|
self.config.default_wide,
|
||||||
|
self.config.default_comment_sort,
|
||||||
|
self.config.default_post_sort,
|
||||||
|
self.config.default_show_nsfw,
|
||||||
|
self.config.default_blur_nsfw,
|
||||||
|
self.config.default_use_hls,
|
||||||
|
self.config.default_hide_hls_notification
|
||||||
|
)
|
||||||
|
}
|
||||||
|
StringType::Html => self.to_table(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
enum StringType {
|
||||||
|
Raw,
|
||||||
|
Html,
|
||||||
|
}
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(path = "message.html")]
|
||||||
|
struct MessageTemplate {
|
||||||
|
title: String,
|
||||||
|
body: String,
|
||||||
|
prefs: Preferences,
|
||||||
|
url: String,
|
||||||
|
}
|
13
src/main.rs
13
src/main.rs
@ -5,6 +5,7 @@
|
|||||||
// Reference local files
|
// Reference local files
|
||||||
mod config;
|
mod config;
|
||||||
mod duplicates;
|
mod duplicates;
|
||||||
|
mod instance_info;
|
||||||
mod post;
|
mod post;
|
||||||
mod search;
|
mod search;
|
||||||
mod settings;
|
mod settings;
|
||||||
@ -20,6 +21,7 @@ use hyper::{header::HeaderValue, Body, Request, Response};
|
|||||||
|
|
||||||
mod client;
|
mod client;
|
||||||
use client::{canonical_path, proxy};
|
use client::{canonical_path, proxy};
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
use server::RequestExt;
|
use server::RequestExt;
|
||||||
use utils::{error, redirect, ThemeAssets};
|
use utils::{error, redirect, ThemeAssets};
|
||||||
|
|
||||||
@ -158,6 +160,13 @@ async fn main() {
|
|||||||
// Begin constructing a server
|
// Begin constructing a server
|
||||||
let mut app = server::Server::new();
|
let mut app = server::Server::new();
|
||||||
|
|
||||||
|
// Force evaluation of statics. In instance_info case, we need to evaluate
|
||||||
|
// the timestamp so deploy date is accurate - in config case, we need to
|
||||||
|
// evaluate the configuration to avoid paying penalty at first request.
|
||||||
|
|
||||||
|
Lazy::force(&config::CONFIG);
|
||||||
|
Lazy::force(&instance_info::INSTANCE_INFO);
|
||||||
|
|
||||||
// Define default headers (added to all responses)
|
// Define default headers (added to all responses)
|
||||||
app.default_headers = headers! {
|
app.default_headers = headers! {
|
||||||
"Referrer-Policy" => "no-referrer",
|
"Referrer-Policy" => "no-referrer",
|
||||||
@ -285,6 +294,10 @@ async fn main() {
|
|||||||
// Handle about pages
|
// Handle about pages
|
||||||
app.at("/about").get(|req| error(req, "About pages aren't added yet".to_string()).boxed());
|
app.at("/about").get(|req| error(req, "About pages aren't added yet".to_string()).boxed());
|
||||||
|
|
||||||
|
// Instance info page
|
||||||
|
app.at("/info").get(|r| instance_info::instance_info(r).boxed());
|
||||||
|
app.at("/info.:extension").get(|r| instance_info::instance_info(r).boxed());
|
||||||
|
|
||||||
app.at("/:id").get(|req: Request<Body>| {
|
app.at("/:id").get(|req: Request<Body>| {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
match req.param("id").as_deref() {
|
match req.param("id").as_deref() {
|
||||||
|
@ -173,13 +173,22 @@ body > footer {
|
|||||||
margin: 20px;
|
margin: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
body > footer > div#sfw-only {
|
.info-button {
|
||||||
color: var(--green);
|
align-items: center;
|
||||||
border: 1px solid var(--green);
|
border-radius: .25rem;
|
||||||
padding: 5px;
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border-radius: 5px;
|
color: var(--text);
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-flex;
|
||||||
|
font-size: 300%;
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.info-button > a:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
/* / Body footer. */
|
/* / Body footer. */
|
||||||
|
|
||||||
/* Footer in content block. */
|
/* Footer in content block. */
|
||||||
@ -1238,6 +1247,10 @@ input[type="submit"] {
|
|||||||
width: 250px;
|
width: 250px;
|
||||||
background: var(--highlighted) !important;
|
background: var(--highlighted) !important;
|
||||||
}
|
}
|
||||||
|
/* Info page */
|
||||||
|
.unset {
|
||||||
|
color: lightslategrey;
|
||||||
|
}
|
||||||
|
|
||||||
/* Markdown */
|
/* Markdown */
|
||||||
|
|
||||||
|
@ -66,9 +66,11 @@
|
|||||||
</main>
|
</main>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block footer %}
|
{% block footer %}
|
||||||
{% if crate::utils::sfw_only() %}
|
<footer>
|
||||||
<footer><div id="sfw-only">This instance of Libreddit is SFW-only.</div></footer>
|
<div class="info-button">
|
||||||
{% endif %}
|
<a href="/info" title="View instance information">ⓘ</a>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
10
templates/message.html
Normal file
10
templates/message.html
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% block title %}{{ title }}{% endblock %}
|
||||||
|
{% block sortstyle %}{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<div id="message">
|
||||||
|
<h1>{{ title }}</h1>
|
||||||
|
<br>
|
||||||
|
{{ body|safe }}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
Loading…
Reference in New Issue
Block a user