mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-13 04:40:44 +08:00
120 lines
4 KiB
JavaScript
120 lines
4 KiB
JavaScript
import { state, t, el, mount, applyI18nStatic, apiPost, authStoreSet, AUTH_TOKEN_KEY, AUTH_SESSION_KEY, navigateAdmin } from "../core.js";
|
|
|
|
/** Survive login remounts (reauth races) so typing is not wiped. */
|
|
const loginDraft = { username: "", password: "" };
|
|
|
|
async function renderLogin() {
|
|
applyI18nStatic();
|
|
const username = el("input", {
|
|
class: "input",
|
|
placeholder: t("auth.username"),
|
|
autocomplete: "username",
|
|
spellcheck: "false",
|
|
});
|
|
username.value = loginDraft.username;
|
|
const userHint = el("div", { class: "muted", text: t("auth.consoleUsernameHint") });
|
|
const password = el("input", {
|
|
class: "input",
|
|
type: "password",
|
|
placeholder: t("auth.password"),
|
|
autocomplete: "current-password",
|
|
});
|
|
password.value = loginDraft.password;
|
|
const status = el("div", { class: "muted", text: "" });
|
|
const btn = el("button", { class: "btn btn--primary", type: "button", text: t("auth.login") });
|
|
let busy = false;
|
|
|
|
const syncDraft = () => {
|
|
loginDraft.username = String(username.value || "");
|
|
loginDraft.password = String(password.value || "");
|
|
};
|
|
username.addEventListener("input", syncDraft);
|
|
password.addEventListener("input", syncDraft);
|
|
|
|
const setBusy = (on) => {
|
|
busy = !!on;
|
|
btn.disabled = busy;
|
|
btn.textContent = busy ? t("auth.loggingIn") : t("auth.login");
|
|
};
|
|
|
|
const doLogin = async () => {
|
|
if (busy) return;
|
|
syncDraft();
|
|
setBusy(true);
|
|
status.textContent = t("auth.loggingIn");
|
|
try {
|
|
const resp = await apiPost("/admin/api/auth/login", {
|
|
tenant_id: "",
|
|
username: username.value.trim(),
|
|
password: password.value.trim(),
|
|
purpose: "console",
|
|
});
|
|
if (!resp || !resp.ok || !resp.token) {
|
|
const err = String((resp && resp.error) || "");
|
|
status.textContent = err === "user_disabled" ? t("auth.disabled") : t("auth.invalid");
|
|
setBusy(false);
|
|
return;
|
|
}
|
|
loginDraft.username = "";
|
|
loginDraft.password = "";
|
|
const newTok = String(resp.token || "").trim();
|
|
authStoreSet(AUTH_TOKEN_KEY, newTok);
|
|
authStoreSet(AUTH_SESSION_KEY, JSON.stringify(resp.session || {}));
|
|
state.authSession = resp.session || null;
|
|
// Leave the login form immediately so the click feels responsive.
|
|
mount(
|
|
el("div", { class: "card" }, [
|
|
el("div", { class: "card__title", text: t("title.stack") }),
|
|
el("div", { class: "muted", text: t("chat.loading") }),
|
|
]),
|
|
);
|
|
await new Promise((resolve) => requestAnimationFrame(() => resolve()));
|
|
await navigateAdmin();
|
|
} catch (err) {
|
|
mount(
|
|
el("div", { class: "card" }, [
|
|
el("div", { class: "card__title", text: t("auth.login") }),
|
|
el("div", { class: "muted", text: String((err && err.message) || err || t("auth.invalid")) }),
|
|
el("div", { class: "row" }, [
|
|
el("button", {
|
|
class: "btn btn--primary",
|
|
type: "button",
|
|
text: t("auth.login"),
|
|
onclick: () => navigateAdmin(),
|
|
}),
|
|
]),
|
|
]),
|
|
);
|
|
}
|
|
};
|
|
|
|
const onEnterLogin = (ev) => {
|
|
if (ev.key !== "Enter") return;
|
|
ev.preventDefault();
|
|
doLogin();
|
|
};
|
|
username.addEventListener("keydown", onEnterLogin);
|
|
password.addEventListener("keydown", onEnterLogin);
|
|
btn.addEventListener("click", (ev) => {
|
|
ev.preventDefault();
|
|
doLogin();
|
|
});
|
|
// Focus after mount so users can type immediately (keep caret if already typing).
|
|
setTimeout(() => {
|
|
try {
|
|
if (document.activeElement === username || document.activeElement === password) return;
|
|
if (loginDraft.username && !loginDraft.password) password.focus();
|
|
else username.focus();
|
|
} catch (_) {}
|
|
}, 0);
|
|
return el("div", { class: "card", "data-admin-login": "1" }, [
|
|
el("div", { class: "card__title", text: t("auth.login") }),
|
|
el("div", { class: "row" }, [username]),
|
|
userHint,
|
|
el("div", { class: "row" }, [password]),
|
|
el("div", { class: "row" }, [btn]),
|
|
status,
|
|
]);
|
|
}
|
|
|
|
export { renderLogin };
|