Compare commits
No commits in common. "a80ad57ca9d5476c2ddbec8dc6ac7ca190f6e3ae" and "f45a43c852267af61ea3d84a9a2daa563f39b86f" have entirely different histories.
a80ad57ca9
...
f45a43c852
14 changed files with 45 additions and 418 deletions
109
Cargo.lock
generated
109
Cargo.lock
generated
|
|
@ -809,18 +809,6 @@ dependencies = [
|
|||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasip2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "globset"
|
||||
version = "0.4.18"
|
||||
|
|
@ -1397,13 +1385,10 @@ dependencies = [
|
|||
"displaydoc",
|
||||
"notify-debouncer-full",
|
||||
"password-auth",
|
||||
"rand 0.9.2",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sqlx",
|
||||
"tera",
|
||||
"thiserror 2.0.17",
|
||||
"time",
|
||||
"tokio",
|
||||
"tower-livereload",
|
||||
"tower-sessions",
|
||||
|
|
@ -1469,7 +1454,7 @@ dependencies = [
|
|||
"num-integer",
|
||||
"num-iter",
|
||||
"num-traits",
|
||||
"rand 0.8.5",
|
||||
"rand",
|
||||
"smallvec",
|
||||
"zeroize",
|
||||
]
|
||||
|
|
@ -1567,9 +1552,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "1a2a4764cc1f8d961d802af27193c6f4f0124bd0e76e8393cf818e18880f0524"
|
||||
dependencies = [
|
||||
"argon2",
|
||||
"getrandom 0.2.17",
|
||||
"getrandom",
|
||||
"password-hash",
|
||||
"rand_core 0.6.4",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1579,7 +1564,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"rand_core 0.6.4",
|
||||
"rand_core",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
|
|
@ -1679,7 +1664,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
|
||||
dependencies = [
|
||||
"phf_shared",
|
||||
"rand 0.8.5",
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1772,12 +1757,6 @@ dependencies = [
|
|||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "5.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
|
|
@ -1785,18 +1764,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha 0.3.1",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
|
||||
dependencies = [
|
||||
"rand_chacha 0.9.0",
|
||||
"rand_core 0.9.5",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1806,17 +1775,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core 0.9.5",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1825,16 +1784,7 @@ version = "0.6.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom 0.2.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
|
||||
dependencies = [
|
||||
"getrandom 0.3.4",
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1916,7 +1866,7 @@ dependencies = [
|
|||
"num-traits",
|
||||
"pkcs1",
|
||||
"pkcs8",
|
||||
"rand_core 0.6.4",
|
||||
"rand_core",
|
||||
"signature",
|
||||
"spki",
|
||||
"subtle",
|
||||
|
|
@ -2081,7 +2031,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
|
||||
dependencies = [
|
||||
"digest",
|
||||
"rand_core 0.6.4",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2259,7 +2209,7 @@ dependencies = [
|
|||
"memchr",
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"rand 0.8.5",
|
||||
"rand",
|
||||
"rsa",
|
||||
"serde",
|
||||
"sha1",
|
||||
|
|
@ -2298,7 +2248,7 @@ dependencies = [
|
|||
"md-5",
|
||||
"memchr",
|
||||
"once_cell",
|
||||
"rand 0.8.5",
|
||||
"rand",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
|
|
@ -2407,7 +2357,7 @@ dependencies = [
|
|||
"percent-encoding",
|
||||
"pest",
|
||||
"pest_derive",
|
||||
"rand 0.8.5",
|
||||
"rand",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
|
@ -2466,30 +2416,30 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.45"
|
||||
version = "0.3.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9e442fc33d7fdb45aa9bfeb312c095964abdf596f7567261062b2a7107aaabd"
|
||||
checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"itoa",
|
||||
"num-conv",
|
||||
"powerfmt",
|
||||
"serde_core",
|
||||
"serde",
|
||||
"time-core",
|
||||
"time-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-core"
|
||||
version = "0.1.7"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b36ee98fd31ec7426d599183e8fe26932a8dc1fb76ddb6214d05493377d34ca"
|
||||
checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.25"
|
||||
version = "0.2.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71e552d1249bf61ac2a52db88179fd0673def1e1ad8243a00d9ec9ed71fee3dd"
|
||||
checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3"
|
||||
dependencies = [
|
||||
"num-conv",
|
||||
"time-core",
|
||||
|
|
@ -2647,7 +2597,7 @@ dependencies = [
|
|||
"futures",
|
||||
"http",
|
||||
"parking_lot",
|
||||
"rand 0.8.5",
|
||||
"rand",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.17",
|
||||
|
|
@ -2853,15 +2803,6 @@ version = "0.11.1+wasi-snapshot-preview1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[package]]
|
||||
name = "wasip2"
|
||||
version = "1.0.2+wasi-0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
|
||||
dependencies = [
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasite"
|
||||
version = "0.1.0"
|
||||
|
|
@ -3140,12 +3081,6 @@ version = "0.53.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
|
||||
|
||||
[[package]]
|
||||
name = "writeable"
|
||||
version = "0.6.2"
|
||||
|
|
|
|||
|
|
@ -23,6 +23,3 @@ tracing-subscriber = { workspace = true, features = ["env-filter"] }
|
|||
tera = "1.20.1"
|
||||
notify-debouncer-full = "0.6.0"
|
||||
tower-livereload = "0.10.2"
|
||||
time = "0.3.45"
|
||||
rand = "0.9.2"
|
||||
serde_json.workspace = true
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
-- Add migration script here
|
||||
|
||||
DROP TABLE IF EXISTS api_keys;
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
-- Add migration script here
|
||||
|
||||
CREATE TABLE IF NOT EXISTS api_keys
|
||||
(
|
||||
id INTEGER PRIMARY KEY NOT NULL,
|
||||
user_id INTEGER NOT NULL REFERENCES users(id),
|
||||
token TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
expiration_date TEXT NOT NULL,
|
||||
permissions TEXT NOT NULL,
|
||||
revoked BOOLEAN NOT NULL
|
||||
);
|
||||
|
|
@ -9,6 +9,7 @@ use axum::response::IntoResponse;
|
|||
use axum::routing::get;
|
||||
use axum_login::AuthManagerLayerBuilder;
|
||||
use axum_login::AuthnBackend;
|
||||
use axum_login::login_required;
|
||||
use displaydoc::Display;
|
||||
use notify_debouncer_full::DebouncedEvent;
|
||||
use notify_debouncer_full::notify::EventKind;
|
||||
|
|
@ -27,25 +28,20 @@ use tower_sessions_sqlx_store::SqliteStore;
|
|||
use tracing::error;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
|
||||
pub mod settings;
|
||||
pub mod users;
|
||||
|
||||
pub type WebResult<T> = Result<T, AppError>;
|
||||
pub type TemplatedHtml = Html<String>;
|
||||
|
||||
#[derive(Debug, Error, Display)]
|
||||
pub enum AppError {
|
||||
/// An error occurred while templating
|
||||
Tera(#[from] tera::Error),
|
||||
|
||||
/// An error occurred while interacting with the database
|
||||
Sqlx(#[from] sqlx::Error),
|
||||
}
|
||||
|
||||
impl IntoResponse for AppError {
|
||||
fn into_response(self) -> axum::response::Response {
|
||||
let mut error_context = Context::new();
|
||||
error_context.insert("error", &format!("{self:?}"));
|
||||
error_context.insert("error", &self.to_string());
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Html(
|
||||
|
|
@ -154,8 +150,9 @@ async fn run() -> anyhow::Result<()> {
|
|||
let reloader = livereload.reloader();
|
||||
|
||||
let app = Router::new()
|
||||
.route("/protected", get(show_protected))
|
||||
.route_layer(login_required!(Backend, login_url = "/login"))
|
||||
.merge(users::routes())
|
||||
.merge(settings::routes())
|
||||
.route("/", get(show_index))
|
||||
.layer(auth_layer)
|
||||
.layer(livereload)
|
||||
|
|
@ -202,7 +199,7 @@ async fn run() -> anyhow::Result<()> {
|
|||
}
|
||||
|
||||
async fn show_index(renderer: Renderer) -> WebResult<Html<String>> {
|
||||
renderer.render_template("index.tera.html", None)
|
||||
renderer.render_template("index.tera.html", None).map(Html)
|
||||
}
|
||||
|
||||
async fn shutdown_signal(handle: AbortHandle) {
|
||||
|
|
@ -237,7 +234,7 @@ impl Renderer {
|
|||
&self,
|
||||
name: &str,
|
||||
context: impl Into<Option<Context>>,
|
||||
) -> WebResult<Html<String>> {
|
||||
) -> WebResult<String> {
|
||||
let mut main_context = Context::default();
|
||||
if let Some(user) = self.auth.user.as_ref() {
|
||||
main_context.insert("logged_in", &true);
|
||||
|
|
@ -246,6 +243,14 @@ impl Renderer {
|
|||
|
||||
main_context.extend(context.into().unwrap_or_default());
|
||||
|
||||
Ok(Html(TERA.read().unwrap().render(name, &main_context)?))
|
||||
Ok(TERA.read().unwrap().render(name, &main_context)?)
|
||||
}
|
||||
}
|
||||
|
||||
async fn show_protected(renderer: Renderer) -> Html<String> {
|
||||
Html(
|
||||
renderer
|
||||
.render_template("protected.tera.html", None)
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,147 +0,0 @@
|
|||
use axum::Form;
|
||||
use axum::Router;
|
||||
use axum::extract::State;
|
||||
use axum::response::IntoResponse;
|
||||
use axum::response::Redirect;
|
||||
use axum::routing::get;
|
||||
use axum::routing::post;
|
||||
use axum_login::login_required;
|
||||
use password_auth::generate_hash;
|
||||
use rand::distr::Alphanumeric;
|
||||
use rand::distr::SampleString;
|
||||
use rand::rng;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use sqlx::prelude::FromRow;
|
||||
use tera::Context;
|
||||
use time::Date;
|
||||
use time::Duration;
|
||||
use time::OffsetDateTime;
|
||||
use time::Time;
|
||||
|
||||
use crate::AppState;
|
||||
use crate::AuthSession;
|
||||
use crate::Renderer;
|
||||
use crate::TemplatedHtml;
|
||||
use crate::WebResult;
|
||||
|
||||
pub fn routes() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/settings", get(show_settings))
|
||||
.route("/settings/change_password", get(show_change_password))
|
||||
.route("/settings/change_password", post(do_change_password))
|
||||
.route("/settings/api_keys", get(show_api_keys))
|
||||
.route("/settings/new_api_key", post(create_new_api_key))
|
||||
.route("/settings/revoke_api_key", post(revoke_api_key))
|
||||
.route_layer(login_required!(crate::Backend, login_url = "/login"))
|
||||
}
|
||||
|
||||
async fn show_settings(renderer: Renderer) -> WebResult<TemplatedHtml> {
|
||||
renderer.render_template("settings/index.tera.html", None)
|
||||
}
|
||||
|
||||
async fn show_change_password(renderer: Renderer) -> WebResult<TemplatedHtml> {
|
||||
renderer.render_template("settings/change_password.tera.html", None)
|
||||
}
|
||||
|
||||
async fn do_change_password() {}
|
||||
|
||||
#[derive(Debug, FromRow, Serialize)]
|
||||
pub struct ApiKey {
|
||||
id: i64,
|
||||
user_id: i64,
|
||||
token: Vec<u8>,
|
||||
name: String,
|
||||
expiration_date: OffsetDateTime,
|
||||
permissions: String,
|
||||
revoked: bool,
|
||||
}
|
||||
|
||||
async fn show_api_keys(
|
||||
renderer: Renderer,
|
||||
app_state: State<AppState>,
|
||||
auth: AuthSession,
|
||||
) -> WebResult<TemplatedHtml> {
|
||||
let mut context = Context::new();
|
||||
|
||||
let api_keys: Vec<ApiKey> = sqlx::query_as("SELECT * FROM api_keys WHERE user_id = ?")
|
||||
.bind(auth.user.unwrap().id())
|
||||
.fetch_all(&app_state.db)
|
||||
.await?;
|
||||
|
||||
context.insert("api_keys", &api_keys.into_iter().map(|key| {
|
||||
serde_json::json! {{
|
||||
"id": key.id,
|
||||
"name": key.name,
|
||||
"revoked": key.revoked,
|
||||
"expiration_date": key.expiration_date.format(&time::format_description::well_known::iso8601::Iso8601::DATE).unwrap(),
|
||||
}}
|
||||
}).collect::<Vec<_>>());
|
||||
|
||||
context.insert(
|
||||
"one_year_date",
|
||||
&OffsetDateTime::now_utc()
|
||||
.replace_year(OffsetDateTime::now_utc().year() + 1)
|
||||
.unwrap()
|
||||
.format(&time::format_description::well_known::iso8601::Iso8601::DATE)
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
renderer.render_template("settings/api_keys.tera.html", context)
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct NewApiKeyForm {
|
||||
name: String,
|
||||
expiration_date: String,
|
||||
}
|
||||
|
||||
async fn create_new_api_key(
|
||||
app_state: State<AppState>,
|
||||
auth: AuthSession,
|
||||
new_api_key: Form<NewApiKeyForm>,
|
||||
) -> WebResult<impl IntoResponse> {
|
||||
let values = Alphanumeric.sample_string(&mut rng(), 32);
|
||||
let token = tokio::task::spawn_blocking(|| generate_hash(values))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let expiration_date = Date::parse(
|
||||
&new_api_key.expiration_date,
|
||||
&time::format_description::well_known::Iso8601::DATE,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let expiration_date =
|
||||
OffsetDateTime::new_utc(expiration_date, Time::from_hms(0, 0, 0).unwrap());
|
||||
|
||||
sqlx::query("INSERT INTO api_keys (user_id, token, name, expiration_date, permissions, revoked) VALUES (?, ?, ?, ?, ?, false)")
|
||||
.bind(auth.user.unwrap().id())
|
||||
.bind(token)
|
||||
.bind(new_api_key.name.clone())
|
||||
.bind(expiration_date)
|
||||
.bind("")
|
||||
.execute(&app_state.db)
|
||||
.await?;
|
||||
|
||||
Ok(Redirect::to("/settings/api_keys"))
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct RevokeApiKeyForm {
|
||||
api_key_id: i64,
|
||||
}
|
||||
|
||||
async fn revoke_api_key(
|
||||
app_state: State<AppState>,
|
||||
auth: AuthSession,
|
||||
revoke_api_key: Form<RevokeApiKeyForm>,
|
||||
) -> WebResult<impl IntoResponse> {
|
||||
sqlx::query("UPDATE api_keys SET revoked = true, token = \"\" WHERE id = ? AND user_id = ?")
|
||||
.bind(revoke_api_key.api_key_id)
|
||||
.bind(auth.user.unwrap().id())
|
||||
.execute(&app_state.db)
|
||||
.await?;
|
||||
|
||||
Ok(Redirect::to("/settings/api_keys"))
|
||||
}
|
||||
|
|
@ -10,7 +10,9 @@ use crate::UserCredentials;
|
|||
use crate::WebResult;
|
||||
|
||||
pub async fn show_login(renderer: Renderer) -> WebResult<Html<String>> {
|
||||
renderer.render_template("users/login.tera.html", None)
|
||||
renderer
|
||||
.render_template("users/login.tera.html", None)
|
||||
.map(Html)
|
||||
}
|
||||
|
||||
pub async fn do_login(
|
||||
|
|
|
|||
|
|
@ -11,7 +11,9 @@ use crate::UserCredentials;
|
|||
use crate::WebResult;
|
||||
|
||||
pub async fn show_register(renderer: Renderer) -> WebResult<Html<String>> {
|
||||
renderer.render_template("users/register.tera.html", None)
|
||||
renderer
|
||||
.render_template("users/register.tera.html", None)
|
||||
.map(Html)
|
||||
}
|
||||
|
||||
pub async fn do_register(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{% macro text_input(label, name, id="",type="text",value="") %}
|
||||
{% macro text_input(label, name, id="",type="text") %}
|
||||
<div class="flex flex-col">
|
||||
<label for="{{id}}" class="font-bold">{{label}}:</label>
|
||||
<input class="border rounded p-1 bg-gray-50" type="{{type}}" name="{{name}}" id="{{id}}" {% if value %}value="{{value}}"{% endif %} />
|
||||
<input class="border rounded p-1 bg-gray-50" type="{{type}}" name="{{name}}" id="{{id}}" />
|
||||
</div>
|
||||
{% endmacro text_input %}
|
||||
|
|
|
|||
|
|
@ -1,25 +0,0 @@
|
|||
{% extends "base.tera.html" %}
|
||||
{% import "inputs.tera.html" as inputs %}
|
||||
|
||||
{% block title %}
|
||||
Home
|
||||
{% endblock title %}
|
||||
|
||||
{% block content %}
|
||||
<div class="place-self-center mx-auto p-4 space-y-4 bg-red-200 md:border border-red-300 shadow-xl md:rounded">
|
||||
<h1 class="font-bold text-2xl">There was an internal error</h1>
|
||||
<p>
|
||||
{{ error }}
|
||||
</p>
|
||||
|
||||
<hr class="bg-red-800 border-0 h-px">
|
||||
|
||||
<p>
|
||||
This error usually indicates that the problem is not on your side.
|
||||
<br>
|
||||
Additional information was logged for the operator.
|
||||
<br>
|
||||
Feel free to try again later.
|
||||
</p>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
{% extends "base.tera.html" %}
|
||||
{% import "inputs.tera.html" as inputs %}
|
||||
|
||||
{% block title %}
|
||||
API Keys - {{ current_user.username }}
|
||||
{% endblock title %}
|
||||
|
||||
{% block content %}
|
||||
<div class="flex flex-col md:flex-row grow">
|
||||
<div class="basis-1/4 p-4 m-4 space-y-4">
|
||||
{% include "settings/sidebar.tera.html" %}
|
||||
</div>
|
||||
<div class="basis-3/4 p-4">
|
||||
<h1 class="font-bold text-3xl">API Keys</h1>
|
||||
<form action="/settings/new_api_key" method="POST" class="space-y-4">
|
||||
{{ inputs::text_input(label="Name", name="name", id="name", type="text") }}
|
||||
{{ inputs::text_input(label="Expiration Date", name="expiration_date", id="expiration_date", type="date", value=one_year_date)}}
|
||||
<div class="flex flex-col">
|
||||
<button type="submit" class="bg-blue-500 p-2 rounded-lg text-white">
|
||||
Create API Key
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<hr class="my-4 w-1/2 border-0 bg-zinc-300 h-0.5 mx-auto">
|
||||
|
||||
<h2 class="font-bold text-2xl my-4">Current API Tokens</h2>
|
||||
|
||||
<table class="border-collapse border-spacing-2 border border-zinc-400 w-full">
|
||||
<thead class="bg-orange-50">
|
||||
<tr>
|
||||
<th class="border border-zinc-300 w-1/4 p-4 font-semibold">Name</th>
|
||||
<th class="border border-zinc-300 w-1/4 p-4 font-semibold">Active</th>
|
||||
<th class="border border-zinc-300 w-1/4 p-4 font-semibold">Expiration Date</th>
|
||||
<th class="border border-zinc-300 w-1/4 p-4 font-semibold">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for api_key in api_keys %}
|
||||
<tr>
|
||||
<td class="border border-zinc-200 p-2">{{ api_key.name}}</td>
|
||||
<td class="border border-zinc-200 p-2 text-center">
|
||||
{% if api_key.revoked %}
|
||||
<span class="align-middle">No Longer Active</span><span class="inline-block align-middle size-6 ml-2 bg-zinc-500 rounded-lg"></span>
|
||||
{% else %}
|
||||
<span class="align-middle">Currently Active</span><span class="inline-block align-middle size-6 ml-2 bg-green-500 rounded-lg"></span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="border border-zinc-200 p-2 text-center">{{ api_key.expiration_date }}</td>
|
||||
<td class="border border-zinc-200 p-2 text-center">
|
||||
{% if api_key.revoked %}
|
||||
<span class="text-zinc-700">A revoked API Key cannot be revived</span>
|
||||
{% else %}
|
||||
<form action="/settings/revoke_api_key" method="POST">
|
||||
<input type="hidden" name="api_key_id" value="{{ api_key.id }}">
|
||||
<button type="submit" class="px-4 py-2 inline-block border border-red-700 bg-red-600 text-white rounded pointer-link">
|
||||
Revoke API Key
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
<tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
{% extends "base.tera.html" %}
|
||||
{% import "inputs.tera.html" as inputs %}
|
||||
|
||||
{% block title %}
|
||||
Change Password - {{ current_user.username }}
|
||||
{% endblock title %}
|
||||
|
||||
{% block content %}
|
||||
<div class="flex flex-col md:flex-row grow">
|
||||
<div class="basis-1/4 p-4 m-4 space-y-4">
|
||||
{% include "settings/sidebar.tera.html" %}
|
||||
</div>
|
||||
<div class="basis-3/4 p-4">
|
||||
<h1 class="font-bold text-3xl">Change Password</h1>
|
||||
<form action="/settings/change_password" method="POST" class="space-y-4">
|
||||
{{ inputs::text_input(label="Old Password", name="old_password", id="old_password", type="password") }}
|
||||
{{ inputs::text_input(label="New Password", name="password", id="password", type="password") }}
|
||||
{{ inputs::text_input(label="Repeat New Password", name="confirm_password", id="confirm_password", type="password") }}
|
||||
<div class="flex flex-col">
|
||||
<button type="submit" class="bg-blue-500 p-2 rounded-lg text-white">
|
||||
Change Password
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
{% extends "base.tera.html" %}
|
||||
{% import "inputs.tera.html" as inputs %}
|
||||
|
||||
{% block title %}
|
||||
Settings - {{ current_user.username }}
|
||||
{% endblock title %}
|
||||
|
||||
{% block content %}
|
||||
<div class="flex flex-col md:flex-row grow">
|
||||
<div class="basis-1/4 p-4 m-4 space-y-4">
|
||||
{% include "settings/sidebar.tera.html" %}
|
||||
</div>
|
||||
<div class="basis-3/4 p-4">
|
||||
<p>THIS IS YOUR MAIN SETTINGS WOOHOOOO</p>
|
||||
|
||||
<p>Your username is: <em>{{ current_user.username }}</em></p>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
<h2 class="text-xl font-bold px-4">Settings</h2>
|
||||
<ul class="space-y-2 bg-zinc-200 p-2 rounded border border-zinc-400 shadow">
|
||||
<li class="hover:bg-zinc-400">
|
||||
<a class="p-1 px-2 inline-block w-full" href="/settings">Overview</a>
|
||||
</li>
|
||||
<li class="hover:bg-zinc-400">
|
||||
<a class="p-1 px-2 inline-block w-full" href="/settings/change_password">Change Password</a>
|
||||
</li>
|
||||
<li class="hover:bg-zinc-400">
|
||||
<a class="p-1 px-2 inline-block w-full" href="/settings/api_keys">API Keys</a>
|
||||
</li>
|
||||
</ul>
|
||||
Loading…
Add table
Add a link
Reference in a new issue