From d28633a22d025f4c93683b31e481473d28eba819 Mon Sep 17 00:00:00 2001 From: kodi Date: Sat, 21 Feb 2026 14:09:13 +0100 Subject: [PATCH] feat(ui): image tabblad uitgebreid met Dockerfile selectie en tag suggestie --- webui/html/assets/js/tabs/images.js | 222 ++++++++++++++++++++++++++++ webui/html/index.html | 79 ++++++++++ 2 files changed, 301 insertions(+) diff --git a/webui/html/assets/js/tabs/images.js b/webui/html/assets/js/tabs/images.js index 382ea59..ba124c9 100644 --- a/webui/html/assets/js/tabs/images.js +++ b/webui/html/assets/js/tabs/images.js @@ -17,6 +17,7 @@ function renderImages(images) { 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; @@ -33,6 +34,7 @@ function renderImages(images) { ${repoTag} ${shortId} ${sizeMB} MB + ${created} ${containers} ${status} @@ -89,4 +91,224 @@ async function pruneUnusedImages() { }); 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; } \ No newline at end of file diff --git a/webui/html/index.html b/webui/html/index.html index 2f9249e..ce21eb5 100644 --- a/webui/html/index.html +++ b/webui/html/index.html @@ -153,6 +153,7 @@ +
@@ -165,6 +166,7 @@ Repo / Tag ID Size + Created Containers Status Acties @@ -231,6 +233,83 @@
+ + + + + +