snapshot of https://github.com/cortezaproject/corteza.git (2024.9.x, 2026-09-18; blobs > 131072B omitted)
Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s

This commit is contained in:
upstream-mirror
2026-09-18 01:22:23 +05:00
commit 7e385fc126
8391 changed files with 1766236 additions and 0 deletions

View File

@@ -0,0 +1,39 @@
@font-face {
font-family: 'Poppins-Bold';
src: url('./fonts/poppins/Poppins-Bold.ttf') format('truetype');
font-weight: 100 900;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Poppins-Medium';
src: url('./fonts/poppins/Poppins-Medium.ttf') format('truetype');
font-weight: 100 900;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Poppins-Regular';
src: url('./fonts/poppins/Poppins-Regular.ttf') format('truetype');
font-weight: 100 900;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Poppins-SemiBold';
src: url('./fonts/poppins/Poppins-SemiBold.ttf') format('truetype');
font-weight: 100 900;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Poppins-Italic';
src: url('./fonts/poppins/Poppins-Italic.ttf') format('truetype');
font-weight: 100 900;
font-style: italic;
font-display: swap;
}

View File

@@ -0,0 +1,18 @@
$(function () {
$('.toast').toast('show');
$('form').not('do-not-disable-buttons-on-submit').on('submit', function() {
let form = this
setTimeout(function() {
$('button, input[type=submit]', form)
.not('do-not-disable-on-submit')
.attr('disabled', true)
}, 50)
})
$('input.mfa-code-mask').mask('000 000')
$('#avatar').change(function () {
$('form').submit();
})
})

View File

@@ -0,0 +1,52 @@
html {
height: 100%;
}
body {
min-height: 100%;
display: grid;
grid-template-rows: auto;
overflow: auto;
}
label {
color: var(--primary);
}
.logo {
max-height: 45px;
max-width: 25%;
}
.version {
bottom: 0;
right: 0;
}
.header {
background-color: var(--light) !important;
}
/* Navigation */
.nav-item.active {
border-bottom: 4px solid var(--primary);
}
.login-title {
font-size: 25px;
}
.auth {
width: 100%;
max-width: 650px;
margin: auto;
padding: 0 20px;
}
.footer {
margin-top: auto;
}
.bi::before {
vertical-align: middle;
}

View File

@@ -0,0 +1,44 @@
{{ template "inc_header.html.tpl" set . "activeNav" "clients" }}
<div class="card-body p-0">
<form
method="POST"
action="{{ links.AuthorizedClients }}"
class="clearfix p-3"
>
{{ .csrfField }}
{{ range .authorizedClients }}
<div class="mb-2 pb-2 border-bottom">
<div class="text-primary font-weight-bold">{{ .Name }}</div>
<div>
{{ tr "authorized-clients.template.list.authorized-on" }}
<time
datetime="{{ .ConfirmedAt | date "2006-01-02T15:04:05Z07:00" }}"
>
{{ .ConfirmedAt | date "Mon, 02 Jan 2006 15:04 MST" }}
</time>
</div>
<button
data-test-id="button-revoke-access"
type="submit"
name="revoke"
value="{{ .ID }}"
class="btn btn-sm btn-danger my-2"
>
{{ tr "authorized-clients.template.list.buttons.revoke" }}
</button>
</div>
{{ end }}
</form>
{{ if not .authorizedClients}}
<div
data-test-id="text-empty-list"
class="text-center m-3 mb-3"
>
<i>{{ tr "authorized-clients.template.list.empty" }}</i>
</div>
{{ end }}
</div>
{{ template "inc_footer.html.tpl" . }}

View File

@@ -0,0 +1,75 @@
{{ template "inc_header.html.tpl" set . "activeNav" "security" }}
<div class="card-body p-0">
<h4 class="mb-0 p-3 border-bottom">{{ tr "change-password.template.title" }}</h4>
<form
method="POST"
action="{{ links.ChangePassword }}"
class="p-3"
>
{{ .csrfField }}
{{ if .form.error }}
<div
data-test-id="error"
class="text-danger font-weight-bold mb-3"
role="alert"
>
{{ .form.error }}
</div>
{{ end }}
<div class="mb-3">
<label>
{{ tr "change-password.template.form.email.label" }}
</label>
<input
data-test-id="input-email"
type="email"
class="form-control"
name="email"
readonly
placeholder="{{ tr "change-password.template.form.email.placeholder" }}"
value="{{ .user.Email }}"
aria-label="{{ tr "change-password.template.form.email.label" }}"
>
</div>
<div class="mb-3">
<label>
{{ tr "change-password.template.form.old-password.label" }}
</label>
<input
data-test-id="input-old-password"
type="password"
required
class="form-control"
name="oldPassword"
autocomplete="current-password"
placeholder="{{ tr "change-password.template.form.old-password.placeholder" }}"
aria-label="{{ tr "change-password.template.form.old-password.label" }}"
>
</div>
<div class="mb-3">
<label>
{{ tr "change-password.template.form.new-password.label" }}
</label>
<input
data-test-id="input-new-password"
type="password"
required
class="form-control"
name="newPassword"
autocomplete="new-password"
placeholder="{{ tr "change-password.template.form.new-password.placeholder" }}"
aria-label="{{ tr "change-password.template.form.new-password.label" }}"
>
</div>
<div class="text-right">
<button
data-test-id="button-change-password"
class="btn btn-primary btn-block btn-lg"
type="submit"
>
{{ tr "change-password.template.form.button.change-password" }}
</button>
</div>
</form>
</div>
{{ template "inc_footer.html.tpl" . }}

View File

@@ -0,0 +1,59 @@
{{ template "inc_header.html.tpl" set . "activeNav" "security" }}
<div class="card-body p-0">
<h4 class="mb-0 p-3 border-bottom">{{ tr "create-password.template.title" }}</h4>
<form
method="POST"
action="{{ links.CreatePassword }}"
class="p-3"
>
{{ .csrfField }}
{{ if .form.error }}
<div
data-test-id="error"
class="text-danger font-weight-bold mb-3"
role="alert"
>
{{ .form.error }}
</div>
{{ end }}
<div class="mb-3">
<label>
{{ tr "create-password.template.form.email.label" }}
</label>
<input
data-test-id="input-email"
type="email"
class="form-control"
name="email"
readonly
value="{{ .user.Email }}"
aria-label="{{ tr "create-password.template.form.email.label" }}"
>
</div>
<div class="mb-3">
<label>
{{ tr "create-password.template.form.password.label" }}
</label>
<input
data-test-id="input-password"
type="password"
required
class="form-control"
name="password"
autocomplete="password"
placeholder="{{ tr "create-password.template.form.password.placeholder" }}"
aria-label="{{ tr "create-password.template.form.password.label" }}"
>
</div>
<div class="text-right">
<button
data-test-id="button-create-password"
class="btn btn-primary btn-block btn-lg"
type="submit"
>
{{ tr "create-password.template.form.button.create-password" }}
</button>
</div>
</form>
</div>
{{ template "inc_footer.html.tpl" . }}

View File

@@ -0,0 +1,8 @@
{{ template "inc_header.html.tpl" . }}
<div class="card-body p-0">
<h4 class="mb-0 p-3 border-bottom">{{ tr "error-internal.template.title" }}</h4>
<div class="text-danger mb-4 font-weight-bold p-3" role="alert">
{{ .error }}
</div>
</div>
{{ template "inc_footer.html.tpl" . }}

View File

@@ -0,0 +1,12 @@
</main>
{{ template "inc_toasts.html.tpl" .alerts }}
<footer class="d-flex align-items-end justify-content-center text-white py-4">
{{ tr "inc_footer.code-link" }}
<a data-test-id="link-github" href="https://github.com/cortezaproject/" target="_blank" class="text-white ml-1">GitHub</a>
</footer>
</body>
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-Piv4xVNRyMGpqkS2by6br4gNJ7DXjqk09RmUpJ8jgGtD7zP9yug3goQfGII0yAns" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.mask/1.14.16/jquery.mask.js" integrity="sha512-0XDfGxFliYJPFrideYOoxdgNIvrwGTLnmK20xZbCAvPfLGQMzHUsaqZK8ZoH+luXGRxTrS46+Aq400nCnAT0/w==" crossorigin="anonymous"></script>
<script src="{{ links.AuthAssets }}/script.js?{{ buildtime }}"></script>
</html>

View File

@@ -0,0 +1,56 @@
<!doctype html>
<html data-color-mode="{{ .theme }}" lang="{{ language }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Bootstrap icons -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.3.0/font/bootstrap-icons.css">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="icon" id="favicon" href="{{ links.Assets }}/icon.svg">
<!-- Fonts -->
<link href="{{ links.AuthAssets }}/fonts.css" rel="stylesheet">
<!-- Custom CSS -->
<link href="/custom.css" rel="stylesheet">
<link href="{{ links.AuthAssets }}/style.css?{{ buildtime }}" rel="stylesheet">
<title>Corteza</title>
<style>
body {
font-size: 1rem !important;
{{ .authBg }}
background-size: cover;
background-attachment: fixed;
}
</style>
</head>
<body>
<header>
{{ if .user }}
<div class="d-flex justify-content-end align-items-center text-white m-2">
<a class="text-white font-weight-bold" href="{{ links.Base }}">
{{ tr "inc_header.menu" }}
</a>
<span class="mx-2">
|
</span>
{{ tr "inc_header.logged-in-as" }}
<a data-test-id="link-redirect-to-profile" class="font-weight-bold text-white mx-2"
href="{{ links.Profile }}">{{ coalesce .user.Name .user.Handle .user.Email }}
</a>
|
<a data-test-id="link-logout" class="font-weight-bold text-white ml-2" href="{{ links.Logout }}">
{{ tr "inc_header.logout" }}
</a>
</div>
{{ end }}
</header>
<main class="auth mt-sm-5">
<div class="tabs card">
{{ template "inc_nav.html.tpl" . }}

View File

@@ -0,0 +1,56 @@
<div class="header card-header border-0 p-0">
<div class="text-center w-100 my-2 my-sm-4">
<a href="{{ links.Profile }}">
<img
data-test-id="img-corteza-logo"
class="logo"
alt="Company logo"
src="{{ links.Assets }}/logo.svg"
>
</a>
</div>
{{ $activeNav := default "" .activeNav }}
{{ if not .hideNav }}
{{ if .user }}
<ul class="nav d-flex justify-content-around">
<li class="nav-item {{ if eq $activeNav "profile" }}active{{ end }}">
<a
data-test-id="link-tab-profile"
class="nav-link"
href="{{ links.Profile }}"
>
{{ tr "inc_nav.template.class.your-profile" }}
</a>
</li>
<li class="nav-item {{ if eq $activeNav "security" }}active{{ end }}">
<a
data-test-id="link-tab-security"
class="nav-link"
href="{{ links.Security }}"
>
{{ tr "inc_nav.template.class.security" }}
</a>
</li>
<li class="nav-item {{ if eq $activeNav "sessions" }}active{{ end }}">
<a
data-test-id="link-tab-login-session"
class="nav-link"
href="{{ links.Sessions }}"
>
{{ tr "inc_nav.template.class.login-session" }}
</a>
</li>
<li class="nav-item {{ if eq $activeNav "clients" }}active{{ end }}">
<a
data-test-id="link-tab-authorized-clients"
class="nav-link"
href="{{ links.AuthorizedClients }}"
>
{{ tr "inc_nav.template.class.authorized-clients" }}
</a>
</li>
</ul>
{{ end }}
{{ end }}
</div>

View File

@@ -0,0 +1,12 @@
<div class="position-fixed bottom-0 right-0 p-3 mb-2" style="z-index: 5; right: 0; bottom: 0;">
{{ range . }}
<div class="toast align-items-center bg-dark text-white" role="alert" aria-live="polite" aria-atomic="true" data-delay="5000" style="min-width:200px;">
<div class="d-flex align-items-center toast-body border-top border-{{ .Type }}">
{{ .Text | html }}
<button type="button" class="ml-auto text-white btn p-0" data-dismiss="toast" aria-label="Close">
<i class="bi bi-x"></i>
</button>
</div>
</div>
{{ end }}
</div>

View File

@@ -0,0 +1,56 @@
{{ template "inc_header.html.tpl" . }}
<div class="card-body p-0">
<h4 class="mb-0 p-3 border-bottom">{{ tr "invite.template.title" }}</h4>
<form
method="POST"
action="{{ links.AcceptInvite }}"
class="p-3"
>
{{ .csrfField }}
{{ if .form.error }}
<div class="text-danger font-weight-bold p-3" role="alert">
{{ .form.error }}
</div>
{{ end }}
<div class="mb-3">
<label>
{{ tr "invite.template.form.email.label" }}
</label>
<input
data-test-id="input-email"
type="email"
class="form-control"
name="email"
readonly
placeholder="{{ tr "invite.template.form.email.placeholder" }}"
value="{{ .user.Email }}"
aria-label="{{ tr "invite.template.form.emaillabel" }}"
>
</div>
<div class="mb-3">
<label>
{{ tr "invite.template.form.new-password.label" }}
</label>
<input
data-test-id="input-new-password"
type="password"
required
class="form-control"
name="password"
autocomplete="new-password"
placeholder="{{ tr "invite.template.form.new-password.placeholder" }}"
aria-label="{{ tr "invite.template.form.new-passwordlabel" }}"
>
</div>
<div class="text-right">
<button
data-test-id="button-change-password"
class="btn btn-primary btn-block btn-lg"
type="submit"
>
{{ tr "invite.template.form.buttons.password" }}
</button>
</div>
</form>
</div>
{{ template "inc_footer.html.tpl" . }}

View File

@@ -0,0 +1,123 @@
{{ template "inc_header.html.tpl" . }}
<div class="card-body p-0">
<h4 class="login-title mb-0 p-3 border-bottom">{{ tr "login.template.title" }}</h4>
{{ if .settings.LocalEnabled }}
<form
method="POST"
action="{{ links.Login }}"
class="p-3"
>
{{ .csrfField }}
{{ if .form.error }}
<div
data-test-id="error"
class="text-danger mb-4 font-weight-bold"
role="alert"
>
{{ .form.error }}
</div>
{{ end }}
<div class="mb-3">
<label>
{{ tr "login.template.form.email.label" }}
</label>
<input
data-test-id="input-email"
type="email"
class="form-control"
name="email"
required
placeholder="{{ tr "login.template.form.email.placeholder" }}"
value="{{ if .form }}{{ .form.email }}{{ end }}"
autocomplete="username"
aria-label="{{ tr "login.template.form.email.label" }}">
</div>
{{ if not .form.splitCredentialsCheck }}
<div class="mb-3">
<label>
{{ tr "login.template.form.password.label" }}
</label>
<input
data-test-id="input-password"
type="password"
required
class="form-control"
name="password"
placeholder="{{ tr "login.template.form.password.placeholder" }}"
autocomplete="current-password"
aria-label="{{ tr "login.template.form.password.label" }}">
</div>
<div class="row">
<div class="col text-right">
{{ if .enableRememberMe }}
<button
data-test-id="button-login-and-remember"
class="btn btn-primary btn-block btn-lg"
name="keep-session"
value="true"
type="submit"
>
{{ tr "login.template.form.button.login-and-remember" }}
</button>
{{ end }}
<button
data-test-id="button-login"
class="btn btn-light btn-block btn-lg"
type="submit"
>
{{ tr "login.template.form.button.login" }}
</button>
</div>
</div>
{{ else }}
<div class="row">
<div class="col text-right">
<button
data-test-id="button-continue"
class="btn btn-primary btn-block btn-lg"
name="keep-session"
value="true"
type="submit"
>
{{ tr "login.template.form.button.continue" }}
</button>
</div>
</div>
{{ end }}
</form>
<div class="row text-center px-3 pb-3">
{{ if .settings.PasswordResetEnabled }}
<div class="col cols-6">
<a
data-test-id="link-request-password-reset"
href="{{ links.RequestPasswordReset }}"
>
{{ tr "login.template.links.request-password-reset" }}
</a>
</div>
{{ end }}
{{ if .settings.SignupEnabled }}
<div class="col cols-6">
<a
data-test-id="link-signup"
href="{{ links.Signup }}"
>
{{ tr "login.template.links.signup" }}
</a>
</div>
{{ end }}
</div>
{{ end }}
{{ if .settings.ExternalEnabled }}
<div class="px-3 pb-3">
{{ range .providers }}
<a href="{{ links.External }}/{{ .Handle }}" class="btn btn-light btn-block btn-lg mb-2 mt-1 text-dark">
<i class="bi bi-{{ .Icon }} mr-1"></i>
{{ tr "login.template.links.external.login-with" "idp" (coalesce .Label .Handle) }}
</a>
{{ end }}
</div>
{{ end }}
</div>
{{ template "inc_footer.html.tpl" . }}

View File

@@ -0,0 +1,24 @@
{{ template "inc_header.html.tpl" . }}
<div class="card-body">
<form>
{{ if .form.error }}
<div
data-test-id="error"
class="text-danger font-weight-bold"
role="alert"
>
{{ .form.error }}
</div>
{{ else }}
<div class="text-dark font-weight-bold" role="primary">
{{ tr "logout.template.log-out" }}
</div>
{{ end }}
<div class="text-center my-3">
{{ tr "logout.template.log-in" "link" .link }}
</div>
</form>
</div>
{{ template "inc_footer.html.tpl" . }}

View File

@@ -0,0 +1,44 @@
{{ template "inc_header.html.tpl" set . "hideNav" true }}
<div class="card-body p-0">
<h4 class="mb-0 p-3 border-bottom">{{ tr "mfa-totp-disable.template.title" }}</h4>
<form
class="p-3"
method="POST"
action="{{ links.MfaTotpDisable }}"
>
{{ tr "mfa-totp-disable.template.instructions" }}
{{ if .form.error }}
<div class="pt-3 text-danger font-weight-bold" role="alert">
{{ .form.error }}
</div>
{{ end }}
{{ .csrfField }}
<div class="input-group my-3">
<input
type="text"
required
class="form-control lg text-center mfa-code-mask"
name="code"
maxlength="6"
minlength="6"
aria-required="true"
placeholder="000 000"
autocomplete="off"
style="letter-spacing:5px;font-size:20px;"
aria-label="Code">
</div>
<button
class="btn btn-primary btn-block btn-lg"
name="keep-session"
value="true"
type="submit"
>
{{ tr "mfa-totp-disable.template.button.remove" }}
</button>
</form>
</div>
{{ template "inc_footer.html.tpl" . }}

View File

@@ -0,0 +1,83 @@
{{ template "inc_header.html.tpl" set . "hideNav" true }}
<div class="card-body p-0">
<h4 class="mb-0 p-3 border-bottom">{{ tr "mfa-totp.template.title" }}</h4>
{{ if .enforced }}
<p
data-test-id="p-enforced-TOTP"
class="p-3 text-danger mb-0 font-weight-bold"
>
{{ tr "mfa-totp.template.enforced" }}
</p>
{{ end }}
<div
data-test-id="div-configure-TOTP"
class="container p-3 m-0"
>
<div class="row">
<div class="col-12 col-sm-6">
<p>
{{ tr "mfa-totp.template.instructions" }}
</p>
<ul>
<li>
{{ tr "mfa-totp.template.lastpass" "lastpass" "https://lastpass.com/auth/" }}
</li>
<li>
{{ tr "mfa-totp.template.gauth" "android" "https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2" "iphone" "https://apps.apple.com/us/app/google-authenticator/id388497605" }}
</li>
<li>
{{ tr "mfa-totp.template.authy" "link" "https://authy.com" }}
</li>
</ul>
</div>
<div class="col-12 col-sm-6 p-0 mb-3">
<img class="px-4 w-100" style="margin-top: -1rem; margin-bottom: -1rem;" src="{{ if .devQRImage }}{{ .devQRImage }}{{ else }}{{ links.MfaTotpQRImage }}{{ end }}" />
<h5 class="px-4 text-center mb-3">{{ .secret }}</h5>
<form
class="px-3"
method="POST"
action="{{ links.MfaTotpNewSecret }}"
>
{{ tr "mfa-totp.template.form.title" }}
{{ if .form.error }}
<div class="alert alert-danger" role="alert">
{{ .form.error }}
</div>
{{ end }}
{{ .csrfField }}
<div class="input-group my-3">
<input
type="text"
required
class="form-control lg text-center mfa-code-mask"
name="code"
maxlength="6"
minlength="6"
aria-required="true"
placeholder="000 000"
autocomplete="off"
style="letter-spacing:5px;font-size:20px;"
aria-label="Code">
</div>
<button
data-test-id="button-submit"
class="btn btn-primary btn-block btn-lg"
name="keep-session"
value="true"
type="submit"
>
{{ tr "mfa-totp.template.form.button" }}
</button>
</form>
</div>
</div>
</div>
</div>
{{ template "inc_footer.html.tpl" . }}

View File

@@ -0,0 +1,106 @@
{{ template "inc_header.html.tpl" set . "hideNav" true }}
<div class="card-body p-0 mb-2">
<h4 class="mb-0 p-3 border-bottom">{{ tr "mfa.template.title" }}</h4>
{{ if .emailOtpPending }}
<form
class="p-3"
method="POST"
action="{{ links.Mfa }}"
>
<p>{{ tr "mfa.template.email.instructions" }}</p>
{{ if .form.emailOtpError }}
<div class="text-danger my-4 font-weight-bold" role="alert">
{{ .form.emailOtpError }}
</div>
{{ end }}
{{ .csrfField }}
<div class="input-group my-3">
<input
type="text"
required
class="form-control text-center mfa-code-mask"
name="code"
maxlength="6"
minlength="6"
aria-required="true"
placeholder="000 000"
autocomplete="off"
style="letter-spacing:5px;font-size:20px;"
aria-label="{{ tr "mfa.template.email.code" }}">
</div>
<button
class="btn btn-primary btn-block btn-lg"
name="action"
value="verifyEmailOtp"
type="submit"
>
{{ tr "mfa.template.email.verify" }}
</button>
<a
href="{{ links.Mfa }}?action=resendEmailOtp"
class="btn btn-light btn-block btn-lg text-dark"
name="action"
value="resendEmailOtp"
>
{{ tr "mfa.template.email.resend" }}
</a>
</form>
{{ else if not .emailOtpDisabled }}
<p class="p-3 mb-0">
<i class="bi bi-check-circle text-success h5 mr-1"></i> {{ tr "mfa.template.email.confirmed" }}
</p>
{{ end }}
{{ if .totpPending }}
<form
class="p-3"
method="POST"
action="{{ links.Mfa }}"
>
<p>{{ tr "mfa.template.totp.instructions" }}</p>
{{ if .form.totpError }}
<div class="alert alert-danger" role="alert">
{{ .form.totpError }}
</div>
{{ end }}
{{ .csrfField }}
<div class="input-group my-3">
<input
type="text"
required
class="form-control text-center mfa-code-mask"
name="code"
maxlength="6"
minlength="6"
aria-required="true"
placeholder="000 000"
autocomplete="off"
style="letter-spacing:5px;font-size:20px;"
aria-label="{{ tr "mfa.template.totp.code" }}">
</div>
<button
class="btn btn-primary btn-block btn-lg"
type="submit"
name="action"
value="verifyTotp"
>
{{ tr "mfa.template.totp.verify" }}
</button>
</form>
{{ else if and (not .totpDisabled) (not .totpPending) (not .totpUnconfigured) }}
<p class="px-3 pt-3 pb-2 mb-0">
<i class="bi bi-check-circle text-success h5 mr-1"></i> {{ tr "mfa.template.totp.confirmed" }}
</p>
{{ end }}
</div>
{{ template "inc_footer.html.tpl" . }}

View File

@@ -0,0 +1,52 @@
{{/* setting hideNav=true to root scope and passign it to header template */}}
{{ template "inc_header.html.tpl" set . "hideNav" true }}
<div class="card-body p-0">
{{ if .invalidUser }}
<div class="text-danger font-weight-bold p-3" role="alert">
{{ tr "oauth2-authorize-client.template.errors.invalid-user" "link" links.Profile }}
</div>
{{ end }}
<h4 class="mb-0 p-3 border-bottom">{{ tr "oauth2-authorize-client.template.title" }} "{{ coalesce .client.Name }}"</h1>
<form
action="{{ links.OAuth2AuthorizeClient }}"
method="POST"
class="p-3"
>
{{ .csrfField }}
<p>
{{ tr "oauth2-authorize-client.template.form.greeting-paragraph" }} {{ coalesce .user.Name .user.Handle .user.Email }},
</p>
<p>
<b>{{ .client.Name }}</b> {{ tr "oauth2-authorize-client.template.form.question-for-client" }}
</p>
<p class="text-center">
<button
data-test-id="button-allow"
type="submit"
name="allow"
{{ if .disabled }}disabled{{ end }}
class="btn btn-{{ if .disabled }}secondary{{ else }}primary{{ end }} btn-lg m-2"
style="width:250px;"
>
{{ tr "oauth2-authorize-client.template.form.buttons.allow" }}
</button>
<button
data-test-id="button-deny"
type="submit"
name="deny"
class="btn btn-danger btn-lg m-2"
style="width:250px;"
>
{{ tr "oauth2-authorize-client.template.form.buttons.deny" }}
</button>
</p>
<div class="text-center">
{{ tr "oauth2-authorize-client.template.links.mistake" }}
</div>
</form>
</div>
{{ template "inc_footer.html.tpl" . }}

View File

@@ -0,0 +1,15 @@
{{ template "inc_header.html.tpl" . }}
<div class="card-body p-0">
<h4 class="mb-0 p-3 border-bottom">{{ tr "password-reset-requested.template.title" }}</h4>
<div
data-test-id="div-reset-instructions"
class="p-3"
role="alert"
>
{{ tr "password-reset-requested.template.instructions" }}
</div>
<div class="text-center my-3">
{{ tr "password-reset-requested.template.links" "signup" links.Signup "login" links.Login }}</a>
</div>
</div>
{{ template "inc_footer.html.tpl" . }}

View File

@@ -0,0 +1,11 @@
{{ template "inc_header.html.tpl" . }}
<div class="card-body p-0">
<h4 class="mb-0 p-3 border-bottom">{{ tr "pending-email-confirmation.template.title" }}</h4>
<div class="p-3" role="alert">
{{ tr "pending-email-confirmation.template.instructions" }}
</div>
<div class="text-center my-3">
{{ tr "pending-email-confirmation.template.links" "signup" links.Signup "login" links.Login }}</a>
</div>
</div>
{{ template "inc_footer.html.tpl" . }}

View File

@@ -0,0 +1,146 @@
{{ template "inc_header.html.tpl" set . "activeNav" "profile" }}
<div class="card-body p-0">
<form
method="POST"
action="{{ links.Profile }}"
enctype="multipart/form-data"
class="p-3"
>
{{ .csrfField }}
{{ if .form.error }}
<div
data-test-id="error"
class="text-danger mb-4 font-weight-bold"
role="alert"
>
{{ .form.error }}
</div>
{{ end }}
<div class="mb-3">
<label for="profileFormEmail">{{ tr "profile.template.form.email.label" }}</label>
<input
data-test-id="input-email"
type="email"
class="form-control"
name="email"
id="profileFormEmail"
placeholder="email@domain.ltd"
autocomplete="username"
readonly
value="{{ .form.email }}"
aria-label="{{ tr "profile.template.form.email.label" }}"
>
<div>
{{ if .emailConfirmationRequired }}
<div class="form-text text-danger">
{{ tr "profile.template.form.email.resend-confirmation-link" "link" links.PendingEmailConfirmation }}
</div>
{{ end }}
</div>
</div>
<div class="mb-3">
<label for="profileFormName">{{ tr "profile.template.form.name.label" }}</label>
<input
data-test-id="input-name"
type="text"
class="form-control"
name="name"
id="profileFormName"
placeholder="{{ tr "profile.template.form.name.placeholder" }}"
value="{{ .form.name }}"
autocomplete="name"
aria-label="{{ tr "profile.template.form.name.label" }}"
>
</div>
<div class="mb-3">
<label for="profileFormHandle">{{ tr "profile.template.form.handle.label" }}</label>
<input
data-test-id="input-handle"
type="text"
class="form-control handle-mask"
name="handle"
id="profileFormHandle"
placeholder="{{ tr "profile.template.form.handle.placeholder" }}"
value="{{ .form.handle }}"
autocomplete="handle"
aria-label="{{ tr "profile.template.form.handle.label" }}"
>
</div>
<div class="mb-3">
<label for="profileFormPreferredLanguage">{{ tr "profile.template.form.preferred-language.label" }}</label>
<select
data-test-id="select-language"
class="form-control"
name="preferredLanguage"
id="profileFormPreferredLanguage"
aria-label="{{ tr "profile.template.form.preferred-language.label" }}"
value="{{ .form.preferredLanguage }}"
>
{{ $prefLang := .form.preferredLanguage }}
{{ range .languages }}
<option
value="{{ .Tag }}"
{{ if eq $prefLang .Tag.String }}selected{{ end }}
>
{{ .LocalizedName }} ({{ .Name }})
</option>
{{ end }}
</select>
</div>
{{ if .avatarEnabled }}
<hr/>
<div class="mb-3">
<label for="profileFormHandle">{{ tr "profile.template.form.avatar.label" }}</label>
<div class="d-block">
<img style="height: 4rem; width: 4rem;" class="rounded-circle" src="{{ .form.avatarUrl }}" alt="Profile Photo">
<div class="mt-3">
<label for="avatar" class="p-2 bg-light text-dark rounded" style="cursor: pointer;">
{{ tr "profile.template.form.avatar.upload" }}
</label>
<input id="avatar" name="avatar" value="avatar" type="file" class="sr-only" accept="image/*">
{{ if .isAvatar }}
<button
name="avatar-delete"
value="avatar-delete"
class="ml-2 btn btn-danger"
>
{{ tr "profile.template.form.avatar.delete" }}
</button>
{{ end }}
</div>
</div>
</div>
<div class="form-row mb-3">
<div class="col">
<label for="initialColor">{{ tr "profile.template.form.avatar-initial.color" }}</label>
<input type="color" id="initialColor" class="form-control input-color" value="{{ .form.initialTextColor }}" name="initial-color">
</div>
<div class="col">
<label for="customColor">{{ tr "profile.template.form.avatar-initial.background-color" }}</label>
<input type="color" id="customColor" class="form-control input-color" value="{{ .form.initialBgColor }}" name="initial-bg">
</div>
</div>
{{ end }}
<div>
<button
data-test-id="button-submit"
type="submit"
class="btn btn-primary btn-block btn-lg"
>
{{ tr "profile.template.form.buttons.submit" }}
</button>
</div>
</form>
</div>
{{ template "inc_footer.html.tpl" . }}

View File

@@ -0,0 +1,49 @@
{{ template "inc_header.html.tpl" . }}
<div class="card-body p-0">
<h4 class="mb-0 p-3 border-bottom">{{ tr "request-password-reset.template.title" }}</h4>
<form
method="POST"
action="{{ links.RequestPasswordReset }}"
class="p-3"
>
{{ .csrfField }}
{{ if .form.error }}
<div
data-test-id="error"
class="text-danger font-weight-bold mb-4"
role="alert"
>
{{ .form.error }}
</div>
{{ end }}
<div class="mb-3">
<label>
{{ tr "request-password-reset.template.form.email.label" }}
</label>
<input
data-test-id="input-email"
type="email"
class="form-control"
name="email"
required
placeholder="{{ tr "request-password-reset.template.form.email.placeholder" }}"
autocomplete="username"
value="{{ if .form }}{{ .form.email }}{{ end }}"
aria-label="{{ tr "request-password-reset.template.form.email.label" }}"
>
</div>
<div class="text-right">
<button
data-test-id="button-password-reset"
class="btn btn-primary btn-block btn-lg"
type="submit"
>
{{ tr "request-password-reset.template.form.buttons.request" }}
</button>
</div>
</form>
<div class="text-center my-3">
{{ tr "request-password-reset.template.links" "signup" links.Signup "login" links.Login }}</a>
</div>
</div>
{{ template "inc_footer.html.tpl" . }}

View File

@@ -0,0 +1,56 @@
{{ template "inc_header.html.tpl" . }}
<div class="card-body p-0">
<h4 class="mb-0 p-3 border-bottom">{{ tr "reset-password.template.title" }}</h4>
<form
method="POST"
action="{{ links.ResetPassword }}"
class="p-3"
>
{{ .csrfField }}
{{ if .form.error }}
<div class="text-danger font-weight-bold p-3" role="alert">
{{ .form.error }}
</div>
{{ end }}
<div class="mb-3">
<label>
{{ tr "reset-password.template.form.email.label" }}
</label>
<input
data-test-id="input-email"
type="email"
class="form-control"
name="email"
readonly
placeholder="{{ tr "reset-password.template.form.email.placeholder" }}"
value="{{ .user.Email }}"
aria-label="{{ tr "reset-password.template.form.emaillabel" }}"
>
</div>
<div class="mb-3">
<label>
{{ tr "reset-password.template.form.new-password.label" }}
</label>
<input
data-test-id="input-new-password"
type="password"
required
class="form-control"
name="password"
autocomplete="new-password"
placeholder="{{ tr "reset-password.template.form.new-password.placeholder" }}"
aria-label="{{ tr "reset-password.template.form.new-passwordlabel" }}"
>
</div>
<div class="text-right">
<button
data-test-id="button-change-password"
class="btn btn-primary btn-block btn-lg"
type="submit"
>
{{ tr "reset-password.template.form.buttons.change-password" }}
</button>
</div>
</form>
</div>
{{ template "inc_footer.html.tpl" . }}

View File

@@ -0,0 +1,233 @@
# The following list of templates and scenarios describes
# all possible views (hopefully).
#
# Please read auth/README.adoc for details
error-internal:
Default:
error: |-
Lorem ipsum dolor sit...
login:
All disabled: {}
All enabled:
settings:
LocalEnabled: true
SignupEnabled: true
PasswordResetEnabled: true
ExternalEnabled: true
providers:
- { Label: Google, Handle: google, Icon: google }
- { Label: Facebook, Handle: facebook, Icon: facebook }
- { Label: GitHub, Handle: github, Icon: github }
- { Label: Corteza, Handle: corteza, Icon: shield-lock }
Only external:
settings:
PasswordResetEnabled: true
ExternalEnabled: true
providers:
- { Label: Google, Handle: google, Icon: google }
- { Label: Facebook, Handle: facebook, Icon: facebook }
- { Label: GitHub, Handle: github, Icon: github }
- { Label: Corteza, Handle: corteza, Icon: shield-lock }
Without signup and pass reset:
settings:
LocalEnabled: true
SignupEnabled: false
PasswordResetEnabled: false
With Split credentials check / username:
form:
splitCredentialsCheck: true
settings:
LocalEnabled: true
With errors after submit:
settings:
LocalEnabled: true
form:
email: some.email@example.tld
error: "There was an error..."
security:
Default:
user: { ID: 123, Name: John Doe }
settings:
LocalEnabled: true
MFA enabled:
user: { ID: 123, Name: John Doe }
settings:
LocalEnabled: true
MultiFactor:
TOTP: { Enabled: true }
EmailOTP: { Enabled: true }
MFA enforced by user:
user: { ID: 123, Name: John Doe }
totpEnforced: false
emailOtpEnforced: true
settings:
LocalEnabled: true
MultiFactor:
TOTP: { Enabled: true }
EmailOTP: { Enabled: true }
mfa:
Default: {}
MFA pending:
emailOtpPending: true
totpDisabled: true
TOTP pending:
totpPending: true
With error:
emailOtpPending: true
form:
emailOtpError: "There was an error..."
With alerts (toast):
alerts:
- Text: booya
- Text: Red top border for errors
Type: danger
- Text: Modi sociosqu quibusdam modi delectus ridiculus per potenti sollicitudin dicta porro cumque, gravida odio? Tempora! Molestia
Type: danger
- Text: Primary toast
Type: primary
mfa-totp:
Default:
devQRImage: https://awgsalesservices.com/wp-content/uploads/2019/02/QR-code-example.jpg
TOTP enforced:
enforced: true
devQRImage: https://awgsalesservices.com/wp-content/uploads/2019/02/QR-code-example.jpg
mfa-totp-disable:
Default: {}
With error:
form:
error: "There was an error..."
logout:
Default:
link: '#'
With error:
form:
error: "There was an error..."
signup:
Default: {}
With error:
form:
error: "There was an error..."
reset-password:
Default: {}
request-password-reset:
Default: {}
With error:
form:
error: "There was an error..."
password-reset-requested:
Default: {}
pending-email-confirmation:
Default:
profile:
Default:
user:
ID: 123
Name: John Doe
settings:
ExternalEnabled: true
LocalEnabled: true
With errros:
user:
ID: 123
Name: John Doe
settings:
ExternalEnabled: true
LocalEnabled: true
form:
error: Profile change form submit error
name: foo
email: foo
With unconfirmed email:
user:
ID: 123
Name: John Doe
settings:
ExternalEnabled: true
LocalEnabled: true
form:
error: Profile change form submit error
name: foo
email: foo
emailConfirmationRequired: true
In OAuth2 flow:
user:
ID: 123
Name: John Doe
settings:
ExternalEnabled: true
LocalEnabled: true
client:
Name: foo
emailConfirmationRequired: true
change-password:
Default: {}
With error:
form:
error: "There was an error..."
create-password:
Default: {}
With error:
form:
error: "There was an error..."
authorized-clients:
Empty: {}
Full:
authorizedClients:
- Name: Corteza Web Applications
- Name: Matrix Chat server
- Name: Company's Wordpress
oauth2-authorize-client:
Default:
client:
Name: name of the client
user:
Name: John Doe
Disabled:
invalidUser: Some good reason why this authorization of client is disabled...
disabled: true
client:
Name: name of the client
user:
Name: John Doe
sessions:
Full:
sessions:
- Current: true
ExpiresIn: 3
RemoteAddr: 4.224.2.3
UserAgent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36
- ExpiresIn: 2
RemoteAddr: 4.224.2.3
SameRemoteAddr: true
UserAgent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36
- ExpiresIn: 1
RemoteAddr: 4.224.2.3
SameUserAgent: true
UserAgent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36
- ExpiresIn: 0
UserAgent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36

View File

@@ -0,0 +1,122 @@
{{ template "inc_header.html.tpl" set . "activeNav" "security" }}
<div class="card-body p-0">
<form
method="POST"
action="{{ links.Security }}"
class="p-3"
>
{{ if .settings.LocalEnabled }}
<h5>{{ tr "security.template.password.title" }}</h5>
<a
data-test-id="link-change-password"
href="{{ links.ChangePassword }}"
>
{{ tr "security.template.password.change-link" }}
</a>
{{ end }}
<hr />
<div>
{{ .csrfField }}
<h5 class="mb-3">{{ tr "security.template.mfa.title" }}</h5>
{{ if or .settings.MultiFactor.TOTP.Enabled .settings.MultiFactor.EmailOTP.Enabled }}
{{ if .settings.MultiFactor.TOTP.Enabled }}
<div class="mb-4">
<label class="text-primary">{{ tr "security.template.mfa.totp.title" }}</label>
<div class="d-flex align-items-center">
<div>
{{ if .totpEnforced }}
<i class="bi bi-check-circle text-success h5 mr-1"></i>
{{ else }}
<i class="bi bi-exclamation-circle-fill text-danger h5 mr-1"></i>
{{ end }}
{{ if .totpEnforced }}
{{ tr "security.template.mfa.totp.enforced" }}
{{ else }}
{{ tr "security.template.mfa.totp.disabled" }}
{{ end }}
</div>
<div class="ml-auto">
{{ if .totpEnforced }}
{{ if not .settings.MultiFactor.TOTP.Enforced }}
<button
data-test-id="button-disable-totp"
name="action"
value="disableTOTP"
class="btn btn-danger"
>
{{ tr "security.template.mfa.totp.disable" }}
</button>
{{ end }}
{{ else }}
<button
data-test-id="button-configure-totp"
name="action"
value="configureTOTP"
class="btn btn-primary"
>
{{ tr "security.template.mfa.totp.configure" }}
</button>
{{ end }}
</div>
</div>
</div>
{{ end }}
{{ if .settings.MultiFactor.EmailOTP.Enabled }}
<div class="mb-3">
<label class="text-primary">{{ tr "security.template.mfa.email.title" }}</label class="text-primary">
<div class="d-flex align-items-center">
<div>
{{ if .emailOtpEnforced }}
<i class="bi bi-check-circle text-success h5 mr-1"></i>
{{ else }}
<i class="bi bi-exclamation-circle-fill text-danger h5 mr-1"></i>
{{ end }}
{{ if .emailOtpEnforced }}
{{ tr "security.template.mfa.email.enforced" }}
{{ else }}
{{ tr "security.template.mfa.email.disabled" }}
{{ end }}
</div>
<div class="ml-auto">
{{ if .emailOtpEnforced }}
{{ if not .settings.MultiFactor.EmailOTP.Enforced }}
<button
data-test-id="button-disable-email-otp"
name="action"
value="disableEmailOTP"
class="btn btn-danger"
>
{{ tr "security.template.mfa.email.disable" }}
</button>
{{ end }}
{{ else }}
<button
data-test-id="button-enable-email-otp"
name="action"
value="enableEmailOTP"
class="btn btn-primary"
>
{{ tr "security.template.mfa.email.enable" }}
</button>
{{ end }}
</div>
</div>
</div>
{{ end }}
{{ else }}
<div class="mb-1 font-italic" role="alert">
{{ tr "security.template.mfa.all-disabled" }}
</div>
{{ end }}
</div>
</form>
</div>
{{ template "inc_footer.html.tpl" . }}

View File

@@ -0,0 +1,68 @@
{{ template "inc_header.html.tpl" set . "activeNav" "sessions" }}
<div class="card-body p-0">
<form
method="POST"
action="{{ links.Sessions }}"
>
{{ .csrfField }}
{{- if and .sessions (gt (len .sessions) 1) }}
<div class="mb-0 p-3 border-bottom">
<h4 class="card-title d-inline">{{ tr "sessions.template.title" }}</h4>
<button
data-test-id="button-logout-from-everywhere"
type="submit"
name="delete-all-but-current"
value="true"
class="btn btn-sm btn-danger float-right"
>
{{ tr "sessions.template.delete-all" }}
</button>
</div>
{{ end }}
</form>
<form
method="POST"
action="{{ links.Sessions }}"
class="p-3"
>
{{ .csrfField }}
{{ range .sessions}}
<div class="mb-2 border-bottom">
{{ if .Current }}
<h5>{{ tr "sessions.template.list.current" }}</h5>
{{ end }}
<label class="mb-0 d-block">{{ tr "sessions.template.list.authorized-on" }}</label>
<p class="w-75 d-inline-block">
<time datetime="{{ .CreatedAt }}">{{ .CreatedAt | date "Mon, 02 Jan 2006 15:04 MST" }}</time>
</p>
<label class="mb-0 d-block">{{ tr "sessions.template.list.ip-address" }}</label>
<p class="w-75 d-inline-block">{{ .RemoteAddr }}</p>
{{ if .SameRemoteAddr}}
<span class="badge badge-light float-right">{{ tr "sessions.template.list.same-machine" }}</span>
{{ end }}
<label class="mb-0 d-block">{{ tr "sessions.template.list.expires" }}</label>
<p class="w-75 d-inline-block">
<time datetime="{{ .ExpiresAt }}">{{ .ExpiresAt | date "Mon, 02 Jan 2006 15:04 MST" }}</time>
</p>
{{ if .Expired }}
<span class="badge badge-warning float-right">{{ tr "sessions.template.list.expired" }}</span>
{{ else if eq .ExpiresIn 0 }}
<span class="badge badge-warning float-right">{{ tr "sessions.template.list.today" }}</span>
{{ else if eq .ExpiresIn 1 }}
<span class="badge badge-light float-right">{{ tr "sessions.template.list.tomorrow" }}</span>
{{ else }}
<span class="badge badge-light float-right">{{ tr "sessions.template.list.soon" "days" .ExpiresIn }}</span>
{{ end }}
<label class="mb-0 d-block">{{ tr "sessions.template.list.browser" }}</label>
<p class="small w-75 d-inline-block">
{{ .UserAgent }}
</p>
{{ if .SameUserAgent}}
<span class="badge badge-light float-right">{{ tr "sessions.template.list.same-browser" }}</span>
{{ end }}
</div>
{{ end }}
</form>
</div>
{{ template "inc_footer.html.tpl" . }}

View File

@@ -0,0 +1,77 @@
{{ template "inc_header.html.tpl" . }}
<div class="card-body p-0">
<h4 class="mb-0 p-3 border-bottom">{{ tr "signup.template.title" }}</h4>
<form
method="POST"
action="{{ links.Signup }}"
class="p-3"
>
{{ .csrfField }}
{{ if .form.error }}
<div
data-test-id="error"
class="text-danger font-weight-bold mb-3"
role="alert"
>
{{ .form.error }}
</div>
{{ end }}
<div class="mb-3">
<label>
{{ tr "signup.template.form.email.label" }}
</label>
<input
data-test-id="input-email"
type="email"
class="form-control"
name="email"
required
placeholder="{{ tr "signup.template.form.email.placeholder" }}"
autocomplete="username"
value="{{ .form.email }}"
aria-label="{{ tr "signup.template.form.email.label" }}">
</div>
<div class="mb-3">
<label>
{{ tr "signup.template.form.password.label" }}
</label>
<input
data-test-id="input-password"
type="password"
class="form-control"
name="password"
required
placeholder="{{ tr "signup.template.form.password.placeholder" }}"
autocomplete="new-password"
aria-label="{{ tr "signup.template.form.password.label" }}">
</div>
<div class="mb-3">
<label>
{{ tr "signup.template.form.name.label" }}
</label>
<input
data-test-id="input-name"
type="text"
class="form-control"
name="name"
placeholder="{{ tr "signup.template.form.name.placeholder" }}"
value="{{ .form.name }}"
autocomplete="name"
aria-label="{{ tr "signup.template.form.name.label" }}">
</div>
<div>
<button
id="submit"
data-test-id="button-submit"
class="btn btn-primary btn-block btn-lg"
type="submit"
>{{ tr "signup.template.form.button.sign-up" }}</button>
</div>
</form>
<div class="text-center mt-2 mb-3">
{{ tr "signup.template.log-in" "link" links.Login }}
</div>
</div>
{{ template "inc_footer.html.tpl" . }}

View File

@@ -0,0 +1,53 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Bootstrap core CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css" rel="stylesheet">
<script type="application/javascript">
window.addEventListener('load', function() {
document.getElementById('preview').src=location.hash.substring(1)
})
</script>
</head>
<body class="bg-dark text-light">
<div class="container-fluid">
<div class="row m-0 p-0">
<div class="col-3 pl-0">
<sidebar class="vh-100 position-fixed w-25 overflow-auto">
{{ range .templates }}
<div class="pt-2 w-75">
<code>{{ .Template }}.html.tpl</code>
<ul class="nav flex-column">
{{ range .Scenes }}
<li class="nav-item border-top">
<a target="preview"
class="nav-link text-light"
href="/auth/dev/scenarios?template={{ .Template }}&scene={{ .Name }}"
onclick="location.hash=this.href"
>
<div>
{{ .Name }}
</div>
</a>
</li>
{{ end }}
</ul>
</div>
{{ end }}
<sidebar>
</div>
<div class="col-9 m-0 p-0">
<iframe
name="preview"
id="preview"
class="w-100 border-0"
style="height: 100vh;"
src="about:blank"
/>
</div>
</div>
</div>
</body>
</html>