Compare commits
1 commit
| Author | SHA1 | Date | |
|---|---|---|---|
| 2e5a1b0c42 |
23 changed files with 177 additions and 323 deletions
|
|
@ -38,7 +38,7 @@ pub struct TokenRequest<'r> {
|
|||
pub grant_type: GrantType,
|
||||
pub code: Option<&'r str>,
|
||||
pub redirect_uri: Option<&'r str>,
|
||||
pub client_id: &'r str,
|
||||
pub client_id: Option<&'r str>,
|
||||
pub client_secret: Option<&'r str>,
|
||||
pub scope: Option<&'r str>,
|
||||
pub refresh_token: Option<&'r str>,
|
||||
|
|
|
|||
|
|
@ -51,12 +51,14 @@ pub enum TokenError {
|
|||
RefreshTokenExpired,
|
||||
AuthorizationCodeUsed,
|
||||
AuthorizationCodeExpired,
|
||||
HttpAuthDifferentClientId,
|
||||
AppError(apps::Error),
|
||||
AppNotFound(String),
|
||||
AppNotFoundFromAuthorizationCode(String),
|
||||
AppNotFoundFromRefreshToken(String),
|
||||
AppIdNotProvided,
|
||||
AppSecretNotProvided,
|
||||
Blocking(task::JoinError),
|
||||
SecretCompare(hash::Error),
|
||||
AppIdWrong,
|
||||
AppSecretWrong,
|
||||
UserError(users::Error),
|
||||
UserNotFound,
|
||||
|
|
@ -111,14 +113,19 @@ impl<'r> Responder<'r, 'static> for TokenError {
|
|||
Status::BadRequest,
|
||||
"Authorization code has expired".to_string(),
|
||||
),
|
||||
TokenError::HttpAuthDifferentClientId => (
|
||||
Status::BadRequest,
|
||||
"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::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,
|
||||
"Could not get client_id: not provided in any way".to_string(),
|
||||
),
|
||||
TokenError::AppSecretNotProvided => {
|
||||
(Status::BadRequest, "Secret was not provided".to_string())
|
||||
}
|
||||
|
|
@ -127,6 +134,7 @@ impl<'r> Responder<'r, 'static> for TokenError {
|
|||
Status::InternalServerError,
|
||||
format!("Failed to check app secret: {e}"),
|
||||
),
|
||||
TokenError::AppIdWrong => (Status::Forbidden, "Invalid client_id provided".to_string()),
|
||||
TokenError::AppSecretWrong => {
|
||||
(Status::Forbidden, "Invalid secret provided".to_string())
|
||||
}
|
||||
|
|
@ -171,8 +179,8 @@ pub async fn request_token(
|
|||
) -> std::result::Result<TokenResponse, TokenError> {
|
||||
let mut transaction = db.begin().await.map_err(TokenError::TransactionStart)?;
|
||||
|
||||
// Get user depending on grant type
|
||||
let user = match token_request.grant_type {
|
||||
// Get user and app depending on grant type
|
||||
let (user, app) = match token_request.grant_type {
|
||||
GrantType::AuthorizationCode => {
|
||||
let authorization_code = token_request
|
||||
.code
|
||||
|
|
@ -220,12 +228,20 @@ pub async fn request_token(
|
|||
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
|
||||
code.use_code(&mut transaction)
|
||||
.await
|
||||
.map_err(TokenError::AuthorizationError)?;
|
||||
|
||||
user
|
||||
(user, app)
|
||||
}
|
||||
GrantType::RefreshToken => {
|
||||
let refresh_token = token_request
|
||||
|
|
@ -264,27 +280,38 @@ pub async fn request_token(
|
|||
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
|
||||
.use_token(&mut transaction)
|
||||
.await
|
||||
.map_err(TokenError::RefreshTokenError)?;
|
||||
|
||||
user
|
||||
(user, app)
|
||||
}
|
||||
};
|
||||
|
||||
// If HTTP Basic Auth is provided, verify provided client id in form
|
||||
if let Some(app_auth) = &app_auth {
|
||||
if app_auth.id != token_request.client_id {
|
||||
return Err(TokenError::HttpAuthDifferentClientId);
|
||||
}
|
||||
// Get client id
|
||||
// https://www.rfc-editor.org/rfc/rfc6749#section-4.1.3
|
||||
let provided_client_id = match (&app_auth, token_request.client_id) {
|
||||
(Some(http_auth), _) => http_auth.id.to_string(),
|
||||
(None, Some(form)) => form.into(),
|
||||
(None, None) => {
|
||||
return Err(TokenError::AppIdNotProvided);
|
||||
}
|
||||
};
|
||||
|
||||
// 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()))?;
|
||||
// Verify client id
|
||||
if app.id().as_ref() != provided_client_id {
|
||||
return Err(TokenError::AppIdWrong);
|
||||
}
|
||||
|
||||
if app.is_confidential() {
|
||||
let provided_secret = match (app_auth, token_request.client_secret) {
|
||||
|
|
|
|||
|
|
@ -41,5 +41,7 @@ pub mod content {
|
|||
#[derive(Serialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
#[derive(Clone)]
|
||||
pub struct ResetPassword {}
|
||||
pub struct ResetPassword {
|
||||
pub username: String,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,11 +12,13 @@ pub async fn reset_password_page(
|
|||
return Err(Error::bad_request("Reset password token has expired"));
|
||||
}
|
||||
|
||||
User::get_one_from_password_reset_token(&mut **db, &token.0)
|
||||
let user = User::get_one_from_password_reset_token(&mut **db, &token.0)
|
||||
.await?
|
||||
.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
|
||||
.map(|flash| Page::with_flash(page.clone(), flash))
|
||||
|
|
|
|||
|
|
@ -1,52 +0,0 @@
|
|||
{
|
||||
"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"
|
||||
}
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
{
|
||||
"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
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1,2 +0,0 @@
|
|||
// 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>
|
||||
<html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"/>
|
||||
|
|
@ -9,11 +9,6 @@
|
|||
<link href="/css/tabler.min.css" rel="stylesheet"/>
|
||||
<link href="/css/tabler-vendors.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 -->
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
|
||||
|
|
@ -32,34 +27,5 @@
|
|||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
{% 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>
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
<body class=" border-top-wide border-primary d-flex flex-column">
|
||||
<script src="/js/demo-theme.min.js"></script>
|
||||
<div class="page page-center">
|
||||
<div class="container-tight py-4" id="error_page">
|
||||
<div class="container-tight py-4">
|
||||
<div class="empty">
|
||||
<div class="empty-header">{{ http_code }}</div>
|
||||
<p class="empty-title">{{ http_reason }}</p>
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
<div class="empty-action">
|
||||
<a href="/" class="btn btn-primary">
|
||||
{% include "icons/arrow-left" %}
|
||||
<label data-i18n="error.go_home"></label>
|
||||
Take me home
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -26,7 +26,3 @@
|
|||
<script src="/js/demo.min.js" defer></script>
|
||||
</body>
|
||||
{% endblock page %}
|
||||
|
||||
{% block i18n %}
|
||||
localize("#error_page");
|
||||
{% endblock i18n %}
|
||||
|
|
|
|||
|
|
@ -16,55 +16,66 @@
|
|||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="card" id="forgot_password_card">
|
||||
<h2 class="card-title text-center my-4 h2" data-i18n="forgot_password.title"></h2>
|
||||
<div class="card">
|
||||
<h2 class="card-title text-center my-4 h2">Reset your password</h2>
|
||||
<div class="card-header">
|
||||
<ul class="nav nav-tabs card-header-tabs nav-fill" data-bs-toggle="tabs">
|
||||
<li class="nav-item">
|
||||
<a href="#tabs-email" class="nav-link active" data-bs-toggle="tab" data-i18n="forgot_password.email"></a>
|
||||
<a href="#tabs-email" class="nav-link active" data-bs-toggle="tab">Email</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="#tabs-paper-key" class="nav-link" data-bs-toggle="tab" data-i18n="forgot_password.paper_key"></a>
|
||||
<a href="#tabs-paper-key" class="nav-link" data-bs-toggle="tab">Paper key</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane active show" id="tabs-email">
|
||||
<p class="mt-2 mb-4" data-i18n="forgot_password.email_description"></p>
|
||||
<p class="mt-2 mb-4">
|
||||
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"
|
||||
novalidate>
|
||||
<div class="mb-3">
|
||||
<label class="form-label required" for="email" data-i18n="forgot_password.email_address"></label>
|
||||
<input id="email" type="email" name="email" class="form-control" required>
|
||||
<label class="form-label required" for="email">Email address</label>
|
||||
<input id="email" type="email" name="email" class="form-control"
|
||||
placeholder="Enter email"
|
||||
required>
|
||||
</div>
|
||||
<div class="form-footer">
|
||||
<button type="submit" class="btn btn-primary w-100">
|
||||
{% include "icons/mail" %}
|
||||
<label data-i18n="forgot_password.request"></label>
|
||||
Request password reset
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane" id="tabs-paper-key">
|
||||
<p class="mt-2 mb-4" data-i18n="forgot_password.paper_key_description"></p>
|
||||
<p class="mt-2 mb-4">
|
||||
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"
|
||||
novalidate>
|
||||
<div class="mb-3">
|
||||
<label class="form-label required" for="login" data-i18n="forgot_password.login"></label>
|
||||
<input id="login" type="text" name="login" class="form-control" required>
|
||||
<label class="form-label required" for="login">Login</label>
|
||||
<input id="login" type="text" name="login" class="form-control"
|
||||
placeholder="Email or username"
|
||||
required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<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" required>
|
||||
<label class="form-label required" for="paper_key">Paper key</label>
|
||||
<input id="paper_key" type="text" name="paper_key" class="form-control"
|
||||
placeholder="Enter your paper key"
|
||||
required>
|
||||
</div>
|
||||
<div class="form-footer">
|
||||
<button type="submit" class="btn btn-primary w-100">
|
||||
{% include "icons/paperclip" %}
|
||||
<label data-i18n="forgot_password.request"></label>
|
||||
Request password reset
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
|
@ -83,7 +94,3 @@
|
|||
<script src="/js/demo.min.js" defer></script>
|
||||
</body>
|
||||
{% endblock page %}
|
||||
|
||||
{% block i18n %}
|
||||
localize("#forgot_password_card");
|
||||
{% endblock i18n %}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,3 @@
|
|||
|
||||
{% block content %}
|
||||
{% endblock content %}
|
||||
|
||||
{% block i18n %}
|
||||
localize("#header_user_nav");
|
||||
{% endblock i18n %}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
<script src="/js/demo-theme.min.js"></script>
|
||||
<div>
|
||||
<div class="min-vh-100 d-flex flex-column justify-content-between">
|
||||
<div class="container container-tight py-4" id="authorize_page">
|
||||
<div class="container container-tight py-4">
|
||||
<div class="text-center mb-4">
|
||||
{% include "utils/logo" %}
|
||||
</div>
|
||||
|
|
@ -22,8 +22,8 @@
|
|||
<div class="card card-md">
|
||||
<div class="card-body">
|
||||
<div class="text-center mb-2">
|
||||
<h2 class="h2" data-i18n="authorize.access_app"></h2>
|
||||
<p class="text-muted" data-i18n="authorize.with_account"></p>
|
||||
<h2 class="h2">Access {{ app_name }}</h2>
|
||||
<p class="text-muted">With your {{ business_name }} account</p>
|
||||
</div>
|
||||
<form id="authorize_form" action="" method="post" autocomplete="off" novalidate class="mt-4">
|
||||
{% if user %}
|
||||
|
|
@ -39,19 +39,23 @@
|
|||
</div>
|
||||
{% else %}
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="login" data-i18n="authorize.login"></label>
|
||||
<input id="login" name="login" type="text" class="form-control" autocomplete="off">
|
||||
<label class="form-label" for="login">Login</label>
|
||||
<input id="login" name="login" type="text" class="form-control"
|
||||
placeholder="Email or username"
|
||||
autocomplete="off">
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="password" data-i18n="authorize.password"></label>
|
||||
<label class="form-label" for="password">Password</label>
|
||||
<div class="input-group input-group-flat">
|
||||
<input id="password" name="password" type="password" class="form-control" autocomplete="off">
|
||||
<input id="password" name="password" type="password" class="form-control"
|
||||
placeholder="Your password"
|
||||
autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="form-footer">
|
||||
<button type="submit" class="btn btn-primary w-100" data-i18n="authorize.authorize"></button>
|
||||
<button type="submit" class="btn btn-primary w-100">Authorize</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
|
@ -59,7 +63,7 @@
|
|||
{% if user %}
|
||||
{% else %}
|
||||
<div class="text-center text-muted mt-3">
|
||||
<a href="/forgot-password" tabindex="-1" data-i18n="authorize.forgot_password"></a>
|
||||
<a href="/forgot-password" tabindex="-1">Forgot your password?</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
|
@ -75,10 +79,3 @@
|
|||
|
||||
</body>
|
||||
{% endblock page %}
|
||||
|
||||
{% block i18n %}
|
||||
localize("#authorize_page", {
|
||||
'app_name': '{{app_name}}',
|
||||
'business_name': '{{business_name}}',
|
||||
});
|
||||
{% endblock i18n %}
|
||||
|
|
|
|||
|
|
@ -8,15 +8,15 @@
|
|||
<div>
|
||||
<div class="min-vh-100 d-flex flex-column justify-content-between">
|
||||
<div class="page page-center">
|
||||
<div class="container container-tight py-4" id="redirect_page">
|
||||
<div class="container container-tight py-4">
|
||||
<div class="text-center mb-4">
|
||||
{% include "utils/logo" %}
|
||||
</div>
|
||||
<div class="card card-md">
|
||||
<div class="card-body text-center">
|
||||
<div class="mb-4">
|
||||
<h2 class="card-title" data-i18n="redirect.hello"></h2>
|
||||
<p class="text-muted" data-i18n="redirect.preparing_app"></p>
|
||||
<h2 class="card-title">Hello!</h2>
|
||||
<p class="text-muted">Preparing application</p>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
{{ user::avatar(username=username, name=name, size="xl", css="mb-3") }}
|
||||
|
|
@ -34,7 +34,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="text-center text-muted mt-3">
|
||||
<a href="{{ home_page }}" tabindex="-1" data-i18n="redirect.not_redirected"></a>
|
||||
<a href="{{ home_page }}" tabindex="-1">Click here if you are not redirected</a>
|
||||
<meta http-equiv="refresh" content="2; url={{ home_page }}">
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -48,7 +48,3 @@
|
|||
<script src="/js/demo.min.js" defer></script>
|
||||
</body>
|
||||
{% endblock page %}
|
||||
|
||||
{% block i18n %}
|
||||
localize("#redirect_page");
|
||||
{% endblock i18n %}
|
||||
|
|
|
|||
|
|
@ -19,11 +19,11 @@
|
|||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="card card-md" id="totp_page">
|
||||
<div class="card card-md">
|
||||
<div class="card-body">
|
||||
<div class="text-center mb-4">
|
||||
<div class="mb-4">
|
||||
<h2 class="card-title" data-i18n="totp.verify_account"></h2>
|
||||
<h2 class="card-title">Verify your account</h2>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
{{ user::avatar(username=username, name=name, size="lg", css="mb-3") }}
|
||||
|
|
@ -38,7 +38,9 @@
|
|||
</div>
|
||||
<form id="totp_form" action="" method="post" autocomplete="off" novalidate class="mt-4">
|
||||
<div class="mb-3">
|
||||
<label class="form-label required" for="code" data-i18n="totp.enter_code"></label>
|
||||
<label class="form-label required" for="code">
|
||||
Enter the code displayed on your device
|
||||
</label>
|
||||
<input
|
||||
class="form-control"
|
||||
type="text"
|
||||
|
|
@ -52,7 +54,7 @@
|
|||
</div>
|
||||
|
||||
<div class="form-footer">
|
||||
<button type="submit" class="btn btn-primary w-100" data-i18n="totp.verify_code"></button>
|
||||
<button type="submit" class="btn btn-primary w-100">Verify code</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
|
@ -70,7 +72,3 @@
|
|||
|
||||
</body>
|
||||
{% endblock page %}
|
||||
|
||||
{% block i18n %}
|
||||
localize("#totp_page");
|
||||
{% endblock i18n %}
|
||||
|
|
|
|||
|
|
@ -16,25 +16,27 @@
|
|||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="card" id="reset_password_card">
|
||||
<h2 class="card-title text-center my-4 h2" data-i18n="reset_password.title"></h2>
|
||||
<div class="card">
|
||||
<h2 class="card-title text-center my-4 h2">Reset your password</h2>
|
||||
<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>
|
||||
<div class="mb-3">
|
||||
<label class="form-label required" for="password" data-i18n="reset_password.new_password"></label>
|
||||
<input name="password" id="password" type="password" class="form-control" required>
|
||||
<label class="form-label required" for="password">New password</label>
|
||||
<input name="password" id="password" type="password" class="form-control" placeholder="Enter new password" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<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" required>
|
||||
<label class="form-label required" for="confirm_password">Confirm new password</label>
|
||||
<input name="confirm_password" id="confirm_password" type="password" class="form-control" placeholder="Confirm new password" required>
|
||||
</div>
|
||||
|
||||
<div class="form-footer">
|
||||
<button type="submit" class="btn btn-primary w-100">
|
||||
{% include "icons/password" %}
|
||||
<label data-i18n="reset_password.set_password"></label>
|
||||
Set new password
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
|
@ -52,7 +54,3 @@
|
|||
<script src="/js/demo.min.js" defer></script>
|
||||
</body>
|
||||
{% endblock page %}
|
||||
|
||||
{% block i18n %}
|
||||
localize("#reset_password_card");
|
||||
{% endblock i18n %}
|
||||
|
|
|
|||
|
|
@ -2,11 +2,13 @@
|
|||
|
||||
{% block content %}
|
||||
<!-- Page header -->
|
||||
<div class="page-header d-print-none" id="settings_header">
|
||||
<div class="page-header d-print-none">
|
||||
<div class="container-xl">
|
||||
<div class="row g-2 align-items-center">
|
||||
<div class="col">
|
||||
<h2 class="page-title" data-i18n="user_settings.settings"></h2>
|
||||
<h2 class="page-title">
|
||||
Settings
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -21,41 +23,41 @@
|
|||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="card" id="settings_personal_page">
|
||||
<div class="card">
|
||||
<div class="row g-0">
|
||||
<div class="col-3 d-none d-md-block border-end">
|
||||
<div class="card-body">
|
||||
<div class="list-group list-group-transparent">
|
||||
<a href="./personal"
|
||||
class="list-group-item list-group-item-action d-flex align-items-center active"
|
||||
data-i18n="user_settings.personal"></a>
|
||||
class="list-group-item list-group-item-action d-flex align-items-center active">Personal</a>
|
||||
<a href="./security"
|
||||
class="list-group-item list-group-item-action d-flex align-items-center"
|
||||
data-i18n="user_settings.security"></a>
|
||||
class="list-group-item list-group-item-action d-flex align-items-center">Security</a>
|
||||
<a href="./visual"
|
||||
class="list-group-item list-group-item-action d-flex align-items-center"
|
||||
data-i18n="user_settings.visual"></a>
|
||||
class="list-group-item list-group-item-action d-flex align-items-center">Visual</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col d-flex flex-column">
|
||||
<form action="" method="post">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title" data-i18n="user_settings_personal.my_profile"></h3>
|
||||
<h3 class="card-title">My Profile</h3>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<!-- Username -->
|
||||
<label class="form-label required" for="username" data-i18n="user_settings_personal.username"></label>
|
||||
<label class="form-label required" for="username">Username</label>
|
||||
<div class="input-icon mb-3">
|
||||
<span class="input-icon-addon">
|
||||
{% include "icons/id-badge-2" %}
|
||||
</span>
|
||||
<input name="username" id="username" value="{{ username }}" type="text" class="form-control" required>
|
||||
<input name="username" id="username" value="{{ username }}" type="text"
|
||||
placeholder="Enter a username"
|
||||
class="form-control"
|
||||
required>
|
||||
</div>
|
||||
|
||||
<!-- Name -->
|
||||
<label class="form-label" for="name" data-i18n="user_settings_personal.full_name"></label>
|
||||
<label class="form-label" for="name">Full Name</label>
|
||||
<div class="input-icon mb-3">
|
||||
<span class="input-icon-addon">
|
||||
{% include "icons/user" %}
|
||||
|
|
@ -66,7 +68,7 @@
|
|||
</div>
|
||||
|
||||
<!-- Email -->
|
||||
<label class="form-label" for="email" data-i18n="user_settings_personal.email"></label>
|
||||
<label class="form-label" for="email">Email address</label>
|
||||
<div class="input-icon mb-3">
|
||||
<span class="input-icon-addon">
|
||||
{% include "icons/at" %}
|
||||
|
|
@ -77,11 +79,12 @@
|
|||
</div>
|
||||
|
||||
<!-- Timezone -->
|
||||
<label class="form-label" for="select-timezone" data-i18n="user_settings_personal.timezone"></label>
|
||||
<label class="form-label" for="select-timezone">Timezone</label>
|
||||
<div class="mb-3">
|
||||
<select type="text" class="form-select" id="select-timezone" name="timezone" autocomplete="off">
|
||||
<select type="text" class="form-select" placeholder="Search your nearest city..."
|
||||
id="select-timezone" name="timezone" autocomplete="off">
|
||||
|
||||
<option value="UTC" data-i18n="user_settings_personal.utc_default"></option>
|
||||
<option value="UTC">UTC (Default)</option>
|
||||
|
||||
{% for tz in list_timezones %}
|
||||
{% if timezone == tz %}
|
||||
|
|
@ -95,11 +98,11 @@
|
|||
</select>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div class="card-footer text-end">
|
||||
<button type="submit" class="btn btn-primary" data-i18n="user_settings_personal.save"></button>
|
||||
<button type="submit" class="btn btn-primary">Save</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
|
@ -143,9 +146,3 @@
|
|||
});
|
||||
</script>
|
||||
{% endblock additional_js %}
|
||||
|
||||
{% block i18n %}
|
||||
localize("#header_user_nav");
|
||||
localize("#settings_header");
|
||||
localize("#settings_personal_page");
|
||||
{% endblock i18n %}
|
||||
|
|
|
|||
|
|
@ -2,11 +2,13 @@
|
|||
|
||||
{% block content %}
|
||||
<!-- Page header -->
|
||||
<div class="page-header d-print-none" id="settings_header">
|
||||
<div class="page-header d-print-none">
|
||||
<div class="container-xl">
|
||||
<div class="row g-2 align-items-center">
|
||||
<div class="col">
|
||||
<h2 class="page-title" data-i18n="user_settings.settings"></h2>
|
||||
<h2 class="page-title">
|
||||
Settings
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -21,20 +23,17 @@
|
|||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="card" id="settings_security_page">
|
||||
<div class="card">
|
||||
<div class="row g-0">
|
||||
<div class="col-3 d-none d-md-block border-end">
|
||||
<div class="card-body">
|
||||
<div class="list-group list-group-transparent">
|
||||
<a href="./personal"
|
||||
class="list-group-item list-group-item-action d-flex align-items-center"
|
||||
data-i18n="user_settings.personal"></a>
|
||||
class="list-group-item list-group-item-action d-flex align-items-center">Personal</a>
|
||||
<a href="./security"
|
||||
class="list-group-item list-group-item-action d-flex align-items-center active"
|
||||
data-i18n="user_settings.security"></a>
|
||||
class="list-group-item list-group-item-action d-flex align-items-center active">Security</a>
|
||||
<a href="./visual"
|
||||
class="list-group-item list-group-item-action d-flex align-items-center"
|
||||
data-i18n="user_settings.visual"></a>
|
||||
class="list-group-item list-group-item-action d-flex align-items-center">Visual</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -330,9 +329,3 @@
|
|||
</div>
|
||||
|
||||
{% endblock content %}
|
||||
|
||||
{% block i18n %}
|
||||
localize("#header_user_nav");
|
||||
localize("#settings_header");
|
||||
localize("#settings_security_page");
|
||||
{% endblock i18n %}
|
||||
|
|
|
|||
|
|
@ -2,11 +2,13 @@
|
|||
|
||||
{% block content %}
|
||||
<!-- Page header -->
|
||||
<div class="page-header d-print-none" id="settings_header">
|
||||
<div class="page-header d-print-none">
|
||||
<div class="container-xl">
|
||||
<div class="row g-2 align-items-center">
|
||||
<div class="col">
|
||||
<h2 class="page-title" data-i18n="user_settings.settings"></h2>
|
||||
<h2 class="page-title">
|
||||
Settings
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -21,35 +23,32 @@
|
|||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="card" id="settings_visual_page">
|
||||
<div class="card">
|
||||
<div class="row g-0">
|
||||
<div class="col-3 d-none d-md-block border-end">
|
||||
<div class="card-body">
|
||||
<div class="list-group list-group-transparent">
|
||||
<a href="./personal"
|
||||
class="list-group-item list-group-item-action d-flex align-items-center"
|
||||
data-i18n="user_settings.personal"></a>
|
||||
class="list-group-item list-group-item-action d-flex align-items-center">Personal</a>
|
||||
<a href="./security"
|
||||
class="list-group-item list-group-item-action d-flex align-items-center"
|
||||
data-i18n="user_settings.security"></a>
|
||||
class="list-group-item list-group-item-action d-flex align-items-center">Security</a>
|
||||
<a href="./visual"
|
||||
class="list-group-item list-group-item-action d-flex align-items-center active"
|
||||
data-i18n="user_settings.visual"></a>
|
||||
class="list-group-item list-group-item-action d-flex align-items-center active">Visual</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col d-flex flex-column">
|
||||
<div class="card-body">
|
||||
<h2 class="mb-4" data-i18n="user_settings.visual"></h2>
|
||||
<h2 class="mb-4">Visual</h2>
|
||||
|
||||
<a href="?theme=dark" class="btn hide-theme-dark">
|
||||
{% include "icons/moon" %}
|
||||
<span data-i18n="user_settings_visual.enable_dark"></span>
|
||||
Enable dark mode
|
||||
</a>
|
||||
|
||||
<a href="?theme=light" class="btn hide-theme-light">
|
||||
{% include "icons/sun" %}
|
||||
<span data-i18n="user_settings_visual.enable_light"></span>
|
||||
Enable light mode
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
|
@ -60,9 +59,3 @@
|
|||
</div>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
|
||||
{% block i18n %}
|
||||
localize("#header_user_nav");
|
||||
localize("#settings_header");
|
||||
localize("#settings_visual_page");
|
||||
{% endblock i18n %}
|
||||
|
|
|
|||
|
|
@ -15,33 +15,35 @@
|
|||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form action="/setup" method="post" id="setup_form">
|
||||
<form action="/setup" method="post">
|
||||
<div class="card card-md">
|
||||
<div class="card-body text-center py-4 p-sm-5">
|
||||
<h1 data-i18n="setup.welcome"></h1>
|
||||
<p class="text-muted" data-i18n="setup.initial_setup"></p>
|
||||
<h1 class="">Welcome to Ezidam!</h1>
|
||||
<p class="text-muted">Initial setup</p>
|
||||
</div>
|
||||
|
||||
<!-- First admin account -->
|
||||
<div class="hr-text hr-text-center hr-text-spaceless" data-i18n="setup.first_admin_account"></div>
|
||||
<div class="hr-text hr-text-center hr-text-spaceless">first admin account</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label required" for="username" data-i18n="setup.username"></label>
|
||||
<input name="username" id="username" type="text" class="form-control" required>
|
||||
<label class="form-label required" for="username">Username</label>
|
||||
<input name="username" id="username" type="text" placeholder="Enter a username"
|
||||
class="form-control" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label required" for="password" data-i18n="setup.password"></label>
|
||||
<label class="form-label required" for="password">Password</label>
|
||||
<div class="input-group input-group-flat">
|
||||
<input name="password" id="password" type="password" class="form-control" autocomplete="off" required>
|
||||
<input name="password" id="password" type="password" placeholder="Enter password"
|
||||
class="form-control" autocomplete="off" required>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Settings -->
|
||||
<div class="hr-text hr-text-center hr-text-spaceless" data-i18n="setup.settings"></div>
|
||||
<div class="hr-text hr-text-center hr-text-spaceless">settings</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label required" for="url" data-i18n="setup.base_url"></label>
|
||||
<label class="form-label required" for="url">Base URL</label>
|
||||
<input name="url" id="url" type="url" placeholder="https://example.com" class="form-control"
|
||||
required>
|
||||
</div>
|
||||
|
|
@ -50,7 +52,7 @@
|
|||
<div class="row align-items-center mt-3">
|
||||
<div class="col">
|
||||
<div class="btn-list justify-content-end">
|
||||
<button type="submit" class="btn btn-primary" data-i18n="setup.finish"></button>
|
||||
<button type="submit" class="btn btn-primary">Finish setup</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -64,7 +66,3 @@
|
|||
<script src="/js/demo.min.js" defer></script>
|
||||
</body>
|
||||
{% 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) }}
|
||||
</div>
|
||||
</a>
|
||||
<div class="dropdown-menu dropdown-menu-end dropdown-menu-arrow" id="header_user_nav">
|
||||
<div class="dropdown-menu dropdown-menu-end dropdown-menu-arrow">
|
||||
<div class="d-xl-none">
|
||||
<div class="dropdown-item-text">
|
||||
{{ user::user_info(name=user.name, username=user.username, email=user.email) }}
|
||||
|
|
@ -24,13 +24,13 @@
|
|||
|
||||
<div class="dropdown-divider"></div>
|
||||
</div>
|
||||
<a href="/settings" class="dropdown-item" data-i18n="header.settings"></a>
|
||||
<a href="/settings" class="dropdown-item">Settings</a>
|
||||
{% if user.isAdmin == true %}
|
||||
<a href="/admin" class="dropdown-item" data-i18n="header.admin_panel"></a>
|
||||
<a href="/admin" class="dropdown-item">Admin panel</a>
|
||||
{% endif %}
|
||||
<div class="dropdown-divider"></div>
|
||||
<form action="/logout" method="post">
|
||||
<button type="submit" class="dropdown-item" data-i18n="header.logout"></button>
|
||||
<button type="submit" class="dropdown-item">Logout</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue