Login
This commit is contained in:
108
static/app.js
108
static/app.js
@@ -6,6 +6,7 @@ import { ThreeMFLoader } from "three/addons/loaders/3MFLoader.js";
|
||||
const view = document.getElementById("view");
|
||||
const crumbs = document.getElementById("crumbs");
|
||||
const homeLink = document.getElementById("home-link");
|
||||
const logoutBtn = document.getElementById("logout-btn");
|
||||
|
||||
const stlLoader = new STLLoader();
|
||||
const mfLoader = new ThreeMFLoader();
|
||||
@@ -37,6 +38,10 @@ function navigateToProject(name) {
|
||||
|
||||
async function api(path, options = {}) {
|
||||
const res = await fetch(path, options);
|
||||
if (res.status === 401) {
|
||||
window.location.href = "/login.html";
|
||||
throw new Error("Nicht angemeldet");
|
||||
}
|
||||
if (!res.ok) {
|
||||
let detail = res.statusText;
|
||||
try {
|
||||
@@ -75,9 +80,6 @@ async function renderDashboard() {
|
||||
<h1 class="view-title">Projekte</h1>
|
||||
<div class="view-subtitle">Lege ein Projekt an, um STL- und 3MF-Dateien zu verwalten.</div>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<a class="btn" href="/api/download-all" download>Alle Projekte herunterladen</a>
|
||||
</div>
|
||||
`;
|
||||
view.appendChild(header);
|
||||
|
||||
@@ -225,33 +227,102 @@ async function renderProject(name) {
|
||||
await loadFileGrid(name, gridContainer);
|
||||
}
|
||||
|
||||
function createProgressBar(afterNode, label) {
|
||||
const wrap = document.createElement("div");
|
||||
wrap.className = "upload-progress";
|
||||
wrap.innerHTML = `
|
||||
<div class="upload-progress-row">
|
||||
<span class="upload-progress-name"></span>
|
||||
<div class="upload-progress-track"><div class="upload-progress-fill"></div></div>
|
||||
<span class="upload-progress-pct">0%</span>
|
||||
</div>
|
||||
`;
|
||||
wrap.querySelector(".upload-progress-name").textContent = label;
|
||||
afterNode.insertAdjacentElement("afterend", wrap);
|
||||
|
||||
const fill = wrap.querySelector(".upload-progress-fill");
|
||||
const pct = wrap.querySelector(".upload-progress-pct");
|
||||
|
||||
return {
|
||||
update(percent) {
|
||||
fill.style.width = `${percent}%`;
|
||||
pct.textContent = `${percent}%`;
|
||||
},
|
||||
done() {
|
||||
fill.style.width = "100%";
|
||||
fill.classList.add("is-done");
|
||||
pct.textContent = "100%";
|
||||
},
|
||||
remove() {
|
||||
wrap.remove();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function uploadFiles(projectName, fileList) {
|
||||
const formData = new FormData();
|
||||
let hasValid = false;
|
||||
const names = [];
|
||||
for (const file of fileList) {
|
||||
const ext = "." + file.name.split(".").pop().toLowerCase();
|
||||
if (ext === ".stl" || ext === ".3mf") {
|
||||
formData.append("files", file);
|
||||
hasValid = true;
|
||||
names.push(file.name);
|
||||
}
|
||||
}
|
||||
if (!hasValid) {
|
||||
if (!names.length) {
|
||||
showToast("Nur .stl und .3mf Dateien werden unterstützt.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
const dropzone = document.querySelector(".dropzone");
|
||||
const label = names.length === 1 ? names[0] : `${names.length} Dateien`;
|
||||
const progress = createProgressBar(dropzone, label);
|
||||
|
||||
try {
|
||||
const result = await api(`/api/projects/${encodeURIComponent(projectName)}/upload`, {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
});
|
||||
const result = await uploadWithProgress(projectName, formData, (percent) => progress.update(percent));
|
||||
progress.done();
|
||||
showToast(`${result.saved.length} Datei(en) hochgeladen`);
|
||||
setTimeout(() => progress.remove(), 600);
|
||||
const container = document.getElementById("file-grid-container");
|
||||
if (container) await loadFileGrid(projectName, container);
|
||||
} catch (err) {
|
||||
progress.remove();
|
||||
showToast("Upload fehlgeschlagen: " + err.message, true);
|
||||
}
|
||||
}
|
||||
|
||||
function uploadWithProgress(projectName, formData, onProgress) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", `/api/projects/${encodeURIComponent(projectName)}/upload`);
|
||||
|
||||
xhr.upload.addEventListener("progress", (e) => {
|
||||
if (e.lengthComputable) {
|
||||
onProgress(Math.round((e.loaded / e.total) * 100));
|
||||
}
|
||||
});
|
||||
|
||||
xhr.addEventListener("load", () => {
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
try {
|
||||
resolve(JSON.parse(xhr.responseText));
|
||||
} catch (err) {
|
||||
reject(new Error("Ungültige Antwort vom Server"));
|
||||
}
|
||||
} else {
|
||||
let message = xhr.statusText;
|
||||
try {
|
||||
message = JSON.parse(xhr.responseText).detail || message;
|
||||
} catch (_) {}
|
||||
reject(new Error(message));
|
||||
}
|
||||
});
|
||||
|
||||
xhr.addEventListener("error", () => reject(new Error("Netzwerkfehler")));
|
||||
xhr.send(formData);
|
||||
});
|
||||
}
|
||||
|
||||
async function loadFileGrid(projectName, container) {
|
||||
disposeViewers();
|
||||
container.innerHTML = "";
|
||||
@@ -436,4 +507,19 @@ function disposeViewers() {
|
||||
|
||||
// ---------- Init ----------
|
||||
|
||||
renderDashboard();
|
||||
logoutBtn.addEventListener("click", async () => {
|
||||
try {
|
||||
await fetch("/api/logout", { method: "POST" });
|
||||
} finally {
|
||||
window.location.href = "/login.html";
|
||||
}
|
||||
});
|
||||
|
||||
(async function init() {
|
||||
try {
|
||||
await api("/api/me");
|
||||
renderDashboard();
|
||||
} catch (err) {
|
||||
// api() already redirects to /login.html on 401
|
||||
}
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user