Files
volumen/internal/web/templates/setup.html
T

146 lines
7.2 KiB
HTML
Raw Normal View History

2026-09-18 12:03:35 +02:00
{{define "title"}}{{ tr "Set up Volumen" }} · {{ tr "Volumen admin" }}{{end}}
{{define "login_content"}}
<div class="login__card setup">
<h1 class="login__title" data-i18n="Welcome to Volumen">{{ tr "Welcome to Volumen" }}</h1>
<p class="login__pitch" data-i18n="Set up the administrator account to open this installation.">{{ tr "Set up the administrator account to open this installation." }}</p>
{{if .Error}}
<div class="status status--error" role="alert">
<svg aria-hidden="true"><use href="/admin/assets/graphis.svg#alert-circle"></use></svg>
<div>{{ .Error }}</div>
</div>
{{end}}
<form method="post" action="/admin/setup" class="stack">
<input type="hidden" name="_csrf" value="{{ .CSRFToken }}">
<input type="hidden" name="language" id="setup-language" value="{{ .Lang }}">
<fieldset class="setup__group">
<legend data-i18n="Account">{{ tr "Account" }}</legend>
<div class="grid-2">
<div class="field">
<label class="field__label" for="username" data-i18n="Username">{{ tr "Username" }}</label>
<input id="username" class="control mono" type="text" name="username" value="admin"
autocomplete="username" required autofocus>
</div>
<div class="field">
<label class="field__label" for="name" data-i18n="Display name">{{ tr "Display name" }}</label>
<input id="name" class="control" type="text" name="name" autocomplete="name"
placeholder="{{ tr "Your real name" }}" data-i18n-placeholder="Your real name">
</div>
</div>
<div class="field">
<label class="field__label" for="password" data-i18n="Password">{{ tr "Password" }}</label>
<div class="pw">
<input id="password" class="control" type="password" name="password"
autocomplete="new-password" required data-pw-meter
data-min-length="{{ .Config.Admin.MinPasswordLength }}"
aria-describedby="pw-strength-text">
<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 class="pw-level" id="pw-level" data-level="0">
<div class="pw-level__bar" aria-hidden="true"><i></i><i></i><i></i><i></i></div>
<span class="pw-level__label" id="pw-strength-text" role="status" aria-live="polite"></span>
</div>
<p class="field__hint" data-i18n="password.hint">{{ trn .Config.Admin.MinPasswordLength "password.min" }}</p>
</div>
</fieldset>
<fieldset class="setup__group">
<legend data-i18n="Language">{{ tr "Language" }}</legend>
<div class="pill-group">
<a class="chip chip--switch" lang="en" href="/admin/setup?lang=en" data-lang-chip="en"
{{if eq .Lang "en"}}aria-current="true"{{end}}>English</a>
<a class="chip chip--switch" lang="cs" href="/admin/setup?lang=cs" data-lang-chip="cs"
{{if eq .Lang "cs"}}aria-current="true"{{end}}>Čeština</a>
</div>
</fieldset>
<fieldset class="setup__group">
<legend data-i18n="Colour scheme">{{ tr "Colour scheme" }}</legend>
<div class="pill-group pill-group--themes">
<label class="chip chip--radio chip--theme">
<input type="radio" name="theme" value="viridis" {{if eq .Theme "viridis"}}checked{{end}}>
<span class="chip__swatch chip__swatch--viridis" aria-hidden="true"></span><span>Viridis</span>
</label>
<label class="chip chip--radio chip--theme">
<input type="radio" name="theme" value="plasma" {{if eq .Theme "plasma"}}checked{{end}}>
<span class="chip__swatch chip__swatch--plasma" aria-hidden="true"></span><span>Plasma</span>
</label>
<label class="chip chip--radio chip--theme">
<input type="radio" name="theme" value="magma" {{if eq .Theme "magma"}}checked{{end}}>
<span class="chip__swatch chip__swatch--magma" aria-hidden="true"></span><span>Magma</span>
</label>
</div>
<p class="field__hint" data-i18n="The page takes the colours as you choose.">{{ tr "The page takes the colours as you choose." }}</p>
</fieldset>
<button type="submit" class="btn btn--primary login__submit">
<span data-i18n="Create account">{{ tr "Create account" }}</span>
<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 wizard previews its own choices: the palette rides the same
html attribute the sheets read, and the language swaps the page
text from the bundle the server carried. The chips are real
links to the same page with ?lang=, so both choices survive a
browser without JavaScript: the link reloads, the theme radios
submit. */
const BUNDLE = {{ .SetupI18n }};
const apply = (lang) => {
document.documentElement.lang = lang;
document.querySelectorAll("[data-i18n]").forEach((el) => {
const pair = BUNDLE.ui[el.dataset.i18n];
if (pair && pair[lang]) el.textContent = pair[lang];
});
document.querySelectorAll("[data-i18n-placeholder]").forEach((el) => {
const pair = BUNDLE.ui[el.dataset.i18nPlaceholder];
if (pair && pair[lang]) el.placeholder = pair[lang];
});
window.__volumen.i18n = BUNDLE.js[lang];
btn.setAttribute("aria-label",
window.__(input.type === "password" ? "Show password" : "Hide password"));
/* The meter's label comes from the shared catalogue: re-score
the field so the word follows the language swap. */
input.dispatchEvent(new Event("input"));
document.getElementById("setup-language").value = lang;
document.querySelectorAll("[data-lang-chip]").forEach((chip) => {
if (chip.dataset.langChip === lang) chip.setAttribute("aria-current", "true");
else chip.removeAttribute("aria-current");
});
};
document.querySelectorAll("[data-lang-chip]").forEach((chip) => {
chip.addEventListener("click", (event) => {
event.preventDefault();
apply(chip.dataset.langChip);
});
});
document.querySelectorAll('input[name="theme"]').forEach((radio) => {
radio.addEventListener("change", () => {
document.documentElement.dataset.palette = radio.value;
});
});
/* The strength meter itself is the shared one the layout wires
for every input[data-pw-meter]; only the label refresh on a
language switch is local to this page. */
})();
</script>
{{end}}