1
0
mirror of https://github.com/coalaura/whiskr.git synced 2025-09-08 00:29:54 +00:00

authentication

This commit is contained in:
Laura
2025-08-16 17:18:48 +02:00
parent a138378f19
commit e10c3dce3f
7 changed files with 357 additions and 18 deletions

View File

@@ -605,6 +605,7 @@ select {
}
body.loading #version,
.modal.loading .content::after,
.reasoning .toggle::before,
.reasoning .toggle::after,
#bottom,
@@ -801,6 +802,129 @@ label[for="reasoning-tokens"] {
background-image: url(icons/stop.svg);
}
.modal .background,
.modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 20;
}
.modal:not(.open) {
display: none;
}
.modal .background {
background: rgba(24, 25, 38, 0.25);
backdrop-filter: blur(10px);
}
.modal .content,
.modal .body {
display: flex;
flex-direction: column;
gap: 6px;
overflow: hidden;
}
.modal .content {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
gap: 14px;
background: #24273a;
padding: 18px 20px;
border-radius: 6px;
box-shadow: 0px 0px 15px 5px rgba(0, 0, 0, 0.1);
z-index: 30;
}
.modal .header {
background: #363a4f;
height: 42px;
margin: -18px -20px;
padding: 10px 20px;
margin-bottom: 0;
font-weight: 500;
font-size: 18px;
}
.modal.loading .content::after,
.modal.loading .content::before {
content: "";
position: absolute;
top: 42px;
left: 0;
z-index: 10;
}
.modal.loading .content::before {
right: 0;
bottom: 0;
backdrop-filter: blur(4px);
}
.modal.loading .content::after {
top: 85px;
left: 50%;
transform: translateX(-50%);
animation: rotating 1.2s linear infinite;
background-image: url(icons/spinner.svg);
}
.modal .error {
background: #ed8796;
font-weight: 500;
font-style: italic;
margin-bottom: 6px;
font-size: 14px;
color: #11111b;
padding: 5px 10px;
border-radius: 2px;
}
.modal:not(.errored) .error {
display: none;
}
.modal.errored input {
border: 1px solid #ed8796;
}
.modal .form-group {
display: flex;
gap: 6px;
}
.modal .form-group label {
width: 80px;
}
.modal .form-group input {
padding: 4px 6px;
}
.modal .buttons {
display: flex;
justify-content: end;
}
#login {
background: #a6da95;
color: #11111b;
padding: 4px 10px;
border-radius: 2px;
font-weight: 500;
transition: 150ms;
}
#login:hover {
background: #89bb77;
}
@keyframes rotating {
from {
transform: rotate(0deg);

View File

@@ -85,6 +85,28 @@
</div>
</div>
<div id="authentication" class="modal">
<div class="background"></div>
<div class="content">
<div class="header">Authentication</div>
<div class="body">
<div id="auth-error" class="error"></div>
<div class="form-group">
<label for="username">Username</label>
<input type="text" name="username" id="username" placeholder="admin" />
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" name="password" id="password" />
</div>
</div>
<div class="buttons">
<button id="login">Login</button>
</div>
</div>
</div>
<script src="lib/highlight.min.js"></script>
<script src="lib/marked.min.js"></script>
<script src="lib/morphdom.min.js"></script>

View File

@@ -17,12 +17,19 @@
$scrolling = document.getElementById("scrolling"),
$export = document.getElementById("export"),
$import = document.getElementById("import"),
$clear = document.getElementById("clear");
$clear = document.getElementById("clear"),
$authentication = document.getElementById("authentication"),
$authError = document.getElementById("auth-error"),
$username = document.getElementById("username"),
$password = document.getElementById("password"),
$login = document.getElementById("login");
const messages = [],
models = {},
modelList = [];
let authToken;
let autoScrolling = false,
searchAvailable = false,
jsonMode = false,
@@ -879,6 +886,30 @@
);
}
async function login() {
const username = $username.value.trim(),
password = $password.value.trim();
if (!username || !password) {
throw new Error("missing username or password");
}
const data = await fetch("/-/auth", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
username: username,
password: password,
}),
}).then((response) => response.json());
if (!data?.authenticated) {
throw new Error(data.error || "authentication failed");
}
}
async function loadData() {
const data = await json("/-/data");
@@ -898,6 +929,11 @@
// update search availability
searchAvailable = data.search;
// show login modal
if (data.authentication && !data.authenticated) {
$authentication.classList.add("open");
}
// render models
$model.innerHTML = "";
@@ -924,7 +960,7 @@
function clearMessages() {
while (messages.length) {
console.log("delete", messages.length)
console.log("delete", messages.length);
messages[0].delete();
}
}
@@ -1169,6 +1205,32 @@
generate(true);
});
$login.addEventListener("click", async () => {
$authentication.classList.remove("errored");
$authentication.classList.add("loading");
try {
await login();
$authentication.classList.remove("open");
} catch(err) {
$authError.textContent =`Error: ${err.message}`;
$authentication.classList.add("errored");
$password.value = "";
}
$authentication.classList.remove("loading");
});
$username.addEventListener("input", () => {
$authentication.classList.remove("errored");
});
$password.addEventListener("input", () => {
$authentication.classList.remove("errored");
});
$message.addEventListener("keydown", (event) => {
if (!event.ctrlKey || event.key !== "Enter") {
return;