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.
This commit is contained in:
@@ -1663,6 +1663,32 @@ def api_cloudinit_templates_get(tid):
|
|||||||
return jsonify({"error": "not found"}), 404
|
return jsonify({"error": "not found"}), 404
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/api/cloudinit-templates/<tid>", 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/<tid>", methods=["DELETE"])
|
@app.route("/api/cloudinit-templates/<tid>", methods=["DELETE"])
|
||||||
@require_admin
|
@require_admin
|
||||||
def api_cloudinit_templates_delete(tid):
|
def api_cloudinit_templates_delete(tid):
|
||||||
|
|||||||
@@ -99,6 +99,7 @@
|
|||||||
<span>Templates:</span>
|
<span>Templates:</span>
|
||||||
<select id="buildTemplateSelect"><option value="">— Load —</option></select>
|
<select id="buildTemplateSelect"><option value="">— Load —</option></select>
|
||||||
<button type="button" id="buildTemplateLoad" class="btn btn-outline btn-sm">Load</button>
|
<button type="button" id="buildTemplateLoad" class="btn btn-outline btn-sm">Load</button>
|
||||||
|
<button type="button" id="buildTemplateUpdate" class="btn btn-outline btn-sm" title="Save current content into the selected template">Update</button>
|
||||||
<button type="button" id="buildTemplateSave" class="btn btn-outline btn-sm">Save as template…</button>
|
<button type="button" id="buildTemplateSave" class="btn btn-outline btn-sm">Save as template…</button>
|
||||||
</div>
|
</div>
|
||||||
<ul id="buildTemplateList" style="margin-top:0.5rem; font-size:0.85rem;"></ul>
|
<ul id="buildTemplateList" style="margin-top:0.5rem; font-size:0.85rem;"></ul>
|
||||||
@@ -159,7 +160,7 @@
|
|||||||
list.forEach(function(t) { var o = document.createElement('option'); o.value = t.id; o.textContent = t.name; sel.appendChild(o); });
|
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');
|
var ul = document.getElementById('buildTemplateList');
|
||||||
ul.innerHTML = list.map(function(t) {
|
ul.innerHTML = list.map(function(t) {
|
||||||
return '<li>' + escapeHtml(t.name) + ' <button type="button" class="btn btn-outline btn-sm load-tpl" data-id="' + t.id + '">Load</button> <button type="button" class="btn btn-outline btn-sm del-tpl" data-id="' + t.id + '">Delete</button></li>';
|
return '<li>' + escapeHtml(t.name) + ' <button type="button" class="btn btn-outline btn-sm load-tpl" data-id="' + t.id + '">Load</button> <button type="button" class="btn btn-outline btn-sm upd-tpl" data-id="' + t.id + '" title="Save current content into this template">Update</button> <button type="button" class="btn btn-outline btn-sm del-tpl" data-id="' + t.id + '">Delete</button></li>';
|
||||||
}).join('') || '<li>No templates</li>';
|
}).join('') || '<li>No templates</li>';
|
||||||
ul.querySelectorAll('.load-tpl').forEach(function(b) {
|
ul.querySelectorAll('.load-tpl').forEach(function(b) {
|
||||||
b.onclick = function() {
|
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) {
|
ul.querySelectorAll('.del-tpl').forEach(function(b) {
|
||||||
b.onclick = function() {
|
b.onclick = function() {
|
||||||
if (!confirm('Delete template?')) return;
|
if (!confirm('Delete template?')) return;
|
||||||
@@ -178,6 +182,12 @@
|
|||||||
});
|
});
|
||||||
}).catch(function() {});
|
}).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() {
|
function loadTemplateFromSelect() {
|
||||||
var s = document.getElementById('buildTemplateSelect');
|
var s = document.getElementById('buildTemplateSelect');
|
||||||
if (s && s.value) authFetch('/api/cloudinit-templates/' + s.value).then(function(r) { return r.json(); }).then(function(t) {
|
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('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;
|
document.getElementById('buildTemplateSave').onclick = saveTemplate;
|
||||||
|
|
||||||
fetchBuildStatus();
|
fetchBuildStatus();
|
||||||
|
|||||||
@@ -449,6 +449,7 @@
|
|||||||
<div class="inner" style="margin-top:0.5rem;">
|
<div class="inner" style="margin-top:0.5rem;">
|
||||||
<p><strong>Templates:</strong> <select id="buildTemplateSelect"><option value="">— Load a template —</option></select>
|
<p><strong>Templates:</strong> <select id="buildTemplateSelect"><option value="">— Load a template —</option></select>
|
||||||
<button type="button" id="buildTemplateLoad" class="btn btn-outline btn-sm">Load</button>
|
<button type="button" id="buildTemplateLoad" class="btn btn-outline btn-sm">Load</button>
|
||||||
|
<button type="button" id="buildTemplateUpdate" class="btn btn-outline btn-sm" title="Save current content into the selected template">Update</button>
|
||||||
<button type="button" id="buildTemplateSave" class="btn btn-outline btn-sm">Save current as template…</button></p>
|
<button type="button" id="buildTemplateSave" class="btn btn-outline btn-sm">Save current as template…</button></p>
|
||||||
<ul id="buildTemplateList" class="backups-mono" style="font-size:0.85rem; list-style:none; padding:0;"></ul>
|
<ul id="buildTemplateList" class="backups-mono" style="font-size:0.85rem; list-style:none; padding:0;"></ul>
|
||||||
<label>user-data (YAML)</label>
|
<label>user-data (YAML)</label>
|
||||||
@@ -824,11 +825,14 @@
|
|||||||
}
|
}
|
||||||
if (listEl) {
|
if (listEl) {
|
||||||
listEl.innerHTML = list.map(function(t) {
|
listEl.innerHTML = list.map(function(t) {
|
||||||
return '<li><span>' + escapeHtml(t.name) + '</span> <button type="button" class="btn btn-outline btn-sm template-load-btn" data-id="' + escapeHtml(t.id) + '">Load</button> <button type="button" class="btn btn-outline btn-sm template-del-btn" data-id="' + escapeHtml(t.id) + '">Delete</button></li>';
|
return '<li><span>' + escapeHtml(t.name) + '</span> <button type="button" class="btn btn-outline btn-sm template-load-btn" data-id="' + escapeHtml(t.id) + '">Load</button> <button type="button" class="btn btn-outline btn-sm template-upd-btn" data-id="' + escapeHtml(t.id) + '" title="Save current content into this template">Update</button> <button type="button" class="btn btn-outline btn-sm template-del-btn" data-id="' + escapeHtml(t.id) + '">Delete</button></li>';
|
||||||
}).join('') || '<li>No templates saved.</li>';
|
}).join('') || '<li>No templates saved.</li>';
|
||||||
listEl.querySelectorAll('.template-load-btn').forEach(function(btn) {
|
listEl.querySelectorAll('.template-load-btn').forEach(function(btn) {
|
||||||
btn.onclick = function() { loadTemplate(btn.getAttribute('data-id')); };
|
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) {
|
listEl.querySelectorAll('.template-del-btn').forEach(function(btn) {
|
||||||
btn.onclick = function() {
|
btn.onclick = function() {
|
||||||
if (!confirm('Delete this template?')) return;
|
if (!confirm('Delete this template?')) return;
|
||||||
@@ -849,6 +853,19 @@
|
|||||||
if (nc) nc.value = t.network_config || '';
|
if (nc) nc.value = t.network_config || '';
|
||||||
}).catch(function() {});
|
}).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() {
|
function saveTemplate() {
|
||||||
var name = prompt('Template name');
|
var name = prompt('Template name');
|
||||||
if (!name || !name.trim()) return;
|
if (!name || !name.trim()) return;
|
||||||
@@ -972,6 +989,8 @@
|
|||||||
if (variantSel) variantSel.onchange = fetchRaspiosUrl;
|
if (variantSel) variantSel.onchange = fetchRaspiosUrl;
|
||||||
var templateLoadBtn = document.getElementById('buildTemplateLoad');
|
var templateLoadBtn = document.getElementById('buildTemplateLoad');
|
||||||
if (templateLoadBtn) templateLoadBtn.onclick = function() { var s = document.getElementById('buildTemplateSelect'); if (s && s.value) loadTemplate(s.value); };
|
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');
|
var templateSaveBtn = document.getElementById('buildTemplateSave');
|
||||||
if (templateSaveBtn) templateSaveBtn.onclick = saveTemplate;
|
if (templateSaveBtn) templateSaveBtn.onclick = saveTemplate;
|
||||||
setInterval(fetchStatus, 2000);
|
setInterval(fetchStatus, 2000);
|
||||||
|
|||||||
Reference in New Issue
Block a user