Compare commits
11 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e53466a2ea | |||
| 1214373c00 | |||
| b70bce66ef | |||
| 65c01fb42f | |||
| 9e8c51790e | |||
| 411fdd7bf3 | |||
| c4c9ebea54 | |||
| 7c607ff514 | |||
| 9a7bd1b476 | |||
| 8eff606e9e | |||
| 29609dbc67 |
23 changed files with 322 additions and 176 deletions
|
|
@ -38,7 +38,7 @@ pub struct TokenRequest<'r> {
|
||||||
pub grant_type: GrantType,
|
pub grant_type: GrantType,
|
||||||
pub code: Option<&'r str>,
|
pub code: Option<&'r str>,
|
||||||
pub redirect_uri: Option<&'r str>,
|
pub redirect_uri: Option<&'r str>,
|
||||||
pub client_id: Option<&'r str>,
|
pub client_id: &'r str,
|
||||||
pub client_secret: Option<&'r str>,
|
pub client_secret: Option<&'r str>,
|
||||||
pub scope: Option<&'r str>,
|
pub scope: Option<&'r str>,
|
||||||
pub refresh_token: Option<&'r str>,
|
pub refresh_token: Option<&'r str>,
|
||||||
|
|
|
||||||
|
|
@ -51,14 +51,12 @@ pub enum TokenError {
|
||||||
RefreshTokenExpired,
|
RefreshTokenExpired,
|
||||||
AuthorizationCodeUsed,
|
AuthorizationCodeUsed,
|
||||||
AuthorizationCodeExpired,
|
AuthorizationCodeExpired,
|
||||||
|
HttpAuthDifferentClientId,
|
||||||
AppError(apps::Error),
|
AppError(apps::Error),
|
||||||
AppNotFoundFromAuthorizationCode(String),
|
AppNotFound(String),
|
||||||
AppNotFoundFromRefreshToken(String),
|
|
||||||
AppIdNotProvided,
|
|
||||||
AppSecretNotProvided,
|
AppSecretNotProvided,
|
||||||
Blocking(task::JoinError),
|
Blocking(task::JoinError),
|
||||||
SecretCompare(hash::Error),
|
SecretCompare(hash::Error),
|
||||||
AppIdWrong,
|
|
||||||
AppSecretWrong,
|
AppSecretWrong,
|
||||||
UserError(users::Error),
|
UserError(users::Error),
|
||||||
UserNotFound,
|
UserNotFound,
|
||||||
|
|
@ -113,19 +111,14 @@ impl<'r> Responder<'r, 'static> for TokenError {
|
||||||
Status::BadRequest,
|
Status::BadRequest,
|
||||||
"Authorization code has expired".to_string(),
|
"Authorization code has expired".to_string(),
|
||||||
),
|
),
|
||||||
TokenError::AppError(e) => (Status::InternalServerError, e.to_string()),
|
TokenError::HttpAuthDifferentClientId => (
|
||||||
TokenError::AppNotFoundFromAuthorizationCode(e) => (
|
|
||||||
Status::NotFound,
|
|
||||||
format!("Could not find application from authorization code {e}"),
|
|
||||||
),
|
|
||||||
TokenError::AppNotFoundFromRefreshToken(e) => (
|
|
||||||
Status::NotFound,
|
|
||||||
format!("Could not find application from refresh token {e}"),
|
|
||||||
),
|
|
||||||
TokenError::AppIdNotProvided => (
|
|
||||||
Status::BadRequest,
|
Status::BadRequest,
|
||||||
"Could not get client_id: not provided in any way".to_string(),
|
"HTTP Auth differs from provided client_id".to_string(),
|
||||||
),
|
),
|
||||||
|
TokenError::AppError(e) => (Status::InternalServerError, e.to_string()),
|
||||||
|
TokenError::AppNotFound(e) => {
|
||||||
|
(Status::NotFound, format!("Could not find application {e}"))
|
||||||
|
}
|
||||||
TokenError::AppSecretNotProvided => {
|
TokenError::AppSecretNotProvided => {
|
||||||
(Status::BadRequest, "Secret was not provided".to_string())
|
(Status::BadRequest, "Secret was not provided".to_string())
|
||||||
}
|
}
|
||||||
|
|
@ -134,7 +127,6 @@ impl<'r> Responder<'r, 'static> for TokenError {
|
||||||
Status::InternalServerError,
|
Status::InternalServerError,
|
||||||
format!("Failed to check app secret: {e}"),
|
format!("Failed to check app secret: {e}"),
|
||||||
),
|
),
|
||||||
TokenError::AppIdWrong => (Status::Forbidden, "Invalid client_id provided".to_string()),
|
|
||||||
TokenError::AppSecretWrong => {
|
TokenError::AppSecretWrong => {
|
||||||
(Status::Forbidden, "Invalid secret provided".to_string())
|
(Status::Forbidden, "Invalid secret provided".to_string())
|
||||||
}
|
}
|
||||||
|
|
@ -179,8 +171,8 @@ pub async fn request_token(
|
||||||
) -> std::result::Result<TokenResponse, TokenError> {
|
) -> std::result::Result<TokenResponse, TokenError> {
|
||||||
let mut transaction = db.begin().await.map_err(TokenError::TransactionStart)?;
|
let mut transaction = db.begin().await.map_err(TokenError::TransactionStart)?;
|
||||||
|
|
||||||
// Get user and app depending on grant type
|
// Get user depending on grant type
|
||||||
let (user, app) = match token_request.grant_type {
|
let user = match token_request.grant_type {
|
||||||
GrantType::AuthorizationCode => {
|
GrantType::AuthorizationCode => {
|
||||||
let authorization_code = token_request
|
let authorization_code = token_request
|
||||||
.code
|
.code
|
||||||
|
|
@ -228,20 +220,12 @@ pub async fn request_token(
|
||||||
return Err(TokenError::UserArchived(user.id().to_string()));
|
return Err(TokenError::UserArchived(user.id().to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get app from code
|
|
||||||
let app = App::get_one_from_authorization_code(&mut transaction, authorization_code)
|
|
||||||
.await
|
|
||||||
.map_err(TokenError::AppError)?
|
|
||||||
.ok_or(TokenError::AppNotFoundFromAuthorizationCode(
|
|
||||||
authorization_code.into(),
|
|
||||||
))?;
|
|
||||||
|
|
||||||
// Mark code as used
|
// Mark code as used
|
||||||
code.use_code(&mut transaction)
|
code.use_code(&mut transaction)
|
||||||
.await
|
.await
|
||||||
.map_err(TokenError::AuthorizationError)?;
|
.map_err(TokenError::AuthorizationError)?;
|
||||||
|
|
||||||
(user, app)
|
user
|
||||||
}
|
}
|
||||||
GrantType::RefreshToken => {
|
GrantType::RefreshToken => {
|
||||||
let refresh_token = token_request
|
let refresh_token = token_request
|
||||||
|
|
@ -280,39 +264,28 @@ pub async fn request_token(
|
||||||
return Err(TokenError::RefreshTokenExpired);
|
return Err(TokenError::RefreshTokenExpired);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get app
|
|
||||||
let app = App::get_one_by_id(&mut transaction, refresh_token.app().as_ref())
|
|
||||||
.await
|
|
||||||
.map_err(TokenError::AppError)?
|
|
||||||
.ok_or(TokenError::AppNotFoundFromRefreshToken(format!(
|
|
||||||
"Refresh token for user {}",
|
|
||||||
refresh_token.user()
|
|
||||||
)))?;
|
|
||||||
|
|
||||||
refresh_token
|
refresh_token
|
||||||
.use_token(&mut transaction)
|
.use_token(&mut transaction)
|
||||||
.await
|
.await
|
||||||
.map_err(TokenError::RefreshTokenError)?;
|
.map_err(TokenError::RefreshTokenError)?;
|
||||||
|
|
||||||
(user, app)
|
user
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get client id
|
// If HTTP Basic Auth is provided, verify provided client id in form
|
||||||
// https://www.rfc-editor.org/rfc/rfc6749#section-4.1.3
|
if let Some(app_auth) = &app_auth {
|
||||||
let provided_client_id = match (&app_auth, token_request.client_id) {
|
if app_auth.id != token_request.client_id {
|
||||||
(Some(http_auth), _) => http_auth.id.to_string(),
|
return Err(TokenError::HttpAuthDifferentClientId);
|
||||||
(None, Some(form)) => form.into(),
|
|
||||||
(None, None) => {
|
|
||||||
return Err(TokenError::AppIdNotProvided);
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
// Verify client id
|
|
||||||
if app.id().as_ref() != provided_client_id {
|
|
||||||
return Err(TokenError::AppIdWrong);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get app
|
||||||
|
let app = App::get_one_by_id(&mut transaction, token_request.client_id)
|
||||||
|
.await
|
||||||
|
.map_err(TokenError::AppError)?
|
||||||
|
.ok_or(TokenError::AppNotFound(token_request.client_id.into()))?;
|
||||||
|
|
||||||
if app.is_confidential() {
|
if app.is_confidential() {
|
||||||
let provided_secret = match (app_auth, token_request.client_secret) {
|
let provided_secret = match (app_auth, token_request.client_secret) {
|
||||||
(Some(http_auth), _) => http_auth.password,
|
(Some(http_auth), _) => http_auth.password,
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,5 @@ pub mod content {
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
#[serde(crate = "rocket::serde")]
|
#[serde(crate = "rocket::serde")]
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ResetPassword {
|
pub struct ResetPassword {}
|
||||||
pub username: String,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,13 +12,11 @@ pub async fn reset_password_page(
|
||||||
return Err(Error::bad_request("Reset password token has expired"));
|
return Err(Error::bad_request("Reset password token has expired"));
|
||||||
}
|
}
|
||||||
|
|
||||||
let user = User::get_one_from_password_reset_token(&mut **db, &token.0)
|
User::get_one_from_password_reset_token(&mut **db, &token.0)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| Error::not_found("Invalid or expired token"))?;
|
.ok_or_else(|| Error::not_found("Invalid or expired token"))?;
|
||||||
|
|
||||||
let page = Page::ResetPassword(super::content::ResetPassword {
|
let page = Page::ResetPassword(super::content::ResetPassword {});
|
||||||
username: user.username().into(),
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(flash
|
Ok(flash
|
||||||
.map(|flash| Page::with_flash(page.clone(), flash))
|
.map(|flash| Page::with_flash(page.clone(), flash))
|
||||||
|
|
|
||||||
52
crates/ezidam/static/i18n/en.json
Normal file
52
crates/ezidam/static/i18n/en.json
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
{
|
||||||
|
"error.go_home": "Go to homepage",
|
||||||
|
"header.settings": "Settings",
|
||||||
|
"header.admin_panel": "Admin panel",
|
||||||
|
"header.logout": "Logout",
|
||||||
|
"setup.welcome": "Welcome to Ezidam!",
|
||||||
|
"setup.initial_setup": "Initial setup",
|
||||||
|
"setup.first_admin_account": "first admin account",
|
||||||
|
"setup.username": "Username",
|
||||||
|
"setup.password": "Password",
|
||||||
|
"setup.settings": "settings",
|
||||||
|
"setup.base_url": "Base URL",
|
||||||
|
"setup.finish": "Finish setup",
|
||||||
|
"forgot_password.title": "Forgot password",
|
||||||
|
"forgot_password.email": "Email",
|
||||||
|
"forgot_password.paper_key": "Paper key",
|
||||||
|
"forgot_password.email_description": "Enter your email address linked to your account. We will email you a link to reset your password.",
|
||||||
|
"forgot_password.email_address": "Email address",
|
||||||
|
"forgot_password.request": "Request password reset",
|
||||||
|
"forgot_password.paper_key_description": "Enter your login linked to your account, with your paper key.",
|
||||||
|
"forgot_password.login": "Login",
|
||||||
|
"reset_password.title": "Reset your password",
|
||||||
|
"reset_password.new_password": "New password",
|
||||||
|
"reset_password.confirm_password": "Confirm new password",
|
||||||
|
"reset_password.set_password": "Set new password",
|
||||||
|
"authorize.access_app": "Access [[app_name]]",
|
||||||
|
"authorize.with_account": "With your [[business_name]] account",
|
||||||
|
"authorize.login": "Login",
|
||||||
|
"authorize.password": "Password",
|
||||||
|
"authorize.authorize": "Authorize",
|
||||||
|
"authorize.forgot_password": "Forgot your password?",
|
||||||
|
"totp.verify_account": "Verify your account",
|
||||||
|
"totp.enter_code": "Enter the code displayed on your device",
|
||||||
|
"totp.verify_code": "Verify code",
|
||||||
|
"redirect.hello": "Hello!",
|
||||||
|
"redirect.preparing_app": "Preparing application",
|
||||||
|
"redirect.not_redirected": "Click here if you are not redirected",
|
||||||
|
"user_settings.settings": "Settings",
|
||||||
|
"user_settings.personal": "Personal",
|
||||||
|
"user_settings.security": "Security",
|
||||||
|
"user_settings.visual": "Visual",
|
||||||
|
"user_settings_visual.enable_dark": "Enable dark mode",
|
||||||
|
"user_settings_visual.enable_light": "Enable light mode",
|
||||||
|
"user_settings_personal.my_profile": "My profile",
|
||||||
|
"user_settings_personal.username": "Username",
|
||||||
|
"user_settings_personal.full_name": "Full name",
|
||||||
|
"user_settings_personal.email": "Email address",
|
||||||
|
"user_settings_personal.timezone": "Timezone",
|
||||||
|
"user_settings_personal.utc_default": "UTC (Default)",
|
||||||
|
"user_settings_personal.last_updated": "Profile last updated on",
|
||||||
|
"user_settings_personal.save": "Save"
|
||||||
|
}
|
||||||
52
crates/ezidam/static/i18n/fr.json
Normal file
52
crates/ezidam/static/i18n/fr.json
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
{
|
||||||
|
"error.go_home": "Aller à la page d'accueil",
|
||||||
|
"header.settings": "Réglages",
|
||||||
|
"header.admin_panel": "Console d'administration",
|
||||||
|
"header.logout": "Se déconnecter",
|
||||||
|
"setup.welcome": "Bienvenue sur Ezidam!",
|
||||||
|
"setup.initial_setup": "Configuration initiale",
|
||||||
|
"setup.first_admin_account": "premier compte administrateur",
|
||||||
|
"setup.username": "Nom d'utilisateur",
|
||||||
|
"setup.password": "Mot de passe",
|
||||||
|
"setup.settings": "réglages",
|
||||||
|
"setup.base_url": "URL de base",
|
||||||
|
"setup.finish": "Terminer la configuration",
|
||||||
|
"forgot_password.title": "Mot de passe oublié",
|
||||||
|
"forgot_password.email": "Email",
|
||||||
|
"forgot_password.paper_key": "Clé papier",
|
||||||
|
"forgot_password.email_description": "Entrez l'adresse email associée à votre compte. Nous allons vous envoyer par email un lien pour réinitialiser votre mot de passe.",
|
||||||
|
"forgot_password.email_address": "Adresse email",
|
||||||
|
"forgot_password.request": "Demander réinitialisation du mot de passe",
|
||||||
|
"forgot_password.paper_key_description": "Entrez votre nom d'utilisateur, ainsi que votre clé papier.",
|
||||||
|
"forgot_password.login": "Nom d'utilisateur",
|
||||||
|
"reset_password.title": "Réinitialisation du mot de passe",
|
||||||
|
"reset_password.new_password": "Nouveau mot de passe",
|
||||||
|
"reset_password.confirm_password": "Confirmer le mot de passe",
|
||||||
|
"reset_password.set_password": "Enregistrer le mot de passe",
|
||||||
|
"authorize.access_app": "Accéder à [[app_name]]",
|
||||||
|
"authorize.with_account": "Avec votre compte [[business_name]]",
|
||||||
|
"authorize.login": "Nom d'utilisateur",
|
||||||
|
"authorize.password": "Mot de passe",
|
||||||
|
"authorize.authorize": "Autoriser",
|
||||||
|
"authorize.forgot_password": "Mot de passe oublié?",
|
||||||
|
"totp.verify_account": "Vérification de votre compte",
|
||||||
|
"totp.enter_code": "Entrez le code de vérification sur votre appareil",
|
||||||
|
"totp.verify_code": "Verifier le code",
|
||||||
|
"redirect.hello": "Bonjour!",
|
||||||
|
"redirect.preparing_app": "Préparation de l'application",
|
||||||
|
"redirect.not_redirected": "Cliquez ici si vous n'êtes pas redirigé",
|
||||||
|
"user_settings.settings": "Réglages",
|
||||||
|
"user_settings.personal": "Personnel",
|
||||||
|
"user_settings.security": "Sécurité",
|
||||||
|
"user_settings.visual": "Visuel",
|
||||||
|
"user_settings_visual.enable_dark": "Activer le mode sombre",
|
||||||
|
"user_settings_visual.enable_light": "Activer le mode clair",
|
||||||
|
"user_settings_personal.my_profile": "Mon profil",
|
||||||
|
"user_settings_personal.username": "Nom d'utilisateur",
|
||||||
|
"user_settings_personal.full_name": "Nom complet",
|
||||||
|
"user_settings_personal.email": "Adresse email",
|
||||||
|
"user_settings_personal.timezone": "Fuseau horaire",
|
||||||
|
"user_settings_personal.utc_default": "UTC (par défaut)",
|
||||||
|
"user_settings_personal.last_updated": "Profil mis à jour le",
|
||||||
|
"user_settings_personal.save": "Enregistrer"
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
2
crates/ezidam/static/libs/i18next-http-backend/i18nextHttpBackend.min.js
vendored
Normal file
2
crates/ezidam/static/libs/i18next-http-backend/i18nextHttpBackend.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
crates/ezidam/static/libs/i18next/i18next.min.js
vendored
Normal file
2
crates/ezidam/static/libs/i18next/i18next.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
crates/ezidam/static/libs/loc-i18next/loc-i18next.min.js
vendored
Normal file
2
crates/ezidam/static/libs/loc-i18next/loc-i18next.min.js
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
// https://www.unpkg.com/loc-i18next@0.1.5/loc-i18next.min.js
|
||||||
|
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.locI18next=t()}(this,function(){"use strict";function e(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function t(t){for(var n=1;n<arguments.length;n++){var r=null!=arguments[n]?arguments[n]:{},i=Object.keys(r);"function"==typeof Object.getOwnPropertySymbols&&(i=i.concat(Object.getOwnPropertySymbols(r).filter(function(e){return Object.getOwnPropertyDescriptor(r,e).enumerable}))),i.forEach(function(n){e(t,n,r[n])})}return t}function n(e){function n(t,n,r){var i="text";if(0==n.indexOf("[")){var o=n.split("]");n=o[1],i=o[0].substr(1,o[0].length-1)}if(n=n.indexOf(";")==n.length-1?n.substr(0,n.length-2):n,"html"===i)t.innerHTML=e.t(n,c(r,t.innerHTML));else if("text"===i)t.textContent=e.t(n,c(r,t.textContent));else if("prepend"===i){var l=t.innerHTML.indexOf("<loc-i18n>"),u=t.innerHTML.indexOf("</loc-i18n>")+11;l>-1&&u>6&&(t.innerHTML=[t.innerHTML.substring(0,l),t.innerHTML.slice(u)].join("")),t.innerHTML=["<loc-i18n>",e.t(n,c(r,t.innerHTML)),"</loc-i18n>",t.innerHTML].join("")}else if("append"===i){var f=t.innerHTML.indexOf("<loc-i18n>"),s=t.innerHTML.indexOf("</loc-i18n>")+11;f>-1&&s>6&&(t.innerHTML=[t.innerHTML.substring(0,f),t.innerHTML.slice(s)].join("")),t.innerHTML=[t.innerHTML,"<loc-i18n>",e.t(n,c(r,t.innerHTML),"</loc-i18n>")].join("")}else if(0===i.indexOf("data-")){var a=i.substr("data-".length),p=e.t(n,c(r,t.getAttribute(a)));t.setAttribute(a,p),t.setAttribute(i,p)}else t.setAttribute(i,e.t(n,c(r,t.getAttribute(i))))}function i(e){return JSON.parse(e.replace(/:\s*"([^"]*)"/g,function(e,t){return': "'+t.replace(/:/g,"@colon@")+'"'}).replace(/:\s*'([^']*)'/g,function(e,t){return': "'+t.replace(/:/g,"@colon@")+'"'}).replace(/(['"])?([a-z0-9A-Z_]+)(['"])?\s*:/g,'"$2": ').replace(/@colon@/g,":"))}function o(e,r){var o=e.getAttribute(u.selectorAttr);if(o){var l=e,c=e.getAttribute(u.targetAttr);if(null!=c&&(l=e.querySelector(c)||e),r||!0!==u.useOptionsAttr||(r=i(e.getAttribute(u.optionsAttr)||"{}")),r=r||{},o.indexOf(";")>=0)for(var f=o.split(";"),s=0,a=f.length;s<a;s++)""!=f[s]&&n(l,f[s],r);else n(l,o,r);if(!0===u.useOptionsAttr){var p={};p=t({clone:p},r),delete p.lng,e.setAttribute(u.optionsAttr,JSON.stringify(p))}}}function l(e,t){for(var n=(null===t||void 0===t?void 0:t.document)||u.document,r=n.querySelectorAll(e),i=0;i<r.length;i++){for(var l=r[i],c=l.querySelectorAll("["+u.selectorAttr+"]"),f=c.length-1;f>-1;f--)o(c[f],t);o(l,t)}}var u=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};u=t({},r,u);var c=function(e,n){return u.parseDefaultValueFromContent?t({},e,{defaultValue:n}):e};return l}var r={selectorAttr:"data-i18n",targetAttr:"i18n-target",optionsAttr:"i18n-options",useOptionsAttr:!1,parseDefaultValueFromContent:!0,document:document};return{init:n}});
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8"/>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"/>
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"/>
|
||||||
|
|
@ -9,6 +9,11 @@
|
||||||
<link href="/css/tabler.min.css" rel="stylesheet"/>
|
<link href="/css/tabler.min.css" rel="stylesheet"/>
|
||||||
<link href="/css/tabler-vendors.min.css" rel="stylesheet"/>
|
<link href="/css/tabler-vendors.min.css" rel="stylesheet"/>
|
||||||
<link href="/css/demo.min.css" rel="stylesheet"/>
|
<link href="/css/demo.min.css" rel="stylesheet"/>
|
||||||
|
<!-- i18n -->
|
||||||
|
<script src="/libs/i18next/i18next.min.js"></script>
|
||||||
|
<script src="/libs/i18next-http-backend/i18nextHttpBackend.min.js"></script>
|
||||||
|
<script src="/libs/i18next-browser-language-detector/i18nextBrowserLanguageDetector.min.js"></script>
|
||||||
|
<script src="/libs/loc-i18next/loc-i18next.min.js"></script>
|
||||||
<!-- Favicon -->
|
<!-- Favicon -->
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
|
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
|
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
|
||||||
|
|
@ -27,5 +32,34 @@
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
{% block page %}{% endblock page %}
|
{% block page %}{% endblock page %}
|
||||||
|
|
||||||
|
<script>
|
||||||
|
i18next
|
||||||
|
.use(i18nextBrowserLanguageDetector)
|
||||||
|
.use(i18nextHttpBackend)
|
||||||
|
.init({
|
||||||
|
detection: {
|
||||||
|
order: ['localStorage', 'navigator'],
|
||||||
|
},
|
||||||
|
fallbackLng: 'en',
|
||||||
|
debug: false,
|
||||||
|
interpolation: {
|
||||||
|
prefix: "[[",
|
||||||
|
suffix: "]]"
|
||||||
|
},
|
||||||
|
backend: {
|
||||||
|
loadPath: '/i18n/[[lng]].json'
|
||||||
|
}
|
||||||
|
}, function (err, _t) {
|
||||||
|
if (err) {
|
||||||
|
return console.error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
localize = locI18next.init(i18next);
|
||||||
|
|
||||||
|
{% block i18n %}{% endblock i18n %}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</html>
|
</html>
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
<body class=" border-top-wide border-primary d-flex flex-column">
|
<body class=" border-top-wide border-primary d-flex flex-column">
|
||||||
<script src="/js/demo-theme.min.js"></script>
|
<script src="/js/demo-theme.min.js"></script>
|
||||||
<div class="page page-center">
|
<div class="page page-center">
|
||||||
<div class="container-tight py-4">
|
<div class="container-tight py-4" id="error_page">
|
||||||
<div class="empty">
|
<div class="empty">
|
||||||
<div class="empty-header">{{ http_code }}</div>
|
<div class="empty-header">{{ http_code }}</div>
|
||||||
<p class="empty-title">{{ http_reason }}</p>
|
<p class="empty-title">{{ http_reason }}</p>
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
<div class="empty-action">
|
<div class="empty-action">
|
||||||
<a href="/" class="btn btn-primary">
|
<a href="/" class="btn btn-primary">
|
||||||
{% include "icons/arrow-left" %}
|
{% include "icons/arrow-left" %}
|
||||||
Take me home
|
<label data-i18n="error.go_home"></label>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -25,4 +25,8 @@
|
||||||
<script src="/js/tabler.min.js" defer></script>
|
<script src="/js/tabler.min.js" defer></script>
|
||||||
<script src="/js/demo.min.js" defer></script>
|
<script src="/js/demo.min.js" defer></script>
|
||||||
</body>
|
</body>
|
||||||
{% endblock page %}
|
{% endblock page %}
|
||||||
|
|
||||||
|
{% block i18n %}
|
||||||
|
localize("#error_page");
|
||||||
|
{% endblock i18n %}
|
||||||
|
|
|
||||||
|
|
@ -16,66 +16,55 @@
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="card">
|
<div class="card" id="forgot_password_card">
|
||||||
<h2 class="card-title text-center my-4 h2">Reset your password</h2>
|
<h2 class="card-title text-center my-4 h2" data-i18n="forgot_password.title"></h2>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<ul class="nav nav-tabs card-header-tabs nav-fill" data-bs-toggle="tabs">
|
<ul class="nav nav-tabs card-header-tabs nav-fill" data-bs-toggle="tabs">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a href="#tabs-email" class="nav-link active" data-bs-toggle="tab">Email</a>
|
<a href="#tabs-email" class="nav-link active" data-bs-toggle="tab" data-i18n="forgot_password.email"></a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a href="#tabs-paper-key" class="nav-link" data-bs-toggle="tab">Paper key</a>
|
<a href="#tabs-paper-key" class="nav-link" data-bs-toggle="tab" data-i18n="forgot_password.paper_key"></a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
<div class="tab-pane active show" id="tabs-email">
|
<div class="tab-pane active show" id="tabs-email">
|
||||||
<p class="mt-2 mb-4">
|
<p class="mt-2 mb-4" data-i18n="forgot_password.email_description"></p>
|
||||||
Enter your email address linked to your account. We will email you a link to reset your
|
|
||||||
password.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<form class="mb-2" action="/forgot-password/email" method="post" autocomplete="off"
|
<form class="mb-2" action="/forgot-password/email" method="post" autocomplete="off"
|
||||||
novalidate>
|
novalidate>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label required" for="email">Email address</label>
|
<label class="form-label required" for="email" data-i18n="forgot_password.email_address"></label>
|
||||||
<input id="email" type="email" name="email" class="form-control"
|
<input id="email" type="email" name="email" class="form-control" required>
|
||||||
placeholder="Enter email"
|
|
||||||
required>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-footer">
|
<div class="form-footer">
|
||||||
<button type="submit" class="btn btn-primary w-100">
|
<button type="submit" class="btn btn-primary w-100">
|
||||||
{% include "icons/mail" %}
|
{% include "icons/mail" %}
|
||||||
Request password reset
|
<label data-i18n="forgot_password.request"></label>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tab-pane" id="tabs-paper-key">
|
<div class="tab-pane" id="tabs-paper-key">
|
||||||
<p class="mt-2 mb-4">
|
<p class="mt-2 mb-4" data-i18n="forgot_password.paper_key_description"></p>
|
||||||
Enter your login linked to your account, with your paper key.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<form class="mb-2" action="/forgot-password/paper-key" method="post" autocomplete="off"
|
<form class="mb-2" action="/forgot-password/paper-key" method="post" autocomplete="off"
|
||||||
novalidate>
|
novalidate>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label required" for="login">Login</label>
|
<label class="form-label required" for="login" data-i18n="forgot_password.login"></label>
|
||||||
<input id="login" type="text" name="login" class="form-control"
|
<input id="login" type="text" name="login" class="form-control" required>
|
||||||
placeholder="Email or username"
|
|
||||||
required>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label required" for="paper_key">Paper key</label>
|
<label class="form-label required" for="paper_key" data-i18n="forgot_password.paper_key"></label>
|
||||||
<input id="paper_key" type="text" name="paper_key" class="form-control"
|
<input id="paper_key" type="text" name="paper_key" class="form-control" required>
|
||||||
placeholder="Enter your paper key"
|
|
||||||
required>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-footer">
|
<div class="form-footer">
|
||||||
<button type="submit" class="btn btn-primary w-100">
|
<button type="submit" class="btn btn-primary w-100">
|
||||||
{% include "icons/paperclip" %}
|
{% include "icons/paperclip" %}
|
||||||
Request password reset
|
<label data-i18n="forgot_password.request"></label>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
@ -94,3 +83,7 @@
|
||||||
<script src="/js/demo.min.js" defer></script>
|
<script src="/js/demo.min.js" defer></script>
|
||||||
</body>
|
</body>
|
||||||
{% endblock page %}
|
{% endblock page %}
|
||||||
|
|
||||||
|
{% block i18n %}
|
||||||
|
localize("#forgot_password_card");
|
||||||
|
{% endblock i18n %}
|
||||||
|
|
|
||||||
|
|
@ -2,3 +2,7 @@
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
||||||
|
{% block i18n %}
|
||||||
|
localize("#header_user_nav");
|
||||||
|
{% endblock i18n %}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
<script src="/js/demo-theme.min.js"></script>
|
<script src="/js/demo-theme.min.js"></script>
|
||||||
<div>
|
<div>
|
||||||
<div class="min-vh-100 d-flex flex-column justify-content-between">
|
<div class="min-vh-100 d-flex flex-column justify-content-between">
|
||||||
<div class="container container-tight py-4">
|
<div class="container container-tight py-4" id="authorize_page">
|
||||||
<div class="text-center mb-4">
|
<div class="text-center mb-4">
|
||||||
{% include "utils/logo" %}
|
{% include "utils/logo" %}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -22,8 +22,8 @@
|
||||||
<div class="card card-md">
|
<div class="card card-md">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="text-center mb-2">
|
<div class="text-center mb-2">
|
||||||
<h2 class="h2">Access {{ app_name }}</h2>
|
<h2 class="h2" data-i18n="authorize.access_app"></h2>
|
||||||
<p class="text-muted">With your {{ business_name }} account</p>
|
<p class="text-muted" data-i18n="authorize.with_account"></p>
|
||||||
</div>
|
</div>
|
||||||
<form id="authorize_form" action="" method="post" autocomplete="off" novalidate class="mt-4">
|
<form id="authorize_form" action="" method="post" autocomplete="off" novalidate class="mt-4">
|
||||||
{% if user %}
|
{% if user %}
|
||||||
|
|
@ -39,23 +39,19 @@
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label" for="login">Login</label>
|
<label class="form-label" for="login" data-i18n="authorize.login"></label>
|
||||||
<input id="login" name="login" type="text" class="form-control"
|
<input id="login" name="login" type="text" class="form-control" autocomplete="off">
|
||||||
placeholder="Email or username"
|
|
||||||
autocomplete="off">
|
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
<label class="form-label" for="password">Password</label>
|
<label class="form-label" for="password" data-i18n="authorize.password"></label>
|
||||||
<div class="input-group input-group-flat">
|
<div class="input-group input-group-flat">
|
||||||
<input id="password" name="password" type="password" class="form-control"
|
<input id="password" name="password" type="password" class="form-control" autocomplete="off">
|
||||||
placeholder="Your password"
|
|
||||||
autocomplete="off">
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="form-footer">
|
<div class="form-footer">
|
||||||
<button type="submit" class="btn btn-primary w-100">Authorize</button>
|
<button type="submit" class="btn btn-primary w-100" data-i18n="authorize.authorize"></button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -63,7 +59,7 @@
|
||||||
{% if user %}
|
{% if user %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="text-center text-muted mt-3">
|
<div class="text-center text-muted mt-3">
|
||||||
<a href="/forgot-password" tabindex="-1">Forgot your password?</a>
|
<a href="/forgot-password" tabindex="-1" data-i18n="authorize.forgot_password"></a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -79,3 +75,10 @@
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
{% endblock page %}
|
{% endblock page %}
|
||||||
|
|
||||||
|
{% block i18n %}
|
||||||
|
localize("#authorize_page", {
|
||||||
|
'app_name': '{{app_name}}',
|
||||||
|
'business_name': '{{business_name}}',
|
||||||
|
});
|
||||||
|
{% endblock i18n %}
|
||||||
|
|
|
||||||
|
|
@ -8,15 +8,15 @@
|
||||||
<div>
|
<div>
|
||||||
<div class="min-vh-100 d-flex flex-column justify-content-between">
|
<div class="min-vh-100 d-flex flex-column justify-content-between">
|
||||||
<div class="page page-center">
|
<div class="page page-center">
|
||||||
<div class="container container-tight py-4">
|
<div class="container container-tight py-4" id="redirect_page">
|
||||||
<div class="text-center mb-4">
|
<div class="text-center mb-4">
|
||||||
{% include "utils/logo" %}
|
{% include "utils/logo" %}
|
||||||
</div>
|
</div>
|
||||||
<div class="card card-md">
|
<div class="card card-md">
|
||||||
<div class="card-body text-center">
|
<div class="card-body text-center">
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<h2 class="card-title">Hello!</h2>
|
<h2 class="card-title" data-i18n="redirect.hello"></h2>
|
||||||
<p class="text-muted">Preparing application</p>
|
<p class="text-muted" data-i18n="redirect.preparing_app"></p>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
{{ user::avatar(username=username, name=name, size="xl", css="mb-3") }}
|
{{ user::avatar(username=username, name=name, size="xl", css="mb-3") }}
|
||||||
|
|
@ -34,7 +34,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-center text-muted mt-3">
|
<div class="text-center text-muted mt-3">
|
||||||
<a href="{{ home_page }}" tabindex="-1">Click here if you are not redirected</a>
|
<a href="{{ home_page }}" tabindex="-1" data-i18n="redirect.not_redirected"></a>
|
||||||
<meta http-equiv="refresh" content="2; url={{ home_page }}">
|
<meta http-equiv="refresh" content="2; url={{ home_page }}">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -48,3 +48,7 @@
|
||||||
<script src="/js/demo.min.js" defer></script>
|
<script src="/js/demo.min.js" defer></script>
|
||||||
</body>
|
</body>
|
||||||
{% endblock page %}
|
{% endblock page %}
|
||||||
|
|
||||||
|
{% block i18n %}
|
||||||
|
localize("#redirect_page");
|
||||||
|
{% endblock i18n %}
|
||||||
|
|
|
||||||
|
|
@ -19,11 +19,11 @@
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="card card-md">
|
<div class="card card-md" id="totp_page">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="text-center mb-4">
|
<div class="text-center mb-4">
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<h2 class="card-title">Verify your account</h2>
|
<h2 class="card-title" data-i18n="totp.verify_account"></h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
{{ user::avatar(username=username, name=name, size="lg", css="mb-3") }}
|
{{ user::avatar(username=username, name=name, size="lg", css="mb-3") }}
|
||||||
|
|
@ -38,9 +38,7 @@
|
||||||
</div>
|
</div>
|
||||||
<form id="totp_form" action="" method="post" autocomplete="off" novalidate class="mt-4">
|
<form id="totp_form" action="" method="post" autocomplete="off" novalidate class="mt-4">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label required" for="code">
|
<label class="form-label required" for="code" data-i18n="totp.enter_code"></label>
|
||||||
Enter the code displayed on your device
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
class="form-control"
|
class="form-control"
|
||||||
type="text"
|
type="text"
|
||||||
|
|
@ -54,7 +52,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-footer">
|
<div class="form-footer">
|
||||||
<button type="submit" class="btn btn-primary w-100">Verify code</button>
|
<button type="submit" class="btn btn-primary w-100" data-i18n="totp.verify_code"></button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -72,3 +70,7 @@
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
{% endblock page %}
|
{% endblock page %}
|
||||||
|
|
||||||
|
{% block i18n %}
|
||||||
|
localize("#totp_page");
|
||||||
|
{% endblock i18n %}
|
||||||
|
|
|
||||||
|
|
@ -16,27 +16,25 @@
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="card">
|
<div class="card" id="reset_password_card">
|
||||||
<h2 class="card-title text-center my-4 h2">Reset your password</h2>
|
<h2 class="card-title text-center my-4 h2" data-i18n="reset_password.title"></h2>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
|
||||||
<p class="mb-4 text-center">Resetting password for <code>{{ username }}</code></p>
|
|
||||||
|
|
||||||
<form class="mb-2" action="" method="post" autocomplete="off" novalidate>
|
<form class="mb-2" action="" method="post" autocomplete="off" novalidate>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label required" for="password">New password</label>
|
<label class="form-label required" for="password" data-i18n="reset_password.new_password"></label>
|
||||||
<input name="password" id="password" type="password" class="form-control" placeholder="Enter new password" required>
|
<input name="password" id="password" type="password" class="form-control" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label required" for="confirm_password">Confirm new password</label>
|
<label class="form-label required" for="confirm_password" data-i18n="reset_password.confirm_password"></label>
|
||||||
<input name="confirm_password" id="confirm_password" type="password" class="form-control" placeholder="Confirm new password" required>
|
<input name="confirm_password" id="confirm_password" type="password" class="form-control" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-footer">
|
<div class="form-footer">
|
||||||
<button type="submit" class="btn btn-primary w-100">
|
<button type="submit" class="btn btn-primary w-100">
|
||||||
{% include "icons/password" %}
|
{% include "icons/password" %}
|
||||||
Set new password
|
<label data-i18n="reset_password.set_password"></label>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
@ -54,3 +52,7 @@
|
||||||
<script src="/js/demo.min.js" defer></script>
|
<script src="/js/demo.min.js" defer></script>
|
||||||
</body>
|
</body>
|
||||||
{% endblock page %}
|
{% endblock page %}
|
||||||
|
|
||||||
|
{% block i18n %}
|
||||||
|
localize("#reset_password_card");
|
||||||
|
{% endblock i18n %}
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,11 @@
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<!-- Page header -->
|
<!-- Page header -->
|
||||||
<div class="page-header d-print-none">
|
<div class="page-header d-print-none" id="settings_header">
|
||||||
<div class="container-xl">
|
<div class="container-xl">
|
||||||
<div class="row g-2 align-items-center">
|
<div class="row g-2 align-items-center">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<h2 class="page-title">
|
<h2 class="page-title" data-i18n="user_settings.settings"></h2>
|
||||||
Settings
|
|
||||||
</h2>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -23,41 +21,41 @@
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="card">
|
<div class="card" id="settings_personal_page">
|
||||||
<div class="row g-0">
|
<div class="row g-0">
|
||||||
<div class="col-3 d-none d-md-block border-end">
|
<div class="col-3 d-none d-md-block border-end">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="list-group list-group-transparent">
|
<div class="list-group list-group-transparent">
|
||||||
<a href="./personal"
|
<a href="./personal"
|
||||||
class="list-group-item list-group-item-action d-flex align-items-center active">Personal</a>
|
class="list-group-item list-group-item-action d-flex align-items-center active"
|
||||||
|
data-i18n="user_settings.personal"></a>
|
||||||
<a href="./security"
|
<a href="./security"
|
||||||
class="list-group-item list-group-item-action d-flex align-items-center">Security</a>
|
class="list-group-item list-group-item-action d-flex align-items-center"
|
||||||
|
data-i18n="user_settings.security"></a>
|
||||||
<a href="./visual"
|
<a href="./visual"
|
||||||
class="list-group-item list-group-item-action d-flex align-items-center">Visual</a>
|
class="list-group-item list-group-item-action d-flex align-items-center"
|
||||||
|
data-i18n="user_settings.visual"></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col d-flex flex-column">
|
<div class="col d-flex flex-column">
|
||||||
<form action="" method="post">
|
<form action="" method="post">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h3 class="card-title">My Profile</h3>
|
<h3 class="card-title" data-i18n="user_settings_personal.my_profile"></h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<!-- Username -->
|
<!-- Username -->
|
||||||
<label class="form-label required" for="username">Username</label>
|
<label class="form-label required" for="username" data-i18n="user_settings_personal.username"></label>
|
||||||
<div class="input-icon mb-3">
|
<div class="input-icon mb-3">
|
||||||
<span class="input-icon-addon">
|
<span class="input-icon-addon">
|
||||||
{% include "icons/id-badge-2" %}
|
{% include "icons/id-badge-2" %}
|
||||||
</span>
|
</span>
|
||||||
<input name="username" id="username" value="{{ username }}" type="text"
|
<input name="username" id="username" value="{{ username }}" type="text" class="form-control" required>
|
||||||
placeholder="Enter a username"
|
|
||||||
class="form-control"
|
|
||||||
required>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Name -->
|
<!-- Name -->
|
||||||
<label class="form-label" for="name">Full Name</label>
|
<label class="form-label" for="name" data-i18n="user_settings_personal.full_name"></label>
|
||||||
<div class="input-icon mb-3">
|
<div class="input-icon mb-3">
|
||||||
<span class="input-icon-addon">
|
<span class="input-icon-addon">
|
||||||
{% include "icons/user" %}
|
{% include "icons/user" %}
|
||||||
|
|
@ -68,7 +66,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Email -->
|
<!-- Email -->
|
||||||
<label class="form-label" for="email">Email address</label>
|
<label class="form-label" for="email" data-i18n="user_settings_personal.email"></label>
|
||||||
<div class="input-icon mb-3">
|
<div class="input-icon mb-3">
|
||||||
<span class="input-icon-addon">
|
<span class="input-icon-addon">
|
||||||
{% include "icons/at" %}
|
{% include "icons/at" %}
|
||||||
|
|
@ -79,12 +77,11 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Timezone -->
|
<!-- Timezone -->
|
||||||
<label class="form-label" for="select-timezone">Timezone</label>
|
<label class="form-label" for="select-timezone" data-i18n="user_settings_personal.timezone"></label>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<select type="text" class="form-select" placeholder="Search your nearest city..."
|
<select type="text" class="form-select" id="select-timezone" name="timezone" autocomplete="off">
|
||||||
id="select-timezone" name="timezone" autocomplete="off">
|
|
||||||
|
|
||||||
<option value="UTC">UTC (Default)</option>
|
<option value="UTC" data-i18n="user_settings_personal.utc_default"></option>
|
||||||
|
|
||||||
{% for tz in list_timezones %}
|
{% for tz in list_timezones %}
|
||||||
{% if timezone == tz %}
|
{% if timezone == tz %}
|
||||||
|
|
@ -98,11 +95,11 @@
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="mt-4 text-muted">Profile last updated on {{ updated_at | date(format="%A %-d %B %Y, %T", timezone=user.zoneinfo | default(value="UTC")) }}</p>
|
<p class="mt-4 text-muted"><span data-i18n="user_settings_personal.last_updated"></span> {{ updated_at | date(format="%Y-%m-%d, %T", timezone=user.zoneinfo | default(value="UTC")) }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-footer text-end">
|
<div class="card-footer text-end">
|
||||||
<button type="submit" class="btn btn-primary">Save</button>
|
<button type="submit" class="btn btn-primary" data-i18n="user_settings_personal.save"></button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|
@ -146,3 +143,9 @@
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock additional_js %}
|
{% endblock additional_js %}
|
||||||
|
|
||||||
|
{% block i18n %}
|
||||||
|
localize("#header_user_nav");
|
||||||
|
localize("#settings_header");
|
||||||
|
localize("#settings_personal_page");
|
||||||
|
{% endblock i18n %}
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,11 @@
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<!-- Page header -->
|
<!-- Page header -->
|
||||||
<div class="page-header d-print-none">
|
<div class="page-header d-print-none" id="settings_header">
|
||||||
<div class="container-xl">
|
<div class="container-xl">
|
||||||
<div class="row g-2 align-items-center">
|
<div class="row g-2 align-items-center">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<h2 class="page-title">
|
<h2 class="page-title" data-i18n="user_settings.settings"></h2>
|
||||||
Settings
|
|
||||||
</h2>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -23,17 +21,20 @@
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="card">
|
<div class="card" id="settings_security_page">
|
||||||
<div class="row g-0">
|
<div class="row g-0">
|
||||||
<div class="col-3 d-none d-md-block border-end">
|
<div class="col-3 d-none d-md-block border-end">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="list-group list-group-transparent">
|
<div class="list-group list-group-transparent">
|
||||||
<a href="./personal"
|
<a href="./personal"
|
||||||
class="list-group-item list-group-item-action d-flex align-items-center">Personal</a>
|
class="list-group-item list-group-item-action d-flex align-items-center"
|
||||||
|
data-i18n="user_settings.personal"></a>
|
||||||
<a href="./security"
|
<a href="./security"
|
||||||
class="list-group-item list-group-item-action d-flex align-items-center active">Security</a>
|
class="list-group-item list-group-item-action d-flex align-items-center active"
|
||||||
|
data-i18n="user_settings.security"></a>
|
||||||
<a href="./visual"
|
<a href="./visual"
|
||||||
class="list-group-item list-group-item-action d-flex align-items-center">Visual</a>
|
class="list-group-item list-group-item-action d-flex align-items-center"
|
||||||
|
data-i18n="user_settings.visual"></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -329,3 +330,9 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
||||||
|
{% block i18n %}
|
||||||
|
localize("#header_user_nav");
|
||||||
|
localize("#settings_header");
|
||||||
|
localize("#settings_security_page");
|
||||||
|
{% endblock i18n %}
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,11 @@
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<!-- Page header -->
|
<!-- Page header -->
|
||||||
<div class="page-header d-print-none">
|
<div class="page-header d-print-none" id="settings_header">
|
||||||
<div class="container-xl">
|
<div class="container-xl">
|
||||||
<div class="row g-2 align-items-center">
|
<div class="row g-2 align-items-center">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<h2 class="page-title">
|
<h2 class="page-title" data-i18n="user_settings.settings"></h2>
|
||||||
Settings
|
|
||||||
</h2>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -23,32 +21,35 @@
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="card">
|
<div class="card" id="settings_visual_page">
|
||||||
<div class="row g-0">
|
<div class="row g-0">
|
||||||
<div class="col-3 d-none d-md-block border-end">
|
<div class="col-3 d-none d-md-block border-end">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="list-group list-group-transparent">
|
<div class="list-group list-group-transparent">
|
||||||
<a href="./personal"
|
<a href="./personal"
|
||||||
class="list-group-item list-group-item-action d-flex align-items-center">Personal</a>
|
class="list-group-item list-group-item-action d-flex align-items-center"
|
||||||
|
data-i18n="user_settings.personal"></a>
|
||||||
<a href="./security"
|
<a href="./security"
|
||||||
class="list-group-item list-group-item-action d-flex align-items-center">Security</a>
|
class="list-group-item list-group-item-action d-flex align-items-center"
|
||||||
|
data-i18n="user_settings.security"></a>
|
||||||
<a href="./visual"
|
<a href="./visual"
|
||||||
class="list-group-item list-group-item-action d-flex align-items-center active">Visual</a>
|
class="list-group-item list-group-item-action d-flex align-items-center active"
|
||||||
|
data-i18n="user_settings.visual"></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col d-flex flex-column">
|
<div class="col d-flex flex-column">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h2 class="mb-4">Visual</h2>
|
<h2 class="mb-4" data-i18n="user_settings.visual"></h2>
|
||||||
|
|
||||||
<a href="?theme=dark" class="btn hide-theme-dark">
|
<a href="?theme=dark" class="btn hide-theme-dark">
|
||||||
{% include "icons/moon" %}
|
{% include "icons/moon" %}
|
||||||
Enable dark mode
|
<span data-i18n="user_settings_visual.enable_dark"></span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href="?theme=light" class="btn hide-theme-light">
|
<a href="?theme=light" class="btn hide-theme-light">
|
||||||
{% include "icons/sun" %}
|
{% include "icons/sun" %}
|
||||||
Enable light mode
|
<span data-i18n="user_settings_visual.enable_light"></span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -59,3 +60,9 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
||||||
|
{% block i18n %}
|
||||||
|
localize("#header_user_nav");
|
||||||
|
localize("#settings_header");
|
||||||
|
localize("#settings_visual_page");
|
||||||
|
{% endblock i18n %}
|
||||||
|
|
|
||||||
|
|
@ -15,35 +15,33 @@
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<form action="/setup" method="post">
|
<form action="/setup" method="post" id="setup_form">
|
||||||
<div class="card card-md">
|
<div class="card card-md">
|
||||||
<div class="card-body text-center py-4 p-sm-5">
|
<div class="card-body text-center py-4 p-sm-5">
|
||||||
<h1 class="">Welcome to Ezidam!</h1>
|
<h1 data-i18n="setup.welcome"></h1>
|
||||||
<p class="text-muted">Initial setup</p>
|
<p class="text-muted" data-i18n="setup.initial_setup"></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- First admin account -->
|
<!-- First admin account -->
|
||||||
<div class="hr-text hr-text-center hr-text-spaceless">first admin account</div>
|
<div class="hr-text hr-text-center hr-text-spaceless" data-i18n="setup.first_admin_account"></div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label required" for="username">Username</label>
|
<label class="form-label required" for="username" data-i18n="setup.username"></label>
|
||||||
<input name="username" id="username" type="text" placeholder="Enter a username"
|
<input name="username" id="username" type="text" class="form-control" required>
|
||||||
class="form-control" required>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label required" for="password">Password</label>
|
<label class="form-label required" for="password" data-i18n="setup.password"></label>
|
||||||
<div class="input-group input-group-flat">
|
<div class="input-group input-group-flat">
|
||||||
<input name="password" id="password" type="password" placeholder="Enter password"
|
<input name="password" id="password" type="password" class="form-control" autocomplete="off" required>
|
||||||
class="form-control" autocomplete="off" required>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Settings -->
|
<!-- Settings -->
|
||||||
<div class="hr-text hr-text-center hr-text-spaceless">settings</div>
|
<div class="hr-text hr-text-center hr-text-spaceless" data-i18n="setup.settings"></div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label required" for="url">Base URL</label>
|
<label class="form-label required" for="url" data-i18n="setup.base_url"></label>
|
||||||
<input name="url" id="url" type="url" placeholder="https://example.com" class="form-control"
|
<input name="url" id="url" type="url" placeholder="https://example.com" class="form-control"
|
||||||
required>
|
required>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -52,7 +50,7 @@
|
||||||
<div class="row align-items-center mt-3">
|
<div class="row align-items-center mt-3">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="btn-list justify-content-end">
|
<div class="btn-list justify-content-end">
|
||||||
<button type="submit" class="btn btn-primary">Finish setup</button>
|
<button type="submit" class="btn btn-primary" data-i18n="setup.finish"></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -66,3 +64,7 @@
|
||||||
<script src="/js/demo.min.js" defer></script>
|
<script src="/js/demo.min.js" defer></script>
|
||||||
</body>
|
</body>
|
||||||
{% endblock page %}
|
{% endblock page %}
|
||||||
|
|
||||||
|
{% block i18n %}
|
||||||
|
localize("#setup_form");
|
||||||
|
{% endblock i18n %}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
{{ user::user_info(name=user.name, username=user.username, email=user.email) }}
|
{{ user::user_info(name=user.name, username=user.username, email=user.email) }}
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<div class="dropdown-menu dropdown-menu-end dropdown-menu-arrow">
|
<div class="dropdown-menu dropdown-menu-end dropdown-menu-arrow" id="header_user_nav">
|
||||||
<div class="d-xl-none">
|
<div class="d-xl-none">
|
||||||
<div class="dropdown-item-text">
|
<div class="dropdown-item-text">
|
||||||
{{ user::user_info(name=user.name, username=user.username, email=user.email) }}
|
{{ user::user_info(name=user.name, username=user.username, email=user.email) }}
|
||||||
|
|
@ -24,13 +24,13 @@
|
||||||
|
|
||||||
<div class="dropdown-divider"></div>
|
<div class="dropdown-divider"></div>
|
||||||
</div>
|
</div>
|
||||||
<a href="/settings" class="dropdown-item">Settings</a>
|
<a href="/settings" class="dropdown-item" data-i18n="header.settings"></a>
|
||||||
{% if user.isAdmin == true %}
|
{% if user.isAdmin == true %}
|
||||||
<a href="/admin" class="dropdown-item">Admin panel</a>
|
<a href="/admin" class="dropdown-item" data-i18n="header.admin_panel"></a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="dropdown-divider"></div>
|
<div class="dropdown-divider"></div>
|
||||||
<form action="/logout" method="post">
|
<form action="/logout" method="post">
|
||||||
<button type="submit" class="dropdown-item">Logout</button>
|
<button type="submit" class="dropdown-item" data-i18n="header.logout"></button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue