feat: HTML default page

This commit is contained in:
Jade Ellis 2025-04-25 02:47:48 +01:00
parent 4158c1cf62
commit e1655edd83
No known key found for this signature in database
GPG key ID: 8705A2A3EBF77BD2
14 changed files with 408 additions and 10 deletions

View file

@ -103,6 +103,7 @@ conduwuit-admin.workspace = true
conduwuit-api.workspace = true
conduwuit-core.workspace = true
conduwuit-service.workspace = true
conduwuit-web.workspace = true
const-str.workspace = true
futures.workspace = true
http.workspace = true

View file

@ -1,6 +1,6 @@
use std::sync::Arc;
use axum::{Router, response::IntoResponse, routing::get};
use axum::{Router, response::IntoResponse};
use conduwuit::Error;
use conduwuit_api::router::{state, state::Guard};
use conduwuit_service::Services;
@ -11,7 +11,7 @@ pub(crate) fn build(services: &Arc<Services>) -> (Router, Guard) {
let router = Router::<state::State>::new();
let (state, guard) = state::create(services.clone());
let router = conduwuit_api::router::build(router, &services.server)
.route("/", get(it_works))
.merge(conduwuit_web::build::<state::State>().with_state(()))
.fallback(not_found)
.with_state(state);
@ -21,5 +21,3 @@ pub(crate) fn build(services: &Arc<Services>) -> (Router, Guard) {
async fn not_found(_uri: Uri) -> impl IntoResponse {
Error::Request(ErrorKind::Unrecognized, "Not Found".into(), StatusCode::NOT_FOUND)
}
async fn it_works() -> &'static str { "hewwo from conduwuit woof!" }

32
src/web/Cargo.toml Normal file
View file

@ -0,0 +1,32 @@
[package]
name = "conduwuit_web"
categories.workspace = true
description.workspace = true
edition.workspace = true
keywords.workspace = true
license.workspace = true
readme.workspace = true
repository.workspace = true
version.workspace = true
[lib]
path = "mod.rs"
crate-type = [
"rlib",
# "dylib",
]
[features]
[dependencies]
askama = "0.14.0"
axum.workspace = true
futures.workspace = true
tracing.workspace = true
rand.workspace = true
thiserror.workspace = true
[lints]
workspace = true

68
src/web/css/index.css Normal file
View file

@ -0,0 +1,68 @@
:root {
color-scheme: light;
--font-stack: sans-serif;
--background-color: #fff;
--text-color: #000;
--bg: oklch(0.76 0.0854 317.27);
--panel-bg: oklch(0.91 0.042 317.27);
--name-lightness: 0.45;
@media (prefers-color-scheme: dark) {
color-scheme: dark;
--text-color: #fff;
--bg: oklch(0.15 0.042 317.27);
--panel-bg: oklch(0.24 0.03 317.27);
--name-lightness: 0.8;
}
--c1: oklch(0.44 0.177 353.06);
--c2: oklch(0.59 0.158 150.88);
--normal-font-size: 1rem;
--small-font-size: 0.8rem;
}
body {
color: var(--text-color);
font-family: var(--font-stack);
margin: 0;
padding: 0;
display: grid;
place-items: center;
min-height: 100vh;
}
html {
background-color: var(--bg);
background-image: linear-gradient(
70deg,
oklch(from var(--bg) l + 0.2 c h),
oklch(from var(--bg) l - 0.2 c h)
);
font-size: 16px;
}
.panel {
width: min(clamp(24rem, 12rem + 40vw, 48rem), 100vw);
border-radius: 15px;
background-color: var(--panel-bg);
padding-inline: 1.5rem;
padding-block: 1rem;
box-shadow: 0 0.25em 0.375em hsla(0, 0%, 0%, 0.1);
}
.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);
}

61
src/web/mod.rs Normal file
View file

@ -0,0 +1,61 @@
use askama::Template;
use axum::{
Router,
http::{StatusCode, header},
response::{Html, IntoResponse, Response},
routing::get,
};
pub fn build<S>() -> Router<()> { Router::new().route("/", get(index_handler)) }
async fn index_handler() -> Result<impl IntoResponse, WebError> {
#[derive(Debug, Template)]
#[template(path = "index.html.j2")]
struct Tmpl<'a> {
nonce: &'a str,
}
let nonce = rand::random::<u64>().to_string();
let template = Tmpl { nonce: &nonce };
Ok((
[(header::CONTENT_SECURITY_POLICY, format!("default-src 'none' 'nonce-{nonce}';"))],
Html(template.render()?),
))
}
#[derive(Debug, thiserror::Error)]
enum WebError {
#[error("Failed to render template: {0}")]
Render(#[from] askama::Error),
}
impl IntoResponse for WebError {
fn into_response(self) -> Response {
#[derive(Debug, Template)]
#[template(path = "error.html.j2")]
struct Tmpl<'a> {
nonce: &'a str,
err: WebError,
}
let nonce = rand::random::<u64>().to_string();
let status = match &self {
| Self::Render(_) => StatusCode::INTERNAL_SERVER_ERROR,
};
let tmpl = Tmpl { nonce: &nonce, err: self };
if let Ok(body) = tmpl.render() {
(
status,
[(
header::CONTENT_SECURITY_POLICY,
format!("default-src 'none' 'nonce-{nonce}';"),
)],
Html(body),
)
.into_response()
} else {
(status, "Something went wrong").into_response()
}
}
}

View file

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>{% block title %}Continuwuity{% endblock %}</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type="text/css" nonce="{{ nonce }}">
/*<![CDATA[*/
{{ include_str !("css/index.css") | safe }}
/*]]>*/
</style>
</head>
<body>
<main>{%~ block content %}{% endblock ~%}</main>
{%~ block footer ~%}
<footer>
<p>Powered by <a href="https://continuwuity.org">Continuwuity</a>
{%~ if let Some(version_info) = option_env!("CONDUWUIT_VERSION_EXTRA").or(option_env!("SHORT_COMMIT_SHA")) ~%}
{%~ if let Some(url) = option_env!("REMOTE_COMMIT_URL").or(option_env!("REMOTE_URL")) ~%}
(<a href="{{ url }}">{{ version_info }}</a>)
{%~ else ~%}
({{ version_info }})
{%~ endif ~%}
{%~ endif ~%}</p>
</footer>
{%~ endblock ~%}
</body>
</html>

View file

@ -0,0 +1,20 @@
{% extends "_layout.html.j2" %}
{%- block title -%}
Server Error
{%- endblock -%}
{%- block content -%}
<h1>
{%- match err -%}
{% else -%} 500: Internal Server Error
{%- endmatch -%}
</h1>
{%- match err -%}
{% when WebError::Render(err) -%}
<pre>{{ err }}</pre>
{% else -%} <p>An error occurred</p>
{%- endmatch -%}
{%- endblock -%}

View file

@ -0,0 +1,16 @@
{% extends "_layout.html.j2" %}
{%- block content -%}
<div class="orb"></div>
<div class="panel">
<h1>Welcome to <a class="project-name" href="https://continuwuity.org">Continuwuity</a>!</h1>
<p>Continuwuity is successfully installed and working. </p>
<p>To get started, you can:</p>
<ul>
<li>Read the <a href="https://continuwuity.org/introduction">documentation</a></li>
<li>Join the <a href="https://continuwuity.org/community">community</a></li>
<li>Log in with a <a href="https://matrix.org/ecosystem/clients/">client</a></li>
<li>Ensure <a href="https://federationtester.matrix.org/">federation</a> works</li>
</ul>
</div>
{%- endblock content -%}