Crank dat!
This commit is contained in:
17 files changed
+6624
No files matched your search
@@ -0,0 +1,264 @@
|
||||
/* ==========================================================================
|
||||
librus-tui frontend - authentication, session chrome, clock
|
||||
========================================================================== */
|
||||
|
||||
"use strict";
|
||||
|
||||
function initClock() {
|
||||
const update = () => {
|
||||
const now = new Date();
|
||||
const dateElement = $("#date");
|
||||
const clockElement = $("#clock");
|
||||
if (dateElement) {
|
||||
dateElement.textContent = now
|
||||
.toLocaleDateString("en-GB", {
|
||||
weekday: "short",
|
||||
day: "2-digit",
|
||||
month: "short",
|
||||
year: "numeric",
|
||||
})
|
||||
.toUpperCase();
|
||||
}
|
||||
if (clockElement) {
|
||||
clockElement.textContent = now.toLocaleTimeString("en-GB", { hour12: false });
|
||||
}
|
||||
};
|
||||
update();
|
||||
setInterval(update, 1000);
|
||||
}
|
||||
|
||||
function updateDemoUI() {
|
||||
const demoButton = $("#demo-btn");
|
||||
if (demoButton) demoButton.classList.toggle("hidden", !state.config.demo);
|
||||
const version = $("#version");
|
||||
if (version) version.textContent = state.config.version || "1.0.0";
|
||||
}
|
||||
|
||||
function setLoginBusy(busy) {
|
||||
const loginInput = $("#login");
|
||||
const passwordInput = $("#password");
|
||||
const loginButton = $("#login-btn");
|
||||
const demoButton = $("#demo-btn");
|
||||
if (loginInput) loginInput.disabled = busy;
|
||||
if (passwordInput) passwordInput.disabled = busy;
|
||||
if (loginButton) {
|
||||
loginButton.disabled = busy;
|
||||
loginButton.textContent = busy ? "[ authenticating… ]" : "[ authenticate ]";
|
||||
}
|
||||
if (demoButton) {
|
||||
demoButton.disabled = busy;
|
||||
demoButton.textContent = busy ? "[ ... ]" : "[ enter demo mode ]";
|
||||
}
|
||||
}
|
||||
|
||||
function showLogin(message) {
|
||||
state.session = null;
|
||||
state.loginBusy = false;
|
||||
state.loadToken += 1;
|
||||
state.messageToken += 1;
|
||||
stopSpinner();
|
||||
stopBootTimer();
|
||||
setLoginBusy(false);
|
||||
|
||||
const loginScreen = $("#login-screen");
|
||||
const appScreen = $("#app-screen");
|
||||
if (loginScreen) loginScreen.classList.remove("hidden");
|
||||
if (appScreen) appScreen.classList.add("hidden");
|
||||
|
||||
const demoBadge = $("#demo-badge");
|
||||
if (demoBadge) demoBadge.classList.add("hidden");
|
||||
const version = $("#version");
|
||||
if (version) version.textContent = state.config.version || "1.0.0";
|
||||
|
||||
setLed("offline");
|
||||
setMeta("");
|
||||
setStatus("ready");
|
||||
const statusRight = $("#status-right");
|
||||
if (statusRight) statusRight.textContent = "synergia://offline";
|
||||
const sideStatus = $("#side-status");
|
||||
if (sideStatus) sideStatus.textContent = "offline";
|
||||
const sideName = $("#side-user-name");
|
||||
if (sideName) sideName.textContent = "disconnected";
|
||||
const sideMeta = $("#side-user-meta");
|
||||
if (sideMeta) sideMeta.textContent = "synergia://";
|
||||
const prompt = $("#prompt");
|
||||
if (prompt) prompt.textContent = "~$";
|
||||
|
||||
closeCommandBar();
|
||||
closeModal();
|
||||
document.title = "librus-tui :: synergia";
|
||||
history.replaceState(null, "", location.pathname);
|
||||
|
||||
const view = $("#view");
|
||||
if (view) view.innerHTML = "";
|
||||
|
||||
if (message) {
|
||||
clearBoot();
|
||||
appendBoot("err", `!! ${message}`);
|
||||
appendBoot("", " enter credentials to reconnect");
|
||||
}
|
||||
|
||||
updateDemoUI();
|
||||
if (state.config.demo) {
|
||||
appendBoot("warn", "[!] demo mode available — no Librus account required");
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
const loginInput = $("#login");
|
||||
if (loginInput) loginInput.focus();
|
||||
}, 40);
|
||||
}
|
||||
|
||||
function enterApp(login, demo) {
|
||||
state.session = { authenticated: true, login: login || "user", demo: !!demo };
|
||||
state.loginBusy = false;
|
||||
setLoginBusy(false);
|
||||
stopBootTimer();
|
||||
stopSpinner();
|
||||
|
||||
const loginScreen = $("#login-screen");
|
||||
const appScreen = $("#app-screen");
|
||||
if (loginScreen) loginScreen.classList.add("hidden");
|
||||
if (appScreen) appScreen.classList.remove("hidden");
|
||||
|
||||
const demoBadge = $("#demo-badge");
|
||||
if (demoBadge) demoBadge.classList.toggle("hidden", !demo);
|
||||
const version = $("#version");
|
||||
if (version) version.textContent = state.config.version || "1.0.0";
|
||||
|
||||
setLed("online");
|
||||
setStatus("[ok] session established", "ok");
|
||||
const sideStatus = $("#side-status");
|
||||
if (sideStatus) sideStatus.textContent = "connected";
|
||||
const sideName = $("#side-user-name");
|
||||
if (sideName) sideName.textContent = login || "connected";
|
||||
const sideMeta = $("#side-user-meta");
|
||||
if (sideMeta) sideMeta.textContent = demo ? "demo dataset" : "synergia://connected";
|
||||
const statusRight = $("#status-right");
|
||||
if (statusRight) statusRight.textContent = demo ? "synergia://demo" : "synergia://connected";
|
||||
const prompt = $("#prompt");
|
||||
if (prompt) prompt.textContent = `${String(login || "user").toLowerCase()}@librus:~$`;
|
||||
|
||||
toast(demo ? "Demo session started." : "Signed in to Synergia.", "ok");
|
||||
|
||||
const requested = location.hash.replace(/^#/, "");
|
||||
const target = VIEWS.includes(requested) ? requested : "dashboard";
|
||||
navigate(target);
|
||||
}
|
||||
|
||||
function applyUserChrome(bootstrap) {
|
||||
const me = (bootstrap && bootstrap.me) || {};
|
||||
const school = (bootstrap && bootstrap.school) || {};
|
||||
const fullName = personName(me) || state.session?.login || "student";
|
||||
const sideName = $("#side-user-name");
|
||||
if (sideName) sideName.textContent = fullName;
|
||||
const sideMeta = $("#side-user-meta");
|
||||
if (sideMeta) {
|
||||
const classPart = me.Class ? `class ${me.Class}` : "student";
|
||||
const schoolPart = school.Name ? ` · ${truncate(school.Name, 30)}` : "";
|
||||
sideMeta.textContent = classPart + schoolPart;
|
||||
}
|
||||
const prompt = $("#prompt");
|
||||
if (prompt) {
|
||||
const shortName = (me.FirstName || state.session?.login || "user").toLowerCase();
|
||||
prompt.textContent = `${shortName}@librus:~$`;
|
||||
}
|
||||
const statusRight = $("#status-right");
|
||||
if (statusRight) {
|
||||
statusRight.textContent = state.session?.demo
|
||||
? "synergia://demo"
|
||||
: "synergia://connected";
|
||||
}
|
||||
}
|
||||
|
||||
async function submitLogin(demo = false) {
|
||||
if (state.loginBusy) return;
|
||||
|
||||
const loginInput = $("#login");
|
||||
const passwordInput = $("#password");
|
||||
const login = demo ? "demo" : (loginInput?.value || "").trim();
|
||||
const password = demo ? "" : passwordInput?.value || "";
|
||||
|
||||
if (!demo && (!login || !password)) {
|
||||
toast("Login and password are required.", "warn");
|
||||
(login ? passwordInput : loginInput)?.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
state.loginBusy = true;
|
||||
setLoginBusy(true);
|
||||
clearBoot();
|
||||
setLed("busy");
|
||||
setStatus("[...] authenticating with synergia");
|
||||
setMeta("oauth handshake…");
|
||||
appendBoot("cmd", " $ librus-tui auth --oauth client_id=46");
|
||||
startBootTimer();
|
||||
|
||||
try {
|
||||
const result = await api("/api/login", {
|
||||
method: "POST",
|
||||
body: demo ? { demo: true } : { login, password },
|
||||
});
|
||||
|
||||
stopBootTimer();
|
||||
appendBoot("ok", "[ ok ] credentials accepted");
|
||||
appendBoot("info", " session established · cookies held server-side");
|
||||
await sleep(420);
|
||||
appendBoot("cmd", " launching interface …");
|
||||
|
||||
if (passwordInput) passwordInput.value = "";
|
||||
state.session = {
|
||||
authenticated: true,
|
||||
login: demo ? "demo" : login,
|
||||
demo: !!(result && result.demo),
|
||||
};
|
||||
await sleep(240);
|
||||
enterApp(demo ? "demo" : login, !!(result && result.demo));
|
||||
} catch (error) {
|
||||
stopBootTimer();
|
||||
appendBoot("err", `[fail] ${error.message}`);
|
||||
setLed("offline");
|
||||
setStatus("authentication failed", "error");
|
||||
setMeta("login failed");
|
||||
toast(error.message, "err", 7000);
|
||||
state.loginBusy = false;
|
||||
setLoginBusy(false);
|
||||
passwordInput?.focus();
|
||||
}
|
||||
}
|
||||
|
||||
function handleExpiredSession() {
|
||||
if (!state.session) return;
|
||||
showLogin("Session expired or revoked by Synergia.");
|
||||
toast("Your session expired. Sign in again.", "warn", 6000);
|
||||
}
|
||||
|
||||
function confirmLogout() {
|
||||
openModal(
|
||||
"logout",
|
||||
`<p>Close the current Synergia session?</p>
|
||||
<p class="dim">Server-side cookies are discarded immediately. Nothing is persisted.</p>`,
|
||||
[
|
||||
{ label: "[ logout ]", action: "logout-confirm", kind: "btn-warn" },
|
||||
{ label: "[ cancel ]", close: true },
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
async function doLogout() {
|
||||
closeModal();
|
||||
const wasLoggedIn = !!(state.session && state.session.authenticated);
|
||||
if (wasLoggedIn) {
|
||||
try {
|
||||
await api("/api/logout", { method: "POST" });
|
||||
} catch (error) {
|
||||
/* the local session is dropped regardless */
|
||||
}
|
||||
}
|
||||
state.session = null;
|
||||
state.messages = [];
|
||||
state.messageIndex = -1;
|
||||
state.messageCache.clear();
|
||||
showLogin("session closed.");
|
||||
toast("Logged out.", "ok");
|
||||
}
|
||||
Reference in new issue
Block a user