From 4da00fa28af20da55831e73dbe92add236027df5 Mon Sep 17 00:00:00 2001 From: Ginger Date: Wed, 20 May 2026 10:40:32 -0400 Subject: [PATCH] feat: Add a page with some information about the server --- src/api/client/well_known.rs | 43 +------- src/api/mod.rs | 1 + src/service/admin/mod.rs | 54 +++++++++- src/web/mod.rs | 1 + src/web/pages/about.rs | 38 +++++++ src/web/pages/mod.rs | 1 + src/web/pages/resources/common.css | 100 ++++++++++++------ src/web/pages/resources/components.css | 12 +-- src/web/pages/resources/index.css | 12 --- .../templates/_components/device_card.html.j2 | 40 +++---- src/web/pages/templates/_layout.html.j2 | 12 +-- src/web/pages/templates/about.html.j2 | 84 +++++++++++++++ src/web/pages/templates/account.html.j2 | 6 +- src/web/pages/templates/device_info.html.j2 | 22 ++-- 14 files changed, 293 insertions(+), 133 deletions(-) create mode 100644 src/web/pages/about.rs create mode 100644 src/web/pages/templates/about.html.j2 diff --git a/src/api/client/well_known.rs b/src/api/client/well_known.rs index 11cd0b351..e7d35ca90 100644 --- a/src/api/client/well_known.rs +++ b/src/api/client/well_known.rs @@ -4,7 +4,7 @@ use ruma::{ api::client::discovery::{ discover_homeserver::{self, HomeserverInfo}, discover_policy_server, - discover_support::{self, Contact, ContactRole}, + discover_support, }, assign, }; @@ -67,46 +67,7 @@ pub(crate) async fn well_known_support( .as_ref() .map(ToString::to_string); - let email_address = services.config.well_known.support_email.clone(); - let matrix_id = services.config.well_known.support_mxid.clone(); - let pgp_key = services.config.well_known.support_pgp_key.clone(); - - // TODO: support defining multiple contacts in the config - let mut contacts: Vec = vec![]; - - let role = services - .config - .well_known - .support_role - .clone() - .unwrap_or(ContactRole::Admin); - - // Add configured contact if at least one contact method is specified - let configured_contact = match (matrix_id, email_address) { - | (Some(matrix_id), email_address) => - Some(assign!(Contact::with_matrix_id(role, matrix_id), { email_address })), - | (None, Some(email_address)) => Some(Contact::with_email_address(role, email_address)), - | (None, None) => None, - }; - - if let Some(mut configured_contact) = configured_contact { - configured_contact.pgp_key = pgp_key; - - contacts.push(configured_contact); - } - - // Try to add admin users as contacts if no contacts are configured - if contacts.is_empty() { - let admin_users = services.admin.get_admins().await; - - for user_id in &admin_users { - if *user_id == services.globals.server_user { - continue; - } - - contacts.push(Contact::with_matrix_id(ContactRole::Admin, user_id.to_owned())); - } - } + let contacts = services.admin.get_support_contacts().await; if contacts.is_empty() && support_page.is_none() { // No admin room, no configured contacts, and no support page diff --git a/src/api/mod.rs b/src/api/mod.rs index 89bdc5c3f..7cbd3d209 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,4 +1,5 @@ #![type_length_limit = "16384"] //TODO: reduce me +#![recursion_limit = "256"] // My Giant Async Function #![allow(clippy::toplevel_ref_arg)] extern crate conduwuit_core as conduwuit; diff --git a/src/service/admin/mod.rs b/src/service/admin/mod.rs index ae3c14eac..c49391a7e 100644 --- a/src/service/admin/mod.rs +++ b/src/service/admin/mod.rs @@ -18,6 +18,8 @@ use futures::{Future, FutureExt, StreamExt, TryFutureExt}; use loole::{Receiver, Sender}; use ruma::{ OwnedEventId, OwnedMxcUri, OwnedRoomId, OwnedUserId, RoomId, UInt, UserId, + api::client::discovery::discover_support::{Contact, ContactRole}, + assign, events::{ Mentions, room::message::{ @@ -28,7 +30,7 @@ use ruma::{ use tokio::sync::RwLock; use crate::{ - Dep, account_data, globals, + Dep, account_data, config, globals, media::{MXC_LENGTH, mxc::Mxc}, rooms::{self, state::RoomMutexGuard}, }; @@ -44,6 +46,7 @@ pub struct Service { struct Services { server: Arc, + config: Dep, globals: Dep, alias: Dep, timeline: Dep, @@ -115,6 +118,7 @@ impl crate::Service for Service { Ok(Arc::new(Self { services: Services { server: args.server.clone(), + config: args.depend::("config"), globals: args.depend::("globals"), alias: args.depend::("rooms::alias"), timeline: args.depend::("rooms::timeline"), @@ -619,4 +623,52 @@ impl Service { let weak = services.map(Arc::downgrade); *receiver = weak; } + + /// Get the server's configured support contacts. + pub async fn get_support_contacts(&self) -> Vec { + let email_address = self.services.config.well_known.support_email.clone(); + let matrix_id = self.services.config.well_known.support_mxid.clone(); + let pgp_key = self.services.config.well_known.support_pgp_key.clone(); + + // TODO: support defining multiple contacts in the config + let mut contacts: Vec = vec![]; + + let role = self + .services + .config + .well_known + .support_role + .clone() + .unwrap_or(ContactRole::Admin); + + // Add configured contact if at least one contact method is specified + let configured_contact = match (matrix_id, email_address) { + | (Some(matrix_id), email_address) => + Some(assign!(Contact::with_matrix_id(role, matrix_id), { email_address })), + | (None, Some(email_address)) => + Some(Contact::with_email_address(role, email_address)), + | (None, None) => None, + }; + + if let Some(mut configured_contact) = configured_contact { + configured_contact.pgp_key = pgp_key; + + contacts.push(configured_contact); + } + + // Try to add admin users as contacts if no contacts are configured + if contacts.is_empty() { + let admin_users = self.get_admins().await; + + for user_id in &admin_users { + if *user_id == self.services.globals.server_user { + continue; + } + + contacts.push(Contact::with_matrix_id(ContactRole::Admin, user_id.to_owned())); + } + } + + contacts + } } diff --git a/src/web/mod.rs b/src/web/mod.rs index 64225d0d7..633b64191 100644 --- a/src/web/mod.rs +++ b/src/web/mod.rs @@ -129,6 +129,7 @@ pub fn build(services: &Services) -> Router { .nest( "/_continuwuity/", Router::new() + .nest("/about", about::build()) .nest("/account/", account::build()) .merge(debug::build()) .nest("/oauth2/", oauth::build()) diff --git a/src/web/pages/about.rs b/src/web/pages/about.rs new file mode 100644 index 000000000..6b7e1caba --- /dev/null +++ b/src/web/pages/about.rs @@ -0,0 +1,38 @@ +use std::collections::BTreeMap; + +use axum::{Extension, Router, extract::State, routing::get}; +use conduwuit_core::config::TermsDocument; +use ruma::{ + OwnedServerName, + api::client::discovery::discover_support::{Contact, ContactRole}, +}; +use url::Url; + +use crate::{ + pages::{Result, TemplateContext}, + response, template, +}; + +pub(crate) fn build() -> Router { Router::new().route("/", get(get_about)) } + +template! { + struct About use "about.html.j2" { + server_name: OwnedServerName, + support_page: Option, + contacts: Vec, + terms: BTreeMap + } +} + +async fn get_about( + State(services): State, + Extension(context): Extension, +) -> Result { + response!(About::new( + context, + services.globals.server_name().to_owned(), + services.config.well_known.support_page.clone(), + services.admin.get_support_contacts().await, + services.config.registration_terms.documents.clone() + )) +} diff --git a/src/web/pages/mod.rs b/src/web/pages/mod.rs index a623e46d8..e2e7ff8fe 100644 --- a/src/web/pages/mod.rs +++ b/src/web/pages/mod.rs @@ -11,6 +11,7 @@ use conduwuit_core::utils; use crate::WebError; +pub(super) mod about; pub(super) mod account; mod components; pub(super) mod debug; diff --git a/src/web/pages/resources/common.css b/src/web/pages/resources/common.css index c4b1650f1..39b8defd8 100644 --- a/src/web/pages/resources/common.css +++ b/src/web/pages/resources/common.css @@ -28,7 +28,7 @@ @media (prefers-color-scheme: dark) { color-scheme: dark; --text-color: #f5ebeb; - --secondary: #888; + --secondary: #999; --bg: oklch(0.15 0.042 317.27); --panel-bg: oklch(0.24 0.03 317.27); @@ -94,6 +94,10 @@ p { } } +ul { + margin: 1rem 0; +} + section { margin: 1rem 0; } @@ -125,38 +129,6 @@ small.error { margin-bottom: 0.5rem; } -.panel { - --preferred-width: 12rem + 40dvw; - --maximum-width: 48rem; - --minimum-width: 32rem; - - width: min(clamp(var(--minimum-width), var(--preferred-width), var(--maximum-width)), calc(100dvw - 3rem)); - border-radius: var(--border-radius-lg); - background-color: var(--panel-bg); - padding-inline: 1.5rem; - padding-block: 1rem; - margin-top: 1em; - margin-bottom: auto; - box-shadow: 0 0.25em 0.375em hsla(0, 0%, 0%, 0.1); - - &.middle { - margin-top: 0; - margin-bottom: 0; - } - - &.narrow { - --preferred-width: 12rem + 20dvw; - --maximum-width: 36rem; - - input, button, a.button { - width: 100%; - } - } - - &:not(.narrow) form p { - margin-bottom: 0; - } -} img.matrix-icon { @media (prefers-color-scheme: dark) { @@ -262,6 +234,22 @@ h1 { margin-bottom: 0.67em; } +ul.bullet-separated { + display: inline-block; + margin: 0; + padding: 0; + + li { + display: inline; + flex: 1; + list-style-type: none; + + &:not(:first-child)::before { + content: "• "; + } + } +} + .fullwidth { width: 100%; margin-bottom: 0 !important; @@ -271,6 +259,52 @@ h1 { user-select: all; } +.panel { + --preferred-width: 12rem + 40dvw; + --maximum-width: 48rem; + --minimum-width: 32rem; + + width: min(clamp(var(--minimum-width), var(--preferred-width), var(--maximum-width)), calc(100dvw - 3rem)); + border-radius: var(--border-radius-lg); + background-color: var(--panel-bg); + padding-inline: 1.5rem; + padding-block: 1rem; + margin-top: 1em; + margin-bottom: auto; + box-shadow: 0 0.25em 0.375em hsla(0, 0%, 0%, 0.1); + + &.middle { + margin-top: 0; + margin-bottom: 0; + } + + &.narrow { + --preferred-width: 12rem + 20dvw; + --maximum-width: 36rem; + + input, button, a.button { + width: 100%; + } + } + + &:not(.narrow) form p { + margin-bottom: 0; + } +} + +.project-name { + font-weight: bold; + text-decoration: none !important; + background: linear-gradient( + 130deg, + oklch(from var(--c1) var(--name-lightness) c h), + oklch(from var(--c2) var(--name-lightness) c h) + ); + background-clip: text; + color: transparent; + filter: brightness(1.2); +} + @media (max-width: 425px) { main { padding-block-start: 2rem; diff --git a/src/web/pages/resources/components.css b/src/web/pages/resources/components.css index a727e92c8..764a2d060 100644 --- a/src/web/pages/resources/components.css +++ b/src/web/pages/resources/components.css @@ -34,17 +34,17 @@ .info { flex: 1 1; - p { - margin: 0; - - &.name { - font-weight: 700; - } + .name { + font-weight: bold; } .id { color: var(--secondary); font-weight: normal; + + &::before { + content: "•"; + } } } diff --git a/src/web/pages/resources/index.css b/src/web/pages/resources/index.css index f83685b8d..967052480 100644 --- a/src/web/pages/resources/index.css +++ b/src/web/pages/resources/index.css @@ -1,15 +1,3 @@ -.project-name { - text-decoration: none; - background: linear-gradient( - 130deg, - oklch(from var(--c1) var(--name-lightness) c h), - oklch(from var(--c2) var(--name-lightness) c h) - ); - background-clip: text; - color: transparent; - filter: brightness(1.2); -} - .button { margin: 0 !important; } diff --git a/src/web/pages/templates/_components/device_card.html.j2 b/src/web/pages/templates/_components/device_card.html.j2 index a89c9815b..16ec62027 100644 --- a/src/web/pages/templates/_components/device_card.html.j2 +++ b/src/web/pages/templates/_components/device_card.html.j2 @@ -1,32 +1,36 @@
{{ avatar }}
-

- {% if let Some(display_name) = display_name %} - {{ display_name }} - {% else %} - Unknown device - {% endif %} +

+ + {% if let Some(display_name) = display_name %} + {{ display_name }} + {% else %} + Unknown device + {% endif %} + {% if style == DeviceCardStyle::Detailed %} - - • {{ device_id }} - {% if let Some(metadata) = oauth_metadata %} - • Client website - {% else %} - (legacy) - {% endif %} +
    +
  • {{ device_id }}
  • +
  • + {% if let Some(metadata) = oauth_metadata %} + Client website + {% else %} + (legacy) + {% endif %} +
  • {% endif %} -

    -

    +

+
Last active: {{ last_active }} -

-

+

+
{% if style == DeviceCardStyle::Detailed %} Remove {% else %} Details {% endif %} -

+
diff --git a/src/web/pages/templates/_layout.html.j2 b/src/web/pages/templates/_layout.html.j2 index df46d8236..77f2ca4fa 100644 --- a/src/web/pages/templates/_layout.html.j2 +++ b/src/web/pages/templates/_layout.html.j2 @@ -19,16 +19,8 @@ {%~ block content %}{% endblock ~%} {%~ block footer ~%}
- -

Powered by Continuwuity {{ env!("CARGO_PKG_VERSION") }} - {%~ if let Some(version_info) = conduwuit_build_metadata::version_tag() ~%} - {%~ if let Some(url) = conduwuit_build_metadata::GIT_REMOTE_COMMIT_URL.or(conduwuit_build_metadata::GIT_REMOTE_WEB_URL) ~%} - ({{ version_info }}) - {%~ else ~%} - ({{ version_info }}) - {%~ endif ~%} - {%~ endif ~%} -

+ +

Powered by Continuwuity {{ env!("CARGO_PKG_VERSION") }} • About

{%~ endblock ~%} diff --git a/src/web/pages/templates/about.html.j2 b/src/web/pages/templates/about.html.j2 new file mode 100644 index 000000000..c64db0db5 --- /dev/null +++ b/src/web/pages/templates/about.html.j2 @@ -0,0 +1,84 @@ +{% extends "_layout.html.j2" %} + +{%- block title -%} +About server +{%- endblock -%} + +{%- block content -%} +
+

About {{ server_name }}

+ {% if let Some(support_page) = support_page %} +

+ Support: {{ support_page }} +

+ {% endif %} + {% if !contacts.is_empty() %} +

+ Contact the operators of this server: +

+
    + {% for contact in contacts %} +
  • + {%- match contact.role -%} + {%- when ContactRole::Admin -%} + Administrator + {%- when ContactRole::Security -%} + Security + {%- when ContactRole::Moderator -%} + Moderator + {%- when _ -%} + Contact + {%- endmatch -%} + :
      + {%- if let Some(matrix_id) = contact.matrix_id -%} +
    • {{ matrix_id }}
    • + {%- endif -%} + {%- if let Some(email_address) = contact.email_address -%} +
    • {{ email_address }} + {%- if let Some(pgp_key) = contact.pgp_key -%} + (PGP) + {%- endif -%} +
    • + {%- endif -%} +
    +
  • + {% endfor %} +
+ {% endif %} + {% if !terms.is_empty() %} +

+ By using {{ server_name }} you agree to the following policies: +

+ + {% endif %} +

+ Server version {{ env!("CARGO_PKG_VERSION") }} + {%~ if let Some(version_info) = conduwuit_build_metadata::version_tag() ~%} + {%~ if let Some(url) = conduwuit_build_metadata::GIT_REMOTE_COMMIT_URL.or(conduwuit_build_metadata::GIT_REMOTE_WEB_URL) ~%} + ({{ version_info }}) + {%~ else ~%} + ({{ version_info }}) + {%~ endif ~%} + {%~ endif ~%} +

+
+

+ {{ server_name }} is powered by Continuwuity, + a high-performance and community-driven Matrix homeserver + maintained as an open source project by volunteers from around the world. +

+

+

+

+
+{% endblock %} diff --git a/src/web/pages/templates/account.html.j2 b/src/web/pages/templates/account.html.j2 index e9c9c8286..6e73ce15b 100644 --- a/src/web/pages/templates/account.html.j2 +++ b/src/web/pages/templates/account.html.j2 @@ -58,8 +58,10 @@ Your account

Settings here may affect the integrity of your account.

- Reset your digital identity • - Deactivate your account + {% when AccountBody::Locked %} diff --git a/src/web/pages/templates/device_info.html.j2 b/src/web/pages/templates/device_info.html.j2 index 0bac15b9b..476a7501b 100644 --- a/src/web/pages/templates/device_info.html.j2 +++ b/src/web/pages/templates/device_info.html.j2 @@ -9,16 +9,18 @@ Device information

About device Back

{{ device_card }} {% if let Some((client_metadata, _)) = client_metadata %} -
-

- {% if let Some(tos_uri) = &client_metadata.tos_uri %} - Terms of service - {% endif %} - {% if let Some(policy_uri) = &client_metadata.policy_uri %} - • Policies - {% endif %} -

-
+ {% if client_metadata.tos_uri.is_some() || client_metadata.policy_uri.is_some() %} +
+
    + {% if let Some(tos_uri) = &client_metadata.tos_uri %} +
  • Terms of service
  • + {% endif %} + {% if let Some(policy_uri) = &client_metadata.policy_uri %} +
  • Policies
  • + {% endif %} +
+
+ {% endif %} {% endif %}