Remove obsolete audio and buzzer control documentation files, including detailed guides and HTML interfaces, to streamline the repository and eliminate redundancy. This cleanup enhances maintainability and focuses on essential resources for the reTerminal DM4 audio and buzzer functionalities.
This commit is contained in:
213
emmc-provisioning/dashboard/templates/cloudinit_build.html
Normal file
213
emmc-provisioning/dashboard/templates/cloudinit_build.html
Normal file
@@ -0,0 +1,213 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Cloud-init build · Admin · CM4 Provisioning</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&family=Outfit:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root {
|
||||
--bg-primary: #0a0e14; --bg-secondary: #11151c; --bg-tertiary: #1a1f2b; --bg-card: #151a24;
|
||||
--accent: #00d4aa; --accent-dim: #00b894; --text: #e6e8eb; --text-dim: #8b949e; --text-muted: #5c6370;
|
||||
--border: #2d333b; --radius: 10px; --radius-sm: 6px;
|
||||
}
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: 'Outfit', sans-serif; background: var(--bg-primary); color: var(--text); min-height: 100vh; font-size: 14px; line-height: 1.5; }
|
||||
.wrap { max-width: 1000px; margin: 0 auto; padding: 1rem; }
|
||||
.header { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 0.75rem; margin-bottom: 1rem; padding-bottom: 0.75rem; border-bottom: 1px solid var(--border); }
|
||||
.header h1 { font-size: 1.25rem; font-weight: 700; }
|
||||
.header span { color: var(--text-dim); font-size: 0.9rem; }
|
||||
.nav a { color: var(--text-dim); text-decoration: none; margin-right: 1rem; }
|
||||
.nav a:hover { color: var(--accent); }
|
||||
.nav a.active { color: var(--accent); font-weight: 600; }
|
||||
.header a { color: var(--text-dim); text-decoration: none; margin-left: 0.5rem; }
|
||||
.header a:hover { color: var(--accent); }
|
||||
.section { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius); padding: 1rem 1.25rem; margin-bottom: 1rem; }
|
||||
.section-title { font-size: 0.7rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-dim); margin-bottom: 0.75rem; }
|
||||
.btn { padding: 0.4rem 0.8rem; font-size: 0.8rem; font-weight: 500; font-family: inherit; border-radius: var(--radius-sm); cursor: pointer; border: none; transition: background 0.15s, color 0.15s; }
|
||||
.btn-outline { background: transparent; border: 1px solid var(--border); color: var(--text); }
|
||||
.btn-outline:hover { border-color: var(--accent); color: var(--accent); }
|
||||
.btn-primary { background: var(--accent); color: var(--bg-primary); }
|
||||
.btn-primary:hover { background: var(--accent-dim); }
|
||||
.btn-sm { padding: 0.3rem 0.5rem; font-size: 0.75rem; }
|
||||
.tabs { display: flex; gap: 0.25rem; margin-bottom: 0.75rem; border-bottom: 1px solid var(--border); }
|
||||
.tabs button { padding: 0.5rem 1rem; background: none; border: none; color: var(--text-dim); cursor: pointer; font: inherit; border-bottom: 2px solid transparent; margin-bottom: -1px; }
|
||||
.tabs button:hover { color: var(--text); }
|
||||
.tabs button.active { color: var(--accent); border-bottom-color: var(--accent); }
|
||||
.tab-pane { display: none; }
|
||||
.tab-pane.active { display: block; }
|
||||
textarea { width: 100%; min-height: 220px; resize: vertical; font-family: 'JetBrains Mono', monospace; font-size: 0.8rem; background: var(--bg-tertiary); border: 1px solid var(--border); color: var(--text); padding: 0.5rem; border-radius: 4px; }
|
||||
.mono { font-family: 'JetBrains Mono', monospace; font-size: 0.8em; color: var(--text-muted); }
|
||||
input[type="text"], select { background: var(--bg-tertiary); border: 1px solid var(--border); color: var(--text); padding: 0.35rem 0.5rem; border-radius: 4px; font: inherit; }
|
||||
label { display: inline-block; margin-right: 0.5rem; }
|
||||
ul { list-style: none; padding: 0; }
|
||||
ul li { margin-bottom: 0.35rem; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<header class="header">
|
||||
<div>
|
||||
<h1>Cloud-init build</h1>
|
||||
<nav class="nav" style="margin-top:0.35rem;">
|
||||
<a href="/admin">Admin</a>
|
||||
<a href="/admin/portal-files">Portal files</a>
|
||||
<a href="/admin/cloudinit-build" class="active">Cloud-init build</a>
|
||||
</nav>
|
||||
</div>
|
||||
<span>Logged in as <strong>{{ username }}</strong></span>
|
||||
<a href="/">Deploy</a>
|
||||
<a href="/logout">Log out</a>
|
||||
</header>
|
||||
|
||||
<div class="section">
|
||||
<h2 class="section-title">Download & build cloud-init image</h2>
|
||||
<p style="font-size:0.9rem; color:var(--text-dim); margin-bottom:0.75rem;">Download latest Raspberry Pi OS (arm64), inject cloud-init. Output goes to <strong>Cloud-init images</strong> on the Admin page.</p>
|
||||
<div style="margin-bottom:0.5rem;">
|
||||
<label>Variant:</label>
|
||||
<select id="buildVariant"><option value="lite">Lite</option><option value="full">Full</option></select>
|
||||
<span id="buildRaspiosUrl" class="mono" style="margin-left:0.5rem;"></span>
|
||||
</div>
|
||||
<div style="margin-bottom:0.5rem;"><label><input type="checkbox" id="buildSetGolden" /> Set as golden after build</label></div>
|
||||
<div style="margin-bottom:0.75rem;"><button type="button" id="buildCloudInitBtn" class="btn btn-primary">Download & build</button></div>
|
||||
<div id="buildCloudInitStatus" class="mono" style="min-height:1.2em;"></div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2 class="section-title">Edit cloud-init files (templates)</h2>
|
||||
<p style="font-size:0.85rem; color:var(--text-dim); margin-bottom:0.75rem;">Choose a file to edit below. These contents are injected into the image when you run the build.</p>
|
||||
<div class="tabs">
|
||||
<button type="button" class="tab-btn active" data-tab="user-data">user-data</button>
|
||||
<button type="button" class="tab-btn" data-tab="meta-data">meta-data</button>
|
||||
<button type="button" class="tab-btn" data-tab="network-config">network-config</button>
|
||||
</div>
|
||||
<div id="pane-user-data" class="tab-pane active">
|
||||
<label>user-data (YAML, #cloud-config)</label>
|
||||
<textarea id="buildUserData" rows="12" placeholder="#cloud-config..."></textarea>
|
||||
</div>
|
||||
<div id="pane-meta-data" class="tab-pane">
|
||||
<label>meta-data (optional)</label>
|
||||
<textarea id="buildMetaData" rows="8" placeholder="instance-id: ..."></textarea>
|
||||
</div>
|
||||
<div id="pane-network-config" class="tab-pane">
|
||||
<label>network-config (optional)</label>
|
||||
<textarea id="buildNetworkConfig" rows="8" placeholder="version: 2..."></textarea>
|
||||
</div>
|
||||
<div style="margin-top:0.75rem;">
|
||||
<span>Templates:</span>
|
||||
<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="buildTemplateSave" class="btn btn-outline btn-sm">Save as template…</button>
|
||||
</div>
|
||||
<ul id="buildTemplateList" style="margin-top:0.5rem; font-size:0.85rem;"></ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function authFetch(url, opts) {
|
||||
opts = opts || {};
|
||||
return fetch(url, opts).then(function(r) {
|
||||
if (r.status === 401) { window.location = '/login?next=' + encodeURIComponent(window.location.pathname); return Promise.reject(new Error('Login required')); }
|
||||
return r;
|
||||
});
|
||||
}
|
||||
function escapeHtml(s) { var d = document.createElement('div'); d.textContent = s == null ? '' : s; return d.innerHTML; }
|
||||
|
||||
document.querySelectorAll('.tab-btn').forEach(function(btn) {
|
||||
btn.onclick = function() {
|
||||
document.querySelectorAll('.tab-btn').forEach(function(b) { b.classList.remove('active'); });
|
||||
document.querySelectorAll('.tab-pane').forEach(function(p) { p.classList.remove('active'); });
|
||||
btn.classList.add('active');
|
||||
var tab = btn.getAttribute('data-tab');
|
||||
var pane = document.getElementById('pane-' + tab);
|
||||
if (pane) pane.classList.add('active');
|
||||
};
|
||||
});
|
||||
|
||||
function fetchBuildStatus() {
|
||||
authFetch('/api/build-cloudinit-status').then(function(r) { return r.json(); }).then(function(d) {
|
||||
var el = document.getElementById('buildCloudInitStatus');
|
||||
var btn = document.getElementById('buildCloudInitBtn');
|
||||
var busy = ['resolving','downloading','decompressing','injecting','finalizing'].indexOf(d.phase) >= 0;
|
||||
if (btn) btn.disabled = busy;
|
||||
if (d.phase === 'idle' && !d.message) el.textContent = '';
|
||||
else if (d.phase === 'done') { el.textContent = 'Done: ' + (d.output_name || '') + ' — see Admin page Cloud-init images.'; }
|
||||
else if (d.phase === 'error') el.textContent = 'Error: ' + (d.error || '');
|
||||
else el.textContent = (d.phase || '') + ': ' + (d.message || '');
|
||||
if (busy) setTimeout(fetchBuildStatus, 5000);
|
||||
}).catch(function() {});
|
||||
}
|
||||
function startBuild() {
|
||||
var btn = document.getElementById('buildCloudInitBtn');
|
||||
if (btn) btn.disabled = true;
|
||||
var body = { variant: document.getElementById('buildVariant').value, set_as_golden_after: document.getElementById('buildSetGolden').checked };
|
||||
body.user_data = document.getElementById('buildUserData').value.trim();
|
||||
body.meta_data = document.getElementById('buildMetaData').value.trim();
|
||||
body.network_config = document.getElementById('buildNetworkConfig').value.trim();
|
||||
authFetch('/api/build-cloudinit', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify(body) }).then(function(r) { return r.json(); }).then(function(d) {
|
||||
if (d.ok) { document.getElementById('buildCloudInitStatus').textContent = 'Build started…'; setTimeout(fetchBuildStatus, 2000); }
|
||||
else { alert(d.error); if (btn) btn.disabled = false; }
|
||||
}).catch(function() { if (btn) btn.disabled = false; });
|
||||
}
|
||||
function fetchTemplates() {
|
||||
authFetch('/api/cloudinit-templates').then(function(r) { return r.json(); }).then(function(d) {
|
||||
var list = d.templates || [];
|
||||
var sel = document.getElementById('buildTemplateSelect');
|
||||
sel.innerHTML = '<option value="">— Load —</option>';
|
||||
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 '<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>';
|
||||
}).join('') || '<li>No templates</li>';
|
||||
ul.querySelectorAll('.load-tpl').forEach(function(b) {
|
||||
b.onclick = function() {
|
||||
authFetch('/api/cloudinit-templates/' + b.getAttribute('data-id')).then(function(r) { return r.json(); }).then(function(t) {
|
||||
document.getElementById('buildUserData').value = t.user_data || '';
|
||||
document.getElementById('buildMetaData').value = t.meta_data || '';
|
||||
document.getElementById('buildNetworkConfig').value = t.network_config || '';
|
||||
});
|
||||
};
|
||||
});
|
||||
ul.querySelectorAll('.del-tpl').forEach(function(b) {
|
||||
b.onclick = function() {
|
||||
if (!confirm('Delete template?')) return;
|
||||
authFetch('/api/cloudinit-templates/' + b.getAttribute('data-id'), { method: 'DELETE' }).then(function(r) { return r.json(); }).then(function(d) { if (d.ok) fetchTemplates(); });
|
||||
};
|
||||
});
|
||||
}).catch(function() {});
|
||||
}
|
||||
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) {
|
||||
document.getElementById('buildUserData').value = t.user_data || '';
|
||||
document.getElementById('buildMetaData').value = t.meta_data || '';
|
||||
document.getElementById('buildNetworkConfig').value = t.network_config || '';
|
||||
});
|
||||
}
|
||||
function saveTemplate() {
|
||||
var name = prompt('Template name');
|
||||
if (!name || !name.trim()) return;
|
||||
var body = { name: name.trim(), user_data: document.getElementById('buildUserData').value, meta_data: document.getElementById('buildMetaData').value, network_config: document.getElementById('buildNetworkConfig').value };
|
||||
authFetch('/api/cloudinit-templates', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify(body) }).then(function(r) { return r.json(); }).then(function(d) { if (d.ok) { fetchTemplates(); alert('Saved'); } else alert(d.error); });
|
||||
}
|
||||
|
||||
document.getElementById('buildCloudInitBtn').onclick = startBuild;
|
||||
document.getElementById('buildVariant').onchange = function() {
|
||||
authFetch('/api/raspios-latest-url?variant=' + encodeURIComponent(document.getElementById('buildVariant').value)).then(function(r) { return r.json(); }).then(function(d) {
|
||||
document.getElementById('buildRaspiosUrl').textContent = (d.ok && d.filename) ? d.filename : (d.error || '');
|
||||
});
|
||||
};
|
||||
document.getElementById('buildTemplateLoad').onclick = loadTemplateFromSelect;
|
||||
document.getElementById('buildTemplateSave').onclick = saveTemplate;
|
||||
|
||||
fetchBuildStatus();
|
||||
fetchTemplates();
|
||||
authFetch('/api/raspios-latest-url').then(function(r) { return r.json(); }).then(function(d) {
|
||||
document.getElementById('buildRaspiosUrl').textContent = (d.ok && d.filename) ? d.filename : (d.error || '');
|
||||
});
|
||||
setInterval(fetchBuildStatus, 15000);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user