feat(admin): improve webui

This commit is contained in:
Christoph J. Scherr 2024-09-07 19:37:06 +02:00
parent 1fe799e1c0
commit f1ef0c26ec
3 changed files with 185 additions and 158 deletions

View File

@ -6,17 +6,9 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content=""> <meta name="author" content="{{author}}">
<meta name="author" content="{AUTHOR}"> <title>{{title}}</title>
<meta name="generator" content="Hugo 0.122.0">
<title>Starter Template · Bootstrap v5.3</title>
<link rel="canonical" href="https://getbootstrap.com/docs/5.3/examples/starter-template/">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@docsearch/css@3"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@docsearch/css@3">
<link href="https://getbootstrap.com/docs/5.3/dist/css/bootstrap.min.css" rel="stylesheet" <link href="https://getbootstrap.com/docs/5.3/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous"> integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
@ -25,7 +17,6 @@
<meta name="theme-color" content="#712cf9"> <meta name="theme-color" content="#712cf9">
<link rel="stylesheet" href="/styles.css"> <link rel="stylesheet" href="/styles.css">
</head> </head>
<body> <body>
@ -116,27 +107,71 @@
<svg class="bi me-2" width="40" height="32"> <svg class="bi me-2" width="40" height="32">
<use xlink:href="#vault" /> <use xlink:href="#vault" />
</svg> </svg>
<span class="fs-4">{TITLE}</span> <span class="fs-4">{{title}}</span>
</a> </a>
</header> </header>
<main> <main>
<h1 class="text-body-emphasis">{TITLE} Admin Interface</h1> <h1 class="text-body-emphasis">{{title}} Admin Interface</h1>
<p class="fs-5 col-md-8">Quickly and easily get started with Bootstrap's compiled, production-ready files with <p class="fs-5 col-md-8">
this barebones example featuring some basic HTML and helpful links. Download all our examples to get started. You have reached the {{title}} Admin Interface. This site can be used by
the host of the challenge to see the challenge progress, solution, and
hints for that challenge. This site is <b>NOT</b> part of the
challenge.
</p> </p>
<div class="mb-5"> <hr class="mb-5">
<a href="/docs/5.3/examples/" class="btn btn-primary btn-lg px-4">Download examples</a>
<div class="row g-5 mb-5">
<h2 class="text-body-emphasis">Challenge {{ challenge_idx}} &mdash; {{ challenge_title }}</h2>
<p class="mt-1 mb-3">{{ challenge_description }}</p>
<div class="col mt-1">
<h3>Hints</h3>
<button class="btn btn-primary my-2" type="button" data-bs-toggle="collapse" data-bs-target="#hints"
aria-expanded="false" aria-controls="collapseExample">
Show hints
</button>
<div class="collapse" id="hints">
<ul class="list-unstyled ps-0">
{% for hint in challenge_hints %}
<li>
<p>
<svg class="bi" width="16" height="16">
<use xlink:href="#arrow-right-circle" />
</svg>
{{hint}}
</p>
</li>
{% endfor %}
</ul>
</div>
</div>
<div class="col mt-1">
<h3>Solution</h3>
<button class="btn btn-primary my-2" type="button" data-bs-toggle="collapse" data-bs-target="#solution"
aria-expanded="false" aria-controls="collapseExample">
Show solution
</button>
<div class="collapse" id="solution">
<p>
<svg class="bi" width="16" height="16">
<use xlink:href="#arrow-right-circle" />
</svg>
{{challenge_solution}}
</p>
</div>
</div>
</div> </div>
<hr class="col-3 col-md-2 mb-5"> <hr class="mb-5">
<div class="row g-5"> <div class="row g-5">
<div class="col-md-6"> <div class="col-md-6">
<h2 class="text-body-emphasis">Starter projects</h2> <h2 class="text-body-emphasis">Contestants</h2>
<p>Ready to go beyond the starter template? Check out these open source projects that you can quickly <p>
duplicate to a new GitHub repository.</p> These contestants currently have had at least one connection to
the challenge.
</p>
<ul class="list-unstyled ps-0"> <ul class="list-unstyled ps-0">
<li> <li>
<a class="icon-link mb-1" href="https://github.com/twbs/examples/tree/main/icons-font" rel="noopener" <a class="icon-link mb-1" href="https://github.com/twbs/examples/tree/main/icons-font" rel="noopener"
@ -147,39 +182,14 @@
Bootstrap npm starter Bootstrap npm starter
</a> </a>
</li> </li>
<li>
<a class="icon-link mb-1" href="https://github.com/twbs/examples/tree/main/parcel" rel="noopener"
target="_blank">
<svg class="bi" width="16" height="16">
<use xlink:href="#arrow-right-circle" />
</svg>
Bootstrap Parcel starter
</a>
</li>
<li>
<a class="icon-link mb-1" href="https://github.com/twbs/examples/tree/main/vite" rel="noopener"
target="_blank">
<svg class="bi" width="16" height="16">
<use xlink:href="#arrow-right-circle" />
</svg>
Bootstrap Vite starter
</a>
</li>
<li>
<a class="icon-link mb-1" href="https://github.com/twbs/examples/tree/main/webpack" rel="noopener"
target="_blank">
<svg class="bi" width="16" height="16">
<use xlink:href="#arrow-right-circle" />
</svg>
Bootstrap Webpack starter
</a>
</li>
</ul> </ul>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<h2 class="text-body-emphasis">Guides</h2> <h2 class="text-body-emphasis">Winners</h2>
<p>Read more detailed instructions and documentation on using or contributing to Bootstrap.</p> <p>
These contestants currently have been sent the secret.
</p>
<ul class="list-unstyled ps-0"> <ul class="list-unstyled ps-0">
<li> <li>
<a class="icon-link mb-1" href="/docs/5.3/getting-started/introduction/"> <a class="icon-link mb-1" href="/docs/5.3/getting-started/introduction/">
@ -189,44 +199,12 @@
Bootstrap quick start guide Bootstrap quick start guide
</a> </a>
</li> </li>
<li>
<a class="icon-link mb-1" href="/docs/5.3/getting-started/webpack/">
<svg class="bi" width="16" height="16">
<use xlink:href="#arrow-right-circle" />
</svg>
Bootstrap Webpack guide
</a>
</li>
<li>
<a class="icon-link mb-1" href="/docs/5.3/getting-started/parcel/">
<svg class="bi" width="16" height="16">
<use xlink:href="#arrow-right-circle" />
</svg>
Bootstrap Parcel guide
</a>
</li>
<li>
<a class="icon-link mb-1" href="/docs/5.3/getting-started/vite/">
<svg class="bi" width="16" height="16">
<use xlink:href="#arrow-right-circle" />
</svg>
Bootstrap Vite guide
</a>
</li>
<li>
<a class="icon-link mb-1" href="/docs/5.3/getting-started/contribute/">
<svg class="bi" width="16" height="16">
<use xlink:href="#arrow-right-circle" />
</svg>
Contributing to Bootstrap
</a>
</li>
</ul> </ul>
</div> </div>
</div> </div>
</main> </main>
<footer class="pt-5 my-5 text-body-secondary border-top"> <footer class="pt-5 my-5 text-body-secondary border-top">
Created by the Bootstrap team &middot; &copy; 2024 Created by {{ author }} &middot; &copy; {{year}}
</footer> </footer>
</div> </div>
<script src="https://getbootstrap.com/docs/5.3/dist/js/bootstrap.bundle.min.js" <script src="https://getbootstrap.com/docs/5.3/dist/js/bootstrap.bundle.min.js"

View File

@ -1,45 +1,45 @@
.bd-placeholder-img { .bd-placeholder-img {
font-size: 1.125rem; font-size: 1.125rem;
text-anchor: middle; text-anchor: middle;
-webkit-user-select: none; -webkit-user-select: none;
-moz-user-select: none; -moz-user-select: none;
user-select: none; user-select: none;
} }
@media (min-width: 768px) { @media (min-width: 768px) {
.bd-placeholder-img-lg { .bd-placeholder-img-lg {
font-size: 3.5rem; font-size: 3.5rem;
} }
} }
.b-example-divider { .b-example-divider {
width: 100%; width: 100%;
height: 3rem; height: 3rem;
background-color: rgba(0, 0, 0, .1); background-color: rgba(0, 0, 0, .1);
border: solid rgba(0, 0, 0, .15); border: solid rgba(0, 0, 0, .15);
border-width: 1px 0; border-width: 1px 0;
box-shadow: inset 0 .5em 1.5em rgba(0, 0, 0, .1), inset 0 .125em .5em rgba(0, 0, 0, .15); box-shadow: inset 0 .5em 1.5em rgba(0, 0, 0, .1), inset 0 .125em .5em rgba(0, 0, 0, .15);
} }
.b-example-vr { .b-example-vr {
flex-shrink: 0; flex-shrink: 0;
width: 1.5rem; width: 1.5rem;
height: 100vh; height: 100vh;
} }
.bi { .bi {
vertical-align: -.125em; vertical-align: -.125em;
fill: currentColor; fill: currentColor;
} }
.nav-scroller { .nav-scroller {
position: relative; position: relative;
z-index: 2; z-index: 2;
height: 2.75rem; height: 2.75rem;
overflow-y: hidden; overflow-y: hidden;
} }
.nav-scroller .nav { .nav-scroller .nav {
display: flex; display: flex;
flex-wrap: nowrap; flex-wrap: nowrap;
padding-bottom: 1rem; padding-bottom: 1rem;
@ -48,9 +48,9 @@
text-align: center; text-align: center;
white-space: nowrap; white-space: nowrap;
-webkit-overflow-scrolling: touch; -webkit-overflow-scrolling: touch;
} }
.btn-bd-primary { .btn-bd-primary {
--bd-violet-bg: #712cf9; --bd-violet-bg: #712cf9;
--bd-violet-rgb: 112.520718, 44.062154, 249.437846; --bd-violet-rgb: 112.520718, 44.062154, 249.437846;
@ -65,12 +65,12 @@
--bs-btn-active-color: var(--bs-btn-hover-color); --bs-btn-active-color: var(--bs-btn-hover-color);
--bs-btn-active-bg: #5a23c8; --bs-btn-active-bg: #5a23c8;
--bs-btn-active-border-color: #5a23c8; --bs-btn-active-border-color: #5a23c8;
} }
.bd-mode-toggle { .bd-mode-toggle {
z-index: 1500; z-index: 1500;
} }
.bd-mode-toggle .dropdown-menu .active .bi { .bd-mode-toggle .dropdown-menu .active .bi {
display: block !important; display: block !important;
} }

View File

@ -2,41 +2,58 @@ use std::fmt::Display;
use std::sync::Arc; use std::sync::Arc;
use anyhow::Result; use anyhow::Result;
use libpt::log::error;
use libpt::log::info;
use libpt::log::warn; use libpt::log::warn;
use minijinja::context; use minijinja::context;
use minijinja::Environment; use minijinja::Environment;
use serde;
use thiserror::Error; use thiserror::Error;
use warp::http::StatusCode;
use warp::reject; use warp::reject;
use warp::reply::Reply;
use warp::reply::Response; use warp::reply::Response;
use warp::Filter; use warp::Filter;
use crate::config::Config; use crate::config::Config;
use crate::vault::VaultRef; use crate::vault::VaultRef;
use super::Descriptions;
#[derive(Clone)] #[derive(Clone)]
pub struct Service<'tp> { pub struct Service<'tp> {
vault: VaultRef, vault: VaultRef,
config: Config, config: Config,
env: Environment<'tp>, env: Environment<'tp>,
text: Descriptions,
} }
impl<'tp> Service<'tp> { impl<'tp> Service<'tp> {
fn new(vault: VaultRef, config: Config, env: Environment<'tp>) -> Arc<Self> { fn new(
Self { vault, config, env }.into() vault: VaultRef,
config: Config,
env: Environment<'tp>,
text: Descriptions,
) -> Arc<Self> {
Self {
vault,
config,
env,
text,
}
.into()
} }
} }
pub async fn serve(vault: VaultRef, config: Config) -> Result<()> { pub async fn serve(text: Descriptions, vault: VaultRef, config: Config) -> Result<()> {
let mut env = Environment::new(); let mut env = Environment::new();
env.add_template("index", include_str!("../../data/www/admin.html"))?; env.add_template("index", include_str!("../../data/www/admin.html"))?;
let service = Service::new(vault, config, env); let service = Service::new(vault, config, env, text);
let service2 = service.clone(); let service2 = service.clone();
let routes = warp::path::end() let routes = warp::path::end()
.map(move || service2.clone()) .map(move || service2.clone())
.and_then(overview); .and_then(overview)
.or(warp::path("styles.css").and_then(styles))
.recover(handle_rejection);
warp::serve(routes) warp::serve(routes)
.run(service.config.addr_admin.unwrap()) .run(service.config.addr_admin.unwrap())
@ -61,7 +78,16 @@ async fn overview(serv: Arc<Service<'_>>) -> Result<Box<dyn warp::Reply>, warp::
serv.env serv.env
.get_template("index") .get_template("index")
.map_err(TemplateError::from)? .map_err(TemplateError::from)?
.render(context!(TITLE => "Wooly-Vault", AUTHOR => env!("CARGO_PKG_AUTHORS"))) .render(context!(
title => "Wooly-Vault",
author => env!("CARGO_PKG_AUTHORS"),
year => "2024",
challenge_idx => serv.config.challenge,
challenge_title => serv.text.title(),
challenge_description => serv.text.description(),
challenge_hints => serv.text.hints(),
challenge_solution => serv.text.solution(),
))
.map_err(TemplateError::from)? .map_err(TemplateError::from)?
.into(), .into(),
); );
@ -70,6 +96,29 @@ async fn overview(serv: Arc<Service<'_>>) -> Result<Box<dyn warp::Reply>, warp::
} }
async fn styles() -> Result<Box<dyn warp::Reply>, warp::Rejection> { async fn styles() -> Result<Box<dyn warp::Reply>, warp::Rejection> {
let r = include_str!("../../data/www/styles.css").to_string(); let r = Response::new(include_str!("../../data/www/styles.css").to_string().into());
Ok(Box::new(r)) Ok(Box::new(r))
} }
async fn handle_rejection(
err: reject::Rejection,
) -> Result<impl warp::reply::Reply, std::convert::Infallible> {
let code;
let message;
info!("rejecting: {err:?}");
if err.is_not_found() {
code = StatusCode::NOT_FOUND;
message = "page not found";
} else if let Some(e) = err.find::<TemplateError>() {
error!("templating error: {e}");
code = StatusCode::INTERNAL_SERVER_ERROR;
message = "could not process data to make a page";
} else {
error!("unhandled rejection: {:?}", err);
code = StatusCode::INTERNAL_SERVER_ERROR;
message = "unhandled rejection";
}
Ok(warp::reply::with_status(message, code))
}