From 196b13c2fa6fdea9b60ce990fe284236bedeec43 Mon Sep 17 00:00:00 2001 From: nearxos Date: Sun, 22 Feb 2026 17:18:50 +0200 Subject: [PATCH] Add update functionality for cloud-init templates in the dashboard Implement a new API endpoint to update existing cloud-init templates, allowing users to modify template attributes such as name, user_data, meta_data, and network_config. Enhance the dashboard UI to include an update button for templates, along with associated JavaScript for handling update requests. This improves user experience by enabling direct template modifications from the interface. --- emmc-provisioning/dashboard/app.py | 26 +++++++++++++++++++ .../dashboard/templates/cloudinit_build.html | 17 +++++++++++- .../dashboard/templates/index.html | 21 ++++++++++++++- 3 files changed, 62 insertions(+), 2 deletions(-) diff --git a/emmc-provisioning/dashboard/app.py b/emmc-provisioning/dashboard/app.py index 8f6f06b..4110a05 100644 --- a/emmc-provisioning/dashboard/app.py +++ b/emmc-provisioning/dashboard/app.py @@ -1663,6 +1663,32 @@ def api_cloudinit_templates_get(tid): return jsonify({"error": "not found"}), 404 +@app.route("/api/cloudinit-templates/", methods=["PUT"]) +@require_admin +def api_cloudinit_templates_update(tid): + """Update an existing template (name, user_data, meta_data, network_config). Id unchanged.""" + body = request.get_json(force=True, silent=True) or {} + data = _load_cloudinit_templates() + templates = data.get("templates", []) + for i, t in enumerate(templates): + if t.get("id") == tid: + name = (body.get("name") or t.get("name") or "").strip() + if not name: + return jsonify({"ok": False, "error": "name required"}), 400 + templates[i] = { + "id": tid, + "name": name, + "user_data": body.get("user_data", t.get("user_data", "")), + "meta_data": body.get("meta_data", t.get("meta_data", "")), + "network_config": body.get("network_config", t.get("network_config", "")), + } + data["templates"] = templates + if not _save_cloudinit_templates(data): + return jsonify({"ok": False, "error": "Failed to save"}), 500 + return jsonify({"ok": True, "id": tid, "name": name}) + return jsonify({"ok": False, "error": "not found"}), 404 + + @app.route("/api/cloudinit-templates/", methods=["DELETE"]) @require_admin def api_cloudinit_templates_delete(tid): diff --git a/emmc-provisioning/dashboard/templates/cloudinit_build.html b/emmc-provisioning/dashboard/templates/cloudinit_build.html index dd44c35..767e48f 100644 --- a/emmc-provisioning/dashboard/templates/cloudinit_build.html +++ b/emmc-provisioning/dashboard/templates/cloudinit_build.html @@ -99,6 +99,7 @@ Templates: +
    @@ -159,7 +160,7 @@ list.forEach(function(t) { var o = document.createElement('option'); o.value = t.id; o.textContent = t.name; sel.appendChild(o); }); var ul = document.getElementById('buildTemplateList'); ul.innerHTML = list.map(function(t) { - return '
  • ' + escapeHtml(t.name) + '
  • '; + return '
  • ' + escapeHtml(t.name) + '
  • '; }).join('') || '
  • No templates
  • '; ul.querySelectorAll('.load-tpl').forEach(function(b) { b.onclick = function() { @@ -170,6 +171,9 @@ }); }; }); + ul.querySelectorAll('.upd-tpl').forEach(function(b) { + b.onclick = function() { updateTemplate(b.getAttribute('data-id')); }; + }); ul.querySelectorAll('.del-tpl').forEach(function(b) { b.onclick = function() { if (!confirm('Delete template?')) return; @@ -178,6 +182,12 @@ }); }).catch(function() {}); } + function updateTemplate(tid) { + var body = { user_data: document.getElementById('buildUserData').value, meta_data: document.getElementById('buildMetaData').value, network_config: document.getElementById('buildNetworkConfig').value }; + authFetch('/api/cloudinit-templates/' + encodeURIComponent(tid), { method: 'PUT', headers: {'Content-Type':'application/json'}, body: JSON.stringify(body) }).then(function(r) { return r.json(); }).then(function(d) { + if (d.ok) { fetchTemplates(); alert('Template updated'); } else alert(d.error || 'Update failed'); + }).catch(function() { alert('Update failed'); }); + } function loadTemplateFromSelect() { var s = document.getElementById('buildTemplateSelect'); if (s && s.value) authFetch('/api/cloudinit-templates/' + s.value).then(function(r) { return r.json(); }).then(function(t) { @@ -200,6 +210,11 @@ }); }; document.getElementById('buildTemplateLoad').onclick = loadTemplateFromSelect; + document.getElementById('buildTemplateUpdate').onclick = function() { + var s = document.getElementById('buildTemplateSelect'); + if (!s || !s.value) { alert('Select a template to update'); return; } + updateTemplate(s.value); + }; document.getElementById('buildTemplateSave').onclick = saveTemplate; fetchBuildStatus(); diff --git a/emmc-provisioning/dashboard/templates/index.html b/emmc-provisioning/dashboard/templates/index.html index 7106ef6..569fd6c 100644 --- a/emmc-provisioning/dashboard/templates/index.html +++ b/emmc-provisioning/dashboard/templates/index.html @@ -449,6 +449,7 @@

    Templates: +

      @@ -824,11 +825,14 @@ } if (listEl) { listEl.innerHTML = list.map(function(t) { - return '
    • ' + escapeHtml(t.name) + '
    • '; + return '
    • ' + escapeHtml(t.name) + '
    • '; }).join('') || '
    • No templates saved.
    • '; listEl.querySelectorAll('.template-load-btn').forEach(function(btn) { btn.onclick = function() { loadTemplate(btn.getAttribute('data-id')); }; }); + listEl.querySelectorAll('.template-upd-btn').forEach(function(btn) { + btn.onclick = function() { updateTemplate(btn.getAttribute('data-id')); }; + }); listEl.querySelectorAll('.template-del-btn').forEach(function(btn) { btn.onclick = function() { if (!confirm('Delete this template?')) return; @@ -849,6 +853,19 @@ if (nc) nc.value = t.network_config || ''; }).catch(function() {}); } + function updateTemplate(id) { + var ud = document.getElementById('buildUserData'); + var md = document.getElementById('buildMetaData'); + var nc = document.getElementById('buildNetworkConfig'); + fetch('/api/cloudinit-templates/' + encodeURIComponent(id), { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ + user_data: ud ? ud.value : '', + meta_data: md ? md.value : '', + network_config: nc ? nc.value : '' + })}).then(function(r) { return r.json(); }).then(function(d) { + if (d.ok) { fetchCloudInitTemplates(); alert('Template updated'); } + else alert(d.error || 'Update failed'); + }).catch(function() { alert('Update failed'); }); + } function saveTemplate() { var name = prompt('Template name'); if (!name || !name.trim()) return; @@ -972,6 +989,8 @@ if (variantSel) variantSel.onchange = fetchRaspiosUrl; var templateLoadBtn = document.getElementById('buildTemplateLoad'); if (templateLoadBtn) templateLoadBtn.onclick = function() { var s = document.getElementById('buildTemplateSelect'); if (s && s.value) loadTemplate(s.value); }; + var templateUpdateBtn = document.getElementById('buildTemplateUpdate'); + if (templateUpdateBtn) templateUpdateBtn.onclick = function() { var s = document.getElementById('buildTemplateSelect'); if (s && s.value) updateTemplate(s.value); else alert('Select a template to update'); }; var templateSaveBtn = document.getElementById('buildTemplateSave'); if (templateSaveBtn) templateSaveBtn.onclick = saveTemplate; setInterval(fetchStatus, 2000);