async function loadImages() { const res = await fetch("/api/images"); const images = await res.json(); renderImages(images); } function renderImages(images) { const tbody = document.getElementById("images-tbody"); tbody.innerHTML = ""; images.forEach(img => { const tr = document.createElement("tr"); const repoTag = (img.RepoTags && img.RepoTags.length > 0) ? img.RepoTags[0] : ""; const shortId = img.Id.substring(0, 12); const sizeMB = (img.Size / 1024 / 1024).toFixed(1); const created = img.Created ? new Date(img.Created * 1000).toLocaleString() : "-"; const containers = img.Containers || 0; const fullId = img.Id; const status = containers > 0 ? `In use` : `Unused`; const disabled = containers > 0 ? "disabled" : ""; tr.innerHTML = ` ${repoTag} ${shortId} ${sizeMB} MB ${created} ${containers} ${status} `; tbody.appendChild(tr); }); } function toggleSelectAllImages(master) { document.querySelectorAll(".image-checkbox:not(:disabled)") .forEach(cb => cb.checked = master.checked); } async function removeSingleImage(id) { if (!confirm("Image verwijderen?")) return; await fetch("/api/images/" + encodeURIComponent(id), { method: "DELETE" }); await loadImages(); } async function removeSelectedImages() { const selected = Array.from(document.querySelectorAll(".image-checkbox:checked")) .map(cb => cb.value); if (!selected.length) { alert("Geen images geselecteerd."); return; } if (!confirm("Geselecteerde images verwijderen?")) return; await fetch("/api/images/remove", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ images: selected }) }); await loadImages(); } async function pruneUnusedImages() { if (!confirm("Alle unused images verwijderen?")) return; await fetch("/api/images/prune?all=true", { method: "POST" }); await loadImages(); } // ---------- Build Modal ---------- function openBuildModal() { document.getElementById("buildModalBack").style.display = "flex"; document.getElementById("buildOutput").value = ""; const ctxEl = document.getElementById("buildContext"); const tagEl = document.getElementById("buildTag"); // Reset auto-mode tagEl.dataset.auto = "1"; // Update tag whenever context changes ctxEl.oninput = () => { if (tagEl.dataset.auto === "1") { const suggestion = suggestTagFromContext(ctxEl.value); tagEl.value = suggestion; } }; // If user types manually → stop auto mode tagEl.oninput = () => { tagEl.dataset.auto = "0"; }; } function hideBuildModal() { document.getElementById("buildModalBack").style.display = "none"; } function closeBuildModal(e) { if (e.target.id === "buildModalBack") hideBuildModal(); } async function buildImage() { const context = document.getElementById("buildContext").value.trim(); const dockerfile = document.getElementById("buildDockerfile").value.trim(); const tag = document.getElementById("buildTag").value.trim(); const pull = document.getElementById("buildPull").checked; const nocache = document.getElementById("buildNoCache").checked; const outputBox = document.getElementById("buildOutput"); if (!context || !dockerfile || !tag) { alert("Vul context_dir, dockerfile en tag in."); return; } if (!ensureSystemdContextOrAlert(context)) { return; } outputBox.value = "Starting build...\n"; try { const res = await fetch("/api/images/build", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ context_dir: context, dockerfile: dockerfile, tag: tag, pull: pull, nocache: nocache }) }); const data = await res.json(); if (!res.ok) { outputBox.value += "\nERROR:\n" + JSON.stringify(data, null, 2); return; } outputBox.value += data.output || "Build completed."; await loadImages(); } catch (err) { outputBox.value += "\nERROR:\n" + err.message; } } // ---------- Dockerfile picker ---------- function openDockerfilePicker() { document.getElementById("dfPickerBack").style.display = "flex"; const search = document.getElementById("dfPickerSearch"); if (search) { search.value = ""; search.oninput = () => renderDockerfilePickerList(window.__dfPickerAll || []); } refreshDockerfilePicker(); } function hideDockerfilePicker() { document.getElementById("dfPickerBack").style.display = "none"; } function closeDockerfilePicker(e) { if (e.target.id === "dfPickerBack") hideDockerfilePicker(); } async function refreshDockerfilePicker() { const listEl = document.getElementById("dfPickerList"); listEl.textContent = "Laden..."; try { const res = await fetch("/api/files/tree"); const tree = await res.json(); // [{path:"systemd/..", files:[...]}] const candidates = []; for (const folder of (tree || [])) { const folderPath = (folder.path || "").replace(/^\/+/, ""); // e.g. systemd/foo if (!folderPath || !(folderPath === "systemd" || folderPath.startsWith("systemd/"))) continue; const files = folder.files || []; for (const f of files) { if (!isDockerfileName(f)) continue; // full path under workloads-root (without leading slash) const full = folderPath === "systemd" ? `systemd/${f}` : `${folderPath}/${f}`; candidates.push(full); } } // sort nice candidates.sort((a, b) => a.localeCompare(b)); window.__dfPickerAll = candidates; renderDockerfilePickerList(candidates); } catch (e) { listEl.textContent = "Fout bij laden: " + (e.message || e); } } function isDockerfileName(name) { const n = String(name || "").toLowerCase(); if (n === "dockerfile" || n === "containerfile") return true; if (n.endsWith(".dockerfile") || n.endsWith(".containerfile")) return true; return false; } function renderDockerfilePickerList(all) { const listEl = document.getElementById("dfPickerList"); const q = (document.getElementById("dfPickerSearch")?.value || "").trim().toLowerCase(); const filtered = (all || []).filter(p => !q || p.toLowerCase().includes(q)); if (!filtered.length) { listEl.innerHTML = `
Geen matches.
`; return; } // Render as clickable buttons listEl.innerHTML = filtered.map(p => { const safe = p.replace(/"/g, """); return `
${safe}
`; }).join(""); } function chooseDockerfilePath(encodedPath) { const fullPath = decodeURIComponent(encodedPath); const idx = fullPath.lastIndexOf("/"); const contextDir = idx > 0 ? fullPath.substring(0, idx) : "systemd"; const dockerfile = idx > 0 ? fullPath.substring(idx + 1) : fullPath; const ctxEl = document.getElementById("buildContext"); const tagEl = document.getElementById("buildTag"); ctxEl.value = contextDir; document.getElementById("buildDockerfile").value = dockerfile; if (tagEl.dataset.auto !== "0") { const suggestion = suggestTagFromContext(contextDir); tagEl.value = suggestion; tagEl.dataset.auto = "1"; } hideDockerfilePicker(); } // ---------- Build helpers (4.3c) ---------- function suggestTagFromContext(contextDir) { const p = String(contextDir || "").trim().replace(/^\/+/, ""); if (!p.startsWith("systemd/")) return ""; const parts = p.split("/").filter(Boolean); // Als alleen "systemd" of "systemd/" → geen geldige image naam if (parts.length <= 1) return ""; const name = parts[parts.length - 1]; const safe = name .toLowerCase() .replace(/[^a-z0-9._-]+/g, "-") .replace(/-+/g, "-") .replace(/^-|-$/g, ""); return safe ? `localhost/${safe}:latest` : ""; } function ensureSystemdContextOrAlert(contextDir) { const p = String(contextDir || "").trim().replace(/^\/+/, ""); if (!p.startsWith("systemd/")) { alert("Context directory moet beginnen met: systemd/\nVoorbeeld: systemd/buildtests/hello"); return false; } return true; }