init
All checks were successful
build-binaries / build (, amd64, linux) (push) Has been skipped
build-binaries / build (, arm, 7, linux) (push) Has been skipped
build-binaries / build (, arm64, linux) (push) Has been skipped
build-binaries / build (.exe, amd64, windows) (push) Has been skipped
build-binaries / release (push) Has been skipped
release-tag / release-image (push) Successful in 4m24s
All checks were successful
build-binaries / build (, amd64, linux) (push) Has been skipped
build-binaries / build (, arm, 7, linux) (push) Has been skipped
build-binaries / build (, arm64, linux) (push) Has been skipped
build-binaries / build (.exe, amd64, windows) (push) Has been skipped
build-binaries / release (push) Has been skipped
release-tag / release-image (push) Successful in 4m24s
This commit is contained in:
187
admin.html
Normal file
187
admin.html
Normal file
@@ -0,0 +1,187 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Version Agent Admin</title>
|
||||
<style>
|
||||
body{font-family:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell;max-width:1000px;margin:40px auto;padding:0 16px}
|
||||
header{display:flex;justify-content:space-between;align-items:center;margin-bottom:24px}
|
||||
section{border:1px solid #ddd;border-radius:12px;padding:16px;margin-bottom:24px;box-shadow:0 1px 3px rgba(0,0,0,.05)}
|
||||
label{display:block;margin:.3rem 0 .1rem;color:#333}
|
||||
input,select,textarea{width:100%;padding:.5rem;border:1px solid #ccc;border-radius:8px}
|
||||
button{padding:.6rem 1rem;border:0;border-radius:10px;cursor:pointer}
|
||||
.btn{background:#111;color:#fff}
|
||||
.grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px}
|
||||
.assets{margin-top:8px}
|
||||
.asset-row{display:grid;grid-template-columns:2fr 2fr 1fr 1fr;gap:8px;margin-bottom:8px}
|
||||
.small{font-size:.9rem;color:#555}
|
||||
code{background:#f6f6f6;padding:.2rem .4rem;border-radius:6px}
|
||||
pre{background:#f6f6f6;padding:8px;border-radius:8px;overflow:auto}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Version Agent Admin</h1>
|
||||
<div>
|
||||
<label>API Token (for POST)</label>
|
||||
<input id="token" placeholder="Bearer Token" />
|
||||
<div class="small">will be saved in <code>localStorage</code></div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section>
|
||||
<h2>Configuration</h2>
|
||||
<div class="grid">
|
||||
<div>
|
||||
<label>Vendor</label><input id="vendor" />
|
||||
</div>
|
||||
<div>
|
||||
<label>Product</label><input id="product" />
|
||||
</div>
|
||||
<div>
|
||||
<label>Default Branch</label><input id="defBranch" placeholder="eg. 12.x" />
|
||||
</div>
|
||||
<div>
|
||||
<label>Default Channel</label>
|
||||
<select id="defChannel"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top:12px"><button class="btn" id="saveConfig">Save</button></div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Maintenance: Set Latest</h2>
|
||||
<div class="grid">
|
||||
<div><label>Branch</label><input id="branch" placeholder="eg. 12.x"/></div>
|
||||
<div><label>Channel</label><select id="channel"></select></div>
|
||||
<div><label>Arch</label><select id="arch"></select></div>
|
||||
<div><label>Bit</label><select id="bit"></select></div>
|
||||
</div>
|
||||
<div class="grid" style="margin-top:12px">
|
||||
<div><label>OS</label><select id="os"></select></div>
|
||||
<div><label>Version</label><input id="version" placeholder="12.3.1"/></div>
|
||||
<div><label>Build</label><input id="build" placeholder="optional"/></div>
|
||||
<div><label>Released At (RFC3339)</label><input id="releasedAt" placeholder="2025-10-15T12:34:56Z"/></div>
|
||||
</div>
|
||||
<div style="margin-top:12px">
|
||||
<label>Notes URL</label><input id="notesUrl" placeholder="https://example.com/release-notes"/>
|
||||
</div>
|
||||
<div class="assets">
|
||||
<h3>Assets</h3>
|
||||
<div id="assets"></div>
|
||||
<button id="addAsset">Add Asset</button>
|
||||
</div>
|
||||
<div style="margin-top:12px"><button class="btn" id="publish">Publish</button></div>
|
||||
<div style="margin-top:12px">
|
||||
<button id="loadLatest">Load Latest Info</button>
|
||||
</div>
|
||||
<pre id="log"></pre>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Manifest</h2>
|
||||
<div class="small">ETag-aware GET <code>/v1/manifest</code></div>
|
||||
<pre id="manifest"></pre>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
const $ = sel => document.querySelector(sel);
|
||||
const log = (msg) => { const el=$('#log'); el.textContent = (new Date()).toISOString()+"\n"+msg; }
|
||||
|
||||
function tokenHeader(){ const t=$('#token').value.trim(); return t? { 'Authorization': 'Bearer '+t } : {}; }
|
||||
|
||||
async function loadValues(){
|
||||
const r = await fetch('/v1/values'); const j = await r.json();
|
||||
const fill = (id, arr) => { const s=$(id); s.innerHTML=''; arr.forEach(v=>{ const o=document.createElement('option'); o.value=v; o.textContent=v; s.appendChild(o); }); };
|
||||
fill('#arch', j.arch); fill('#bit', j.bit); fill('#os', j.os); fill('#channel', j.channels); fill('#defChannel', j.channels);
|
||||
$('#defBranch').value = j.defaults.branch || '';
|
||||
$('#defChannel').value = j.defaults.channel || 'stable';
|
||||
$('#vendor').value = j.meta.vendor || '';
|
||||
$('#product').value = j.meta.product || '';
|
||||
}
|
||||
|
||||
async function loadManifest(){ const r=await fetch('/v1/manifest'); const j=await r.json(); $('#manifest').textContent = JSON.stringify(j, null, 2); }
|
||||
|
||||
function addAssetRow(data={}){
|
||||
const wrap=document.createElement('div'); wrap.className='asset-row';
|
||||
wrap.innerHTML = `
|
||||
<input placeholder="URL" value="${data.url||''}"/>
|
||||
<input placeholder="SHA256" value="${data.sha256||''}"/>
|
||||
<input placeholder="Size (bytes)" value="${data.size_bytes||''}"/>
|
||||
<input placeholder="Signature URL" value="${data.signature_url||''}"/>
|
||||
`;
|
||||
$('#assets').appendChild(wrap);
|
||||
}
|
||||
|
||||
async function publish(){
|
||||
const assets = Array.from(document.querySelectorAll('.asset-row')).map(row=>{
|
||||
const [url,sha,size,sig] = row.querySelectorAll('input');
|
||||
const a={ url:url.value.trim(), sha256:sha.value.trim() };
|
||||
if(size.value.trim()) a.size_bytes = parseInt(size.value.trim(),10);
|
||||
if(sig.value.trim()) a.signature_url = sig.value.trim();
|
||||
return a;
|
||||
}).filter(a=>a.url && a.sha256);
|
||||
|
||||
const payload = {
|
||||
branch: $('#branch').value.trim() || $('#defBranch').value.trim(),
|
||||
channel: $('#channel').value,
|
||||
arch: $('#arch').value,
|
||||
bit: $('#bit').value,
|
||||
os: $('#os').value,
|
||||
release: {
|
||||
version: $('#version').value.trim(),
|
||||
build: $('#build').value.trim(),
|
||||
released_at: $('#releasedAt').value.trim(),
|
||||
notes_url: $('#notesUrl').value.trim(),
|
||||
assets
|
||||
}
|
||||
};
|
||||
|
||||
const r = await fetch('/v1/publish', { method:'POST', headers:{ 'Content-Type':'application/json', ...tokenHeader() }, body: JSON.stringify(payload) });
|
||||
const txt = await r.text(); log(txt); await loadManifest();
|
||||
}
|
||||
|
||||
async function loadLatest(){
|
||||
const params = new URLSearchParams({
|
||||
branch: $('#branch').value.trim() || $('#defBranch').value.trim(),
|
||||
channel: $('#channel').value,
|
||||
arch: $('#arch').value,
|
||||
bit: $('#bit').value,
|
||||
os: $('#os').value
|
||||
});
|
||||
const r = await fetch('/v1/latest?'+params.toString());
|
||||
if(!r.ok){ log('not found'); return; }
|
||||
const j = await r.json();
|
||||
$('#version').value = j.release.version || '';
|
||||
$('#build').value = j.release.build || '';
|
||||
$('#releasedAt').value = (j.release.released_at||'');
|
||||
$('#notesUrl').value = j.release.notes_url || '';
|
||||
$('#assets').innerHTML='';
|
||||
(j.release.assets||[]).forEach(a=>addAssetRow({ url:a.url, sha256:a.sha256, size_bytes:a.size_bytes||'', signature_url:a.signature_url||'' }));
|
||||
if((j.release.assets||[]).length===0) addAssetRow();
|
||||
}
|
||||
|
||||
async function saveConfig(){
|
||||
const payload = {
|
||||
vendor: $('#vendor').value.trim(),
|
||||
product: $('#product').value.trim(),
|
||||
default_branch: $('#defBranch').value.trim(),
|
||||
default_channel: $('#defChannel').value
|
||||
};
|
||||
const r = await fetch('/v1/config', { method:'POST', headers:{ 'Content-Type':'application/json', ...tokenHeader() }, body: JSON.stringify(payload) });
|
||||
const txt = await r.text(); log(txt); await loadValues(); await loadManifest();
|
||||
}
|
||||
|
||||
// init
|
||||
(function(){
|
||||
$('#token').value = localStorage.getItem('apiToken')||'';
|
||||
$('#token').addEventListener('input', e=> localStorage.setItem('apiToken', e.target.value));
|
||||
$('#addAsset').addEventListener('click', e=>{ e.preventDefault(); addAssetRow(); });
|
||||
$('#publish').addEventListener('click', e=>{ e.preventDefault(); publish(); });
|
||||
$('#loadLatest').addEventListener('click', e=>{ e.preventDefault(); loadLatest(); });
|
||||
$('#saveConfig').addEventListener('click', e=>{ e.preventDefault(); saveConfig(); });
|
||||
addAssetRow(); loadValues(); loadManifest();
|
||||
})();
|
||||
</script>
|
||||
</body></html>
|
||||
Reference in New Issue
Block a user