You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
183 lines
5.2 KiB
183 lines
5.2 KiB
const LOGIN_KEY = "iron_access_token";
|
|
|
|
const loginCard = document.querySelector("#login-card");
|
|
const appCard = document.querySelector("#app-card");
|
|
const loginForm = document.querySelector("#login-form");
|
|
const loginError = document.querySelector("#login-error");
|
|
const appError = document.querySelector("#app-error");
|
|
const usernameInput = document.querySelector("#username");
|
|
const passwordInput = document.querySelector("#password");
|
|
const welcomeTitle = document.querySelector("#welcome-title");
|
|
const sessionUser = document.querySelector("#session-user");
|
|
const rootName = document.querySelector("#root-name");
|
|
const itemsCount = document.querySelector("#items-count");
|
|
const rootItems = document.querySelector("#root-items");
|
|
const backendCount = document.querySelector("#backend-count");
|
|
const backendsList = document.querySelector("#backends-list");
|
|
const refreshButton = document.querySelector("#refresh-button");
|
|
const logoutButton = document.querySelector("#logout-button");
|
|
|
|
function getToken() {
|
|
return window.localStorage.getItem(LOGIN_KEY);
|
|
}
|
|
|
|
function setToken(token) {
|
|
window.localStorage.setItem(LOGIN_KEY, token);
|
|
}
|
|
|
|
function clearToken() {
|
|
window.localStorage.removeItem(LOGIN_KEY);
|
|
}
|
|
|
|
async function apiFetch(path, options = {}) {
|
|
const headers = new Headers(options.headers || {});
|
|
const token = getToken();
|
|
if (token) {
|
|
headers.set("Authorization", `Bearer ${token}`);
|
|
}
|
|
|
|
const response = await fetch(path, { ...options, headers });
|
|
if (!response.ok) {
|
|
let message = `Request failed with status ${response.status}`;
|
|
try {
|
|
const payload = await response.json();
|
|
message = payload.error?.message || message;
|
|
} catch {
|
|
// keep fallback message
|
|
}
|
|
throw new Error(message);
|
|
}
|
|
return response;
|
|
}
|
|
|
|
function renderList(container, items, renderItem) {
|
|
container.innerHTML = "";
|
|
if (items.length === 0) {
|
|
const empty = document.createElement("li");
|
|
empty.textContent = "Nothing here yet.";
|
|
container.append(empty);
|
|
return;
|
|
}
|
|
for (const item of items) {
|
|
container.append(renderItem(item));
|
|
}
|
|
}
|
|
|
|
function renderRootItem(item) {
|
|
const element = document.createElement("li");
|
|
const left = document.createElement("div");
|
|
const right = document.createElement("div");
|
|
right.className = "item-meta";
|
|
|
|
left.innerHTML = `<strong>${item.name}</strong>`;
|
|
if (item.kind === "file" && item.mime_type) {
|
|
right.textContent = item.mime_type;
|
|
} else {
|
|
right.textContent = item.kind;
|
|
}
|
|
|
|
element.append(left, right);
|
|
return element;
|
|
}
|
|
|
|
function renderBackendItem(item) {
|
|
const element = document.createElement("li");
|
|
const left = document.createElement("div");
|
|
left.innerHTML = `<strong>${item.name}</strong>`;
|
|
const right = document.createElement("div");
|
|
right.className = "item-meta";
|
|
right.textContent = `${item.type} · ${item.is_enabled ? "enabled" : "disabled"}`;
|
|
element.append(left, right);
|
|
return element;
|
|
}
|
|
|
|
function showLogin(errorMessage = "") {
|
|
loginCard.hidden = false;
|
|
appCard.hidden = true;
|
|
loginError.hidden = !errorMessage;
|
|
loginError.textContent = errorMessage;
|
|
}
|
|
|
|
function showApp() {
|
|
loginCard.hidden = true;
|
|
appCard.hidden = false;
|
|
appError.hidden = true;
|
|
}
|
|
|
|
async function loadWorkspace() {
|
|
try {
|
|
const [meResponse, directoryResponse, backendResponse] = await Promise.all([
|
|
apiFetch("/api/auth/me"),
|
|
apiFetch("/api/directories/dir_root/children"),
|
|
apiFetch("/api/backends"),
|
|
]);
|
|
|
|
const me = await meResponse.json();
|
|
const directory = await directoryResponse.json();
|
|
const backends = await backendResponse.json();
|
|
|
|
welcomeTitle.textContent = `Welcome, ${me.user.username}`;
|
|
sessionUser.textContent = me.user.username;
|
|
rootName.textContent = directory.directory.name;
|
|
itemsCount.textContent = `${directory.items.length} items`;
|
|
backendCount.textContent = String(backends.items.length);
|
|
|
|
renderList(rootItems, directory.items, renderRootItem);
|
|
renderList(backendsList, backends.items, renderBackendItem);
|
|
showApp();
|
|
} catch (error) {
|
|
if (String(error.message).includes("Authentication")) {
|
|
clearToken();
|
|
showLogin("Your session expired. Please sign in again.");
|
|
return;
|
|
}
|
|
appError.hidden = false;
|
|
appError.textContent = error.message;
|
|
}
|
|
}
|
|
|
|
loginForm.addEventListener("submit", async (event) => {
|
|
event.preventDefault();
|
|
loginError.hidden = true;
|
|
|
|
try {
|
|
const response = await fetch("/api/auth/login", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({
|
|
username: usernameInput.value,
|
|
password: passwordInput.value,
|
|
}),
|
|
});
|
|
const payload = await response.json();
|
|
if (!response.ok) {
|
|
throw new Error(payload.error?.message || "Unable to sign in.");
|
|
}
|
|
|
|
setToken(payload.access_token);
|
|
passwordInput.value = "";
|
|
await loadWorkspace();
|
|
} catch (error) {
|
|
showLogin(error.message);
|
|
}
|
|
});
|
|
|
|
refreshButton.addEventListener("click", async () => {
|
|
await loadWorkspace();
|
|
});
|
|
|
|
logoutButton.addEventListener("click", async () => {
|
|
try {
|
|
await apiFetch("/api/auth/logout", { method: "POST" });
|
|
} catch {
|
|
// best-effort logout
|
|
}
|
|
clearToken();
|
|
showLogin();
|
|
});
|
|
|
|
if (getToken()) {
|
|
void loadWorkspace();
|
|
} else {
|
|
showLogin();
|
|
}
|
|
|