Files
logstream/web/login.html
T
cedricandClaude Opus 5.5 0bfea7cd93 UI: replace Lucide icons with Papicons
Swap the inline SVG icons of the web UI (top bar, view mode, time range,
export, close, empty state, back to top, settings tabs, save, delete,
lock) for their Papicons equivalents (MIT, Association Papillon).
Papicons are filled shapes: they carry class "pi", which sets
fill: currentColor and no stroke, so the stroked Lucide icons that stay
(zoom out, Docker badge) are unaffected. Add THIRD_PARTY_NOTICES.md with
the Papicons MIT notice and a pointer from both READMEs.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-11-01 20:03:15 +01:00

96 lines
6.7 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>LogStream</title>
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='8' fill='%236366f1'/%3E%3Cpath d='M8 10h16M8 16h11M8 22h14' stroke='white' stroke-width='3' stroke-linecap='round'/%3E%3C/svg%3E">
<link rel="stylesheet" href="style.css">
<script>
// Same saved theme and language as the UI, applied before first paint.
try {
var t = localStorage.getItem('logstream.theme');
if (t === 'light' || t === 'dark') document.documentElement.dataset.theme = t;
var l = localStorage.getItem('logstream.lang');
if (l) document.documentElement.lang = l;
} catch (e) {}
</script>
</head>
<body class="login-page">
<div class="login-tools">
<div class="seg" role="radiogroup" id="langSwitch">
<button type="button" role="radio" data-lang="fr">FR</button>
<button type="button" role="radio" data-lang="en">EN</button>
</div>
<button id="themeBtn" class="icon-btn" type="button" data-i18n-title="theme" data-i18n-aria="theme">
<svg class="pi sun" viewBox="0 0 24 24"><path d="M15 12.8572C15 14.514 13.6569 15.8572 12 15.8572C10.3431 15.8572 9 14.514 9 12.8572C9 11.2003 10.3431 9.85718 12 9.85718C13.6569 9.85718 15 11.2003 15 12.8572Z"/><path d="M11.9994 9.85732C13.6563 9.85732 14.9994 11.2005 14.9994 12.8573C14.9993 14.514 13.6562 15.8573 11.9994 15.8573C10.3429 15.8571 8.99959 14.5139 8.99943 12.8573C8.99943 11.2006 10.3428 9.85754 11.9994 9.85732Z"/><path fill-rule="evenodd" clip-rule="evenodd" d="M12.224 2.71279C13.1976 2.82269 13.8745 3.93342 15.2279 6.1542C17.808 5.82774 19.0985 5.66373 19.7914 6.35635C19.8975 6.46241 19.9909 6.58086 20.0707 6.70791C20.5916 7.53758 20.1456 8.75872 19.2533 11.2011C21.1171 13.0146 22.0492 13.9219 21.9399 14.8954C21.9231 15.0444 21.8898 15.1914 21.8403 15.3329C21.5165 16.2576 20.283 16.6707 17.8168 17.496C17.561 20.084 17.4324 21.3779 16.6029 21.8993C16.4762 21.979 16.3409 22.0442 16.1996 22.0937C15.2748 22.4171 14.1824 21.7104 11.9994 20.2968C9.81658 21.7103 8.72506 22.4171 7.80021 22.0937C7.65874 22.0441 7.52282 21.9791 7.39592 21.8993C6.56669 21.3778 6.43883 20.0837 6.18303 17.496C3.71676 16.6707 2.48338 16.2576 2.15959 15.3329C2.11005 15.1914 2.07674 15.0444 2.05998 14.8954C1.95064 13.9218 2.88271 13.0146 4.7465 11.2011C3.8542 8.75861 3.40816 7.53759 3.92912 6.70791C4.00891 6.58086 4.10231 6.46241 4.20842 6.35635C4.9014 5.66383 6.19193 5.82775 8.77189 6.1542C10.1253 3.93342 10.8022 2.82269 11.7758 2.71279C11.9247 2.696 12.0751 2.69599 12.224 2.71279ZM11.9994 7.85732C9.23819 7.85754 6.99943 10.096 6.99943 12.8573C6.99959 15.6185 9.23829 17.8571 11.9994 17.8573C14.7608 17.8573 16.9993 15.6186 16.9994 12.8573C16.9994 10.0959 14.7609 7.85732 11.9994 7.85732Z"/></svg>
<svg class="pi moon" viewBox="0 0 24 24"><path d="M11 3.85718C11.7891 3.85718 12.0004 5.04248 11.4641 5.62132C10.5555 6.60206 10 7.91462 10 9.35718C10 12.3947 12.4624 14.8572 15.5 14.8572C16.9423 14.8572 18.2542 14.3018 19.2346 13.3934C19.8136 12.8568 20.9996 13.0677 21 13.8572C21.0027 18.8277 16.9706 21.8572 12 21.8572C7.02944 21.8572 3 17.8277 3 12.8572C3 7.88661 6.02944 3.85718 11 3.85718Z"/></svg>
</button>
</div>
<main class="login-card">
<img id="logo" class="login-logo" src="auth/logo" alt="" hidden>
<div class="brand">
<svg viewBox="0 0 32 32" aria-hidden="true"><rect width="32" height="32" rx="8"/><path d="M8 10h16M8 16h11M8 22h14"/></svg>
<span>LogStream</span>
</div>
<p class="muted" data-i18n="intro">Sign in to view the logs.</p>
<form method="post" action="auth/login">
<input type="hidden" name="r" id="ret">
<label for="user" data-i18n="user">User</label>
<input id="user" name="user" type="text" autocomplete="username" autocapitalize="none" spellcheck="false" required autofocus>
<label for="pass" data-i18n="pass">Password</label>
<input id="pass" name="pass" type="password" autocomplete="current-password" required>
<p id="err" class="login-err" role="alert" data-i18n="error" hidden>Wrong user or password.</p>
<button class="btn primary" type="submit" data-i18n="submit">Sign in</button>
</form>
</main>
<script>
(function () {
var I18N = {
en: { intro: 'Sign in to view the logs.', user: 'User', pass: 'Password', submit: 'Sign in',
error: 'Wrong user or password.', theme: 'Light / dark theme' },
fr: { intro: 'Connectez-vous pour consulter les logs.', user: 'Utilisateur', pass: 'Mot de passe',
submit: 'Se connecter', error: 'Utilisateur ou mot de passe incorrect.', theme: 'Thème clair / sombre' },
};
function get(k) { try { return localStorage.getItem('logstream.' + k); } catch (e) { return null; } }
function set(k, v) { try { localStorage.setItem('logstream.' + k, v); } catch (e) {} }
var lang = get('lang');
if (!I18N[lang]) lang = /^fr\b/i.test(navigator.language || '') ? 'fr' : 'en';
function applyLang() {
var d = I18N[lang];
document.documentElement.lang = lang;
document.querySelectorAll('[data-i18n]').forEach(function (el) { el.textContent = d[el.dataset.i18n]; });
document.querySelectorAll('[data-i18n-title]').forEach(function (el) { el.title = d[el.dataset.i18nTitle]; });
document.querySelectorAll('[data-i18n-aria]').forEach(function (el) { el.setAttribute('aria-label', d[el.dataset.i18nAria]); });
document.querySelectorAll('#langSwitch [data-lang]').forEach(function (b) { b.setAttribute('aria-checked', String(b.dataset.lang === lang)); });
}
document.querySelectorAll('#langSwitch [data-lang]').forEach(function (b) {
b.addEventListener('click', function () { lang = b.dataset.lang; set('lang', lang); applyLang(); });
});
document.getElementById('themeBtn').addEventListener('click', function () {
var root = document.documentElement;
var dark = root.dataset.theme ? root.dataset.theme === 'dark' : matchMedia('(prefers-color-scheme: dark)').matches;
root.dataset.theme = dark ? 'light' : 'dark';
set('theme', root.dataset.theme);
});
var q = new URLSearchParams(location.search);
document.getElementById('ret').value = q.get('r') || '/';
document.getElementById('err').hidden = q.get('e') !== '1';
// LOGIN_LOGO: shown only when the server has one.
var logo = document.getElementById('logo');
logo.addEventListener('load', function () { logo.hidden = false; });
if (logo.complete && logo.naturalWidth) logo.hidden = false;
applyLang();
})();
</script>
</body>
</html>