mirror of
https://forgejo.ellis.link/continuwuation/continuwuity.git
synced 2026-05-26 20:49:55 +00:00
feat: Add a page with some information about the server
This commit is contained in:
@@ -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<Contact> = 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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<Server>,
|
||||
config: Dep<config::Service>,
|
||||
globals: Dep<globals::Service>,
|
||||
alias: Dep<rooms::alias::Service>,
|
||||
timeline: Dep<rooms::timeline::Service>,
|
||||
@@ -115,6 +118,7 @@ impl crate::Service for Service {
|
||||
Ok(Arc::new(Self {
|
||||
services: Services {
|
||||
server: args.server.clone(),
|
||||
config: args.depend::<config::Service>("config"),
|
||||
globals: args.depend::<globals::Service>("globals"),
|
||||
alias: args.depend::<rooms::alias::Service>("rooms::alias"),
|
||||
timeline: args.depend::<rooms::timeline::Service>("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<Contact> {
|
||||
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<Contact> = 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,6 +129,7 @@ pub fn build(services: &Services) -> Router<state::State> {
|
||||
.nest(
|
||||
"/_continuwuity/",
|
||||
Router::new()
|
||||
.nest("/about", about::build())
|
||||
.nest("/account/", account::build())
|
||||
.merge(debug::build())
|
||||
.nest("/oauth2/", oauth::build())
|
||||
|
||||
@@ -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<crate::State> { Router::new().route("/", get(get_about)) }
|
||||
|
||||
template! {
|
||||
struct About use "about.html.j2" {
|
||||
server_name: OwnedServerName,
|
||||
support_page: Option<Url>,
|
||||
contacts: Vec<Contact>,
|
||||
terms: BTreeMap<String, TermsDocument>
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_about(
|
||||
State(services): State<crate::State>,
|
||||
Extension(context): Extension<TemplateContext>,
|
||||
) -> 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()
|
||||
))
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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: "•";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -1,32 +1,36 @@
|
||||
<div class="card">
|
||||
{{ avatar }}
|
||||
<div class="info">
|
||||
<p class="name">
|
||||
{% if let Some(display_name) = display_name %}
|
||||
{{ display_name }}
|
||||
{% else %}
|
||||
Unknown device
|
||||
{% endif %}
|
||||
<div class="name">
|
||||
<span>
|
||||
{% if let Some(display_name) = display_name %}
|
||||
{{ display_name }}
|
||||
{% else %}
|
||||
Unknown device
|
||||
{% endif %}
|
||||
</span>
|
||||
{% if style == DeviceCardStyle::Detailed %}
|
||||
<span class="id">
|
||||
• {{ device_id }}
|
||||
{% if let Some(metadata) = oauth_metadata %}
|
||||
• <a href="{{ metadata.client_uri }}">Client website</a>
|
||||
{% else %}
|
||||
(legacy)
|
||||
{% endif %}
|
||||
<ul class="id bullet-separated">
|
||||
<li>{{ device_id }}</li>
|
||||
<li>
|
||||
{% if let Some(metadata) = oauth_metadata %}
|
||||
<a href="{{ metadata.client_uri }}">Client website</a>
|
||||
{% else %}
|
||||
(legacy)
|
||||
{% endif %}
|
||||
</li>
|
||||
</span>
|
||||
{% endif %}
|
||||
</p>
|
||||
<p>
|
||||
</div>
|
||||
<div>
|
||||
Last active: {{ last_active }}
|
||||
</p>
|
||||
<p>
|
||||
</div>
|
||||
<div>
|
||||
{% if style == DeviceCardStyle::Detailed %}
|
||||
<a href="{{ crate::ROUTE_PREFIX }}/account/device/{{ device_id }}/remove">Remove</a>
|
||||
{% else %}
|
||||
<a href="{{ crate::ROUTE_PREFIX }}/account/device/{{ device_id }}/">Details</a>
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -19,16 +19,8 @@
|
||||
{%~ block content %}{% endblock ~%}
|
||||
{%~ block footer ~%}
|
||||
<footer>
|
||||
<img class="logo" src="{{ crate::ROUTE_PREFIX }}/resources/logo.svg">
|
||||
<p>Powered by <a href="https://continuwuity.org">Continuwuity</a> {{ 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) ~%}
|
||||
(<a href="{{ url }}">{{ version_info }}</a>)
|
||||
{%~ else ~%}
|
||||
({{ version_info }})
|
||||
{%~ endif ~%}
|
||||
{%~ endif ~%}
|
||||
</p>
|
||||
<a href="{{ crate::ROUTE_PREFIX }}/"><img class="logo" src="{{ crate::ROUTE_PREFIX }}/resources/logo.svg"></a>
|
||||
<p>Powered by <a href="https://continuwuity.org">Continuwuity</a> {{ env!("CARGO_PKG_VERSION") }} • <a href="{{ crate::ROUTE_PREFIX }}/about">About</a></p>
|
||||
</footer>
|
||||
{%~ endblock ~%}
|
||||
</body>
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
{% extends "_layout.html.j2" %}
|
||||
|
||||
{%- block title -%}
|
||||
About server
|
||||
{%- endblock -%}
|
||||
|
||||
{%- block content -%}
|
||||
<div class="panel">
|
||||
<h1>About {{ server_name }}</h1>
|
||||
{% if let Some(support_page) = support_page %}
|
||||
<p>
|
||||
Support: <a href="{{ support_page }} target="_blank">{{ support_page }}</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
{% if !contacts.is_empty() %}
|
||||
<p>
|
||||
Contact the operators of this server:
|
||||
</p>
|
||||
<ul>
|
||||
{% for contact in contacts %}
|
||||
<li>
|
||||
{%- match contact.role -%}
|
||||
{%- when ContactRole::Admin -%}
|
||||
Administrator
|
||||
{%- when ContactRole::Security -%}
|
||||
Security
|
||||
{%- when ContactRole::Moderator -%}
|
||||
Moderator
|
||||
{%- when _ -%}
|
||||
Contact
|
||||
{%- endmatch -%}
|
||||
: <ul class="bullet-separated">
|
||||
{%- if let Some(matrix_id) = contact.matrix_id -%}
|
||||
<li><a href="matrix:u/{{ matrix_id.localpart() }}:{{ matrix_id.server_name() }}?action=chat" target="_blank">{{ matrix_id }}</a></li>
|
||||
{%- endif -%}
|
||||
{%- if let Some(email_address) = contact.email_address -%}
|
||||
<li><a href="mailto:{{ email_address }}" target="_blank">{{ email_address }}</a>
|
||||
{%- if let Some(pgp_key) = contact.pgp_key -%}
|
||||
(<a href="{{ pgp_key }}" target="_blank">PGP</a>)
|
||||
{%- endif -%}
|
||||
</li>
|
||||
{%- endif -%}
|
||||
</ul>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% if !terms.is_empty() %}
|
||||
<p>
|
||||
By using {{ server_name }} you agree to the following policies:
|
||||
</p>
|
||||
<ul>
|
||||
{% for (id, document) in terms %}
|
||||
<li>
|
||||
<a target="_blank" href="{{ document.url }}">{{ document.name }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
<p>
|
||||
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) ~%}
|
||||
(<a href="{{ url }}">{{ version_info }}</a>)
|
||||
{%~ else ~%}
|
||||
({{ version_info }})
|
||||
{%~ endif ~%}
|
||||
{%~ endif ~%}
|
||||
</p>
|
||||
<hr>
|
||||
<p>
|
||||
{{ server_name }} is powered by <a class="project-name" href="https://continuwuity.org">Continuwuity</a>,
|
||||
a high-performance and community-driven <a href="https://matrix.org">Matrix</a> homeserver
|
||||
maintained as an open source project by volunteers from around the world.
|
||||
</p>
|
||||
<p>
|
||||
<ul class="bullet-separated">
|
||||
<li><a href="https://forgejo.ellis.link/continuwuation/continuwuity">Source code</a></li>
|
||||
<li><a href="https://matrix.to/#/#continuwuity:continuwuity.org">Official Matrix chatroom</a></li>
|
||||
<li><span class="project-name">❤</span> <a href="https://opencollective.com/continuwuity">Support the project</a></li>
|
||||
</ul>
|
||||
</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -58,8 +58,10 @@ Your account
|
||||
<p>
|
||||
Settings here <em class="negative">may affect the integrity of your account</em>.
|
||||
</p>
|
||||
<a href="cross_signing_reset">Reset your digital identity</a> •
|
||||
<a href="deactivate">Deactivate your account</a>
|
||||
<ul class="bullet-separated">
|
||||
<li><a href="cross_signing_reset">Reset your digital identity</a></li>
|
||||
<li><a href="deactivate">Deactivate your account</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
</section>
|
||||
{% when AccountBody::Locked %}
|
||||
|
||||
@@ -9,16 +9,18 @@ Device information
|
||||
<h1>About device <a class="back" href="{{ crate::ROUTE_PREFIX }}/account/">Back</a></h1>
|
||||
{{ device_card }}
|
||||
{% if let Some((client_metadata, _)) = client_metadata %}
|
||||
<section>
|
||||
<p>
|
||||
{% if let Some(tos_uri) = &client_metadata.tos_uri %}
|
||||
<a href="{{ tos_uri }}">Terms of service</a>
|
||||
{% endif %}
|
||||
{% if let Some(policy_uri) = &client_metadata.policy_uri %}
|
||||
• <a href="{{ policy_uri }}">Policies</a>
|
||||
{% endif %}
|
||||
</p>
|
||||
</section>
|
||||
{% if client_metadata.tos_uri.is_some() || client_metadata.policy_uri.is_some() %}
|
||||
<section>
|
||||
<ul class="bullet-separated">
|
||||
{% if let Some(tos_uri) = &client_metadata.tos_uri %}
|
||||
<li><a href="{{ tos_uri }}">Terms of service</a></li>
|
||||
{% endif %}
|
||||
{% if let Some(policy_uri) = &client_metadata.policy_uri %}
|
||||
<li><a href="{{ policy_uri }}">Policies</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</section>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<section>
|
||||
<p>
|
||||
|
||||
Reference in New Issue
Block a user