generated from PlexSheep/rs-base
feat(admin): improve webui
This commit is contained in:
parent
1fe799e1c0
commit
f1ef0c26ec
|
@ -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}} — {{ 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 · © 2024
|
Created by {{ author }} · © {{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"
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue