Test / test (push) Successful in 7m5s
Release / gates (push) Successful in 7m28s
Release / build (amd64, freebsd) (push) Successful in 2m52s
Release / build (amd64, linux) (push) Successful in 2m46s
Release / build (arm64, freebsd) (push) Successful in 2m22s
Release / build (arm64, linux) (push) Successful in 2m38s
Release / build (loong64, linux) (push) Successful in 2m7s
Release / build (riscv64, linux) (push) Successful in 2m17s
Release / release (push) Successful in 1m0s
Assisted-by: GLM 5.3
77 lines
3.1 KiB
HTML
77 lines
3.1 KiB
HTML
{{define "title"}}{{ tr "Sign in" }} · {{ tr "Volumen admin" }}{{end}}
|
|
{{define "login_content"}}
|
|
<div class="login__card">
|
|
<h1 class="login__title">{{ tr "Sign in" }}</h1>
|
|
<p class="login__pitch">{{ tr "Welcome back. Sign in to manage your posts." }}</p>
|
|
|
|
{{if .Error}}
|
|
<div class="status status--error" role="alert"
|
|
{{if .RetryAfter}}id="login-lockout"{{end}}>
|
|
<svg aria-hidden="true"><use href="/admin/assets/graphis.svg#alert-circle"></use></svg>
|
|
<div>
|
|
<span class="lockout-msg">{{ .Error }}</span>
|
|
{{if .RetryAfter}}<span class="lockout-count" id="lockout-countdown" data-seconds="{{ .RetryAfter }}"></span>{{end}}
|
|
</div>
|
|
</div>
|
|
{{end}}
|
|
|
|
<form method="post" action="/admin/login" class="stack">
|
|
<input type="hidden" name="_csrf" value="{{ .CSRFToken }}">
|
|
<div class="field">
|
|
<label class="field__label" for="username">{{ tr "Username" }}</label>
|
|
<input id="username" class="control" type="text" name="username"
|
|
autocomplete="username" required autofocus>
|
|
</div>
|
|
<div class="field">
|
|
<label class="field__label" for="password">{{ tr "Password" }}</label>
|
|
<div class="pw">
|
|
<input id="password" class="control" type="password" name="password"
|
|
autocomplete="current-password" required>
|
|
<button type="button" class="pw__toggle" id="pw-toggle" aria-label="{{ tr "Show password" }}">
|
|
<svg aria-hidden="true"><use href="/admin/assets/graphis.svg#eye"></use></svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<button type="submit" class="btn btn--primary login__submit">
|
|
{{ tr "Sign in" }}
|
|
<svg aria-hidden="true"><use href="/admin/assets/graphis.svg#arrow-right"></use></svg>
|
|
</button>
|
|
</form>
|
|
</div>
|
|
|
|
<script nonce="{{ .CSPNonce }}">
|
|
(() => {
|
|
const btn = document.getElementById("pw-toggle");
|
|
const input = document.getElementById("password");
|
|
btn.addEventListener("click", () => {
|
|
const show = input.type === "password";
|
|
input.type = show ? "text" : "password";
|
|
btn.setAttribute("aria-label", window.__(show ? "Hide password" : "Show password"));
|
|
});
|
|
|
|
/* The lockout counts down to the minute, and the form waits with
|
|
it: an honest disabled state, not a submit that fails. */
|
|
const countdown = document.getElementById("lockout-countdown");
|
|
if (!countdown) return;
|
|
let seconds = Number.parseInt(countdown.dataset.seconds || "0", 10);
|
|
const form = document.querySelector("form[action='/admin/login']");
|
|
if (form) {
|
|
form.querySelectorAll("input, button").forEach((el) => { el.disabled = true; });
|
|
}
|
|
const tick = () => {
|
|
if (seconds <= 0) {
|
|
countdown.textContent = window.__("login.retry");
|
|
if (form) {
|
|
form.querySelectorAll("input, button").forEach((el) => { el.disabled = false; });
|
|
}
|
|
return;
|
|
}
|
|
countdown.textContent = window.__("login.unlock").replace("{}", String(seconds));
|
|
seconds -= 1;
|
|
setTimeout(tick, 1000);
|
|
};
|
|
tick();
|
|
})();
|
|
</script>
|
|
{{end}}
|