feat(ui): image tabblad uitgebreid met Dockerfile selectie en tag suggestie
This commit is contained in:
@@ -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) {
|
||||
<td>${repoTag}</td>
|
||||
<td>${shortId}</td>
|
||||
<td>${sizeMB} MB</td>
|
||||
<td>${created}</td>
|
||||
<td>${containers}</td>
|
||||
<td>${status}</td>
|
||||
<td>
|
||||
@@ -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 = `<div class="muted">Geen matches.</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
// Render as clickable buttons
|
||||
listEl.innerHTML = filtered.map(p => {
|
||||
const safe = p.replace(/"/g, """);
|
||||
return `
|
||||
<div style="display:flex; align-items:center; justify-content:space-between; gap:10px; padding:6px 0; border-bottom:1px dashed rgba(36,52,95,.35);">
|
||||
<span>${safe}</span>
|
||||
<button class="btn small ok" type="button" onclick="chooseDockerfilePath('${encodeURIComponent(p)}')">Kies</button>
|
||||
</div>
|
||||
`;
|
||||
}).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;
|
||||
}
|
||||
Reference in New Issue
Block a user