428 lines
23 KiB
C
428 lines
23 KiB
C
#include "amduatd_ui.h"
|
|
#include "amduatd_http.h"
|
|
|
|
#include "amduat/asl/artifact_io.h"
|
|
#include "amduat/asl/asl_store_fs.h"
|
|
#include "amduat/asl/asl_store_fs_meta.h"
|
|
#include "amduat/asl/ref_derive.h"
|
|
|
|
#include <stdbool.h>
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
|
|
static const char k_amduatd_ui_html[] =
|
|
"<!doctype html>\n"
|
|
"<html lang=\"en\">\n"
|
|
"<head>\n"
|
|
" <meta charset=\"utf-8\" />\n"
|
|
" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n"
|
|
" <title>amduatd — Concept editor</title>\n"
|
|
" <style>\n"
|
|
" :root{\n"
|
|
" --bg:#0b1220;--card:#111a2e;--text:#eaf0ff;--muted:#b7c3e6;--border:rgba(255,255,255,.10);\n"
|
|
" --shadow:0 10px 30px rgba(0,0,0,.35);--radius:18px;--max:980px;--pad:clamp(16px,3.5vw,28px);\n"
|
|
" }\n"
|
|
" *{box-sizing:border-box;}\n"
|
|
" html,body{min-height:100%;}\n"
|
|
" html{background:var(--bg);}\n"
|
|
" body{margin:0;min-height:100vh;font-family:\"Avenir Next\",\"Avenir\",\"Trebuchet MS\",\"Segoe UI\",sans-serif;color:var(--text);line-height:1.55;"
|
|
" background:radial-gradient(900px 400px at 15% 10%,rgba(95,145,255,.35),transparent 60%),"
|
|
" radial-gradient(800px 450px at 85% 20%,rgba(255,140,92,.25),transparent 60%),"
|
|
" radial-gradient(700px 500px at 50% 95%,rgba(56,220,181,.18),transparent 60%),var(--bg);}\n"
|
|
" a{color:inherit;text-decoration:none;}\n"
|
|
" a:hover{text-decoration:underline;text-underline-offset:4px;}\n"
|
|
" .wrap{max-width:var(--max);margin:0 auto;padding:26px var(--pad) 70px;min-height:100vh;}\n"
|
|
" header{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:14px 0 22px;}\n"
|
|
" .brand{display:flex;align-items:center;gap:10px;font-weight:700;letter-spacing:.2px;}\n"
|
|
" .logo{width:38px;height:38px;border-radius:12px;border:1px solid var(--border);"
|
|
" background:linear-gradient(135deg,rgba(95,145,255,.9),rgba(56,220,181,.8));box-shadow:var(--shadow);}\n"
|
|
" nav{display:flex;gap:14px;flex-wrap:wrap;color:var(--muted);font-size:14px;}\n"
|
|
" nav a{padding:6px 10px;border-radius:10px;}\n"
|
|
" nav a:hover{background:rgba(255,255,255,.06);text-decoration:none;}\n"
|
|
" .hero{border:1px solid var(--border);background:rgba(17,26,46,.72);border-radius:var(--radius);box-shadow:var(--shadow);"
|
|
" padding:clamp(22px,4.5vw,42px);backdrop-filter:blur(10px);}\n"
|
|
" h1{margin:0 0 10px;font-size:clamp(28px,3.6vw,40px);line-height:1.1;letter-spacing:-0.6px;}\n"
|
|
" .lead{margin:0 0 18px;color:var(--muted);font-size:clamp(14px,2vw,17px);max-width:70ch;}\n"
|
|
" .cta-row{display:flex;flex-wrap:wrap;gap:12px;margin-top:10px;}\n"
|
|
" .grid{display:grid;grid-template-columns:repeat(12,1fr);gap:14px;margin-top:16px;}\n"
|
|
" .card{grid-column:span 12;border:1px solid var(--border);background:rgba(17,26,46,.62);border-radius:16px;padding:16px;"
|
|
" box-shadow:0 8px 22px rgba(0,0,0,.25);backdrop-filter:blur(10px);}\n"
|
|
" .card h2{margin:2px 0 6px;font-size:16px;letter-spacing:.1px;}\n"
|
|
" .muted{color:var(--muted);font-size:13px;}\n"
|
|
" .span-7{grid-column:span 12;}\n"
|
|
" .span-5{grid-column:span 12;}\n"
|
|
" .span-6{grid-column:span 12;}\n"
|
|
" .stack{display:grid;gap:14px;}\n"
|
|
" @media (min-width: 980px){.span-7{grid-column:span 7;}.span-5{grid-column:span 5;}.span-6{grid-column:span 6;}}\n"
|
|
" textarea,input,select{width:100%;box-sizing:border-box;border-radius:12px;padding:10px;border:1px solid var(--border);"
|
|
" background:rgba(0,0,0,.12);color:var(--text);font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,monospace;"
|
|
" font-size:12.5px;}\n"
|
|
" textarea{min-height:420px;resize:vertical;}\n"
|
|
" .btn{display:inline-flex;align-items:center;justify-content:center;gap:10px;padding:10px 14px;border-radius:12px;border:1px solid var(--border);"
|
|
" background:rgba(255,255,255,.06);color:var(--text);font-weight:600;font-size:14px;cursor:pointer;}\n"
|
|
" .btn:hover{background:rgba(255,255,255,.10);}\n"
|
|
" .btn.primary{background:linear-gradient(135deg,rgba(95,145,255,.95),rgba(56,220,181,.85));border-color:rgba(255,255,255,.18);}\n"
|
|
" .btn.primary:hover{filter:brightness(1.05);}\n"
|
|
" .row{display:flex;gap:10px;flex-wrap:wrap;align-items:center;}\n"
|
|
" .row > *{flex:1 1 auto;}\n"
|
|
" .row .btn{flex:0 0 auto;}\n"
|
|
" pre{white-space:pre-wrap;word-break:break-word;margin:0;padding:10px;border-radius:12px;border:1px solid var(--border);"
|
|
" background:rgba(0,0,0,.2);font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,monospace;"
|
|
" font-size:12.5px;min-height:120px;color:var(--text);}\n"
|
|
" footer{margin-top:26px;color:var(--muted);font-size:13px;display:flex;gap:10px;justify-content:space-between;flex-wrap:wrap;}\n"
|
|
" .sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0;}\n"
|
|
" </style>\n"
|
|
"</head>\n"
|
|
"<body>\n"
|
|
" <div class=\"wrap\">\n"
|
|
" <header>\n"
|
|
" <div class=\"brand\" aria-label=\"Site brand\">\n"
|
|
" <div class=\"logo\" aria-hidden=\"true\"></div>\n"
|
|
" <span>amduatd</span>\n"
|
|
" </div>\n"
|
|
" <nav aria-label=\"Primary\">\n"
|
|
" <a href=\"#editor\">Editor</a>\n"
|
|
" <a href=\"#runner\">Run</a>\n"
|
|
" <a href=\"#relations\">Relations</a>\n"
|
|
" <a href=\"#about\">About</a>\n"
|
|
" </nav>\n"
|
|
" </header>\n"
|
|
" <main>\n"
|
|
" <section class=\"hero\" aria-labelledby=\"title\">\n"
|
|
" <h1 id=\"title\">Concept editor + PEL runner</h1>\n"
|
|
" <p class=\"lead\">Load shows the latest materialized bytes; Save uploads a new artifact and publishes a new version. Use the runner to execute PEL programs against stored artifacts.</p>\n"
|
|
" <div class=\"cta-row\">\n"
|
|
" <a class=\"btn primary\" href=\"#editor\" role=\"button\">Open editor</a>\n"
|
|
" <a class=\"btn\" href=\"#runner\" role=\"button\">Run program</a>\n"
|
|
" </div>\n"
|
|
" </section>\n"
|
|
" <section class=\"grid\" style=\"margin-top:16px;\">\n"
|
|
" <div class=\"card span-7\" id=\"editor\">\n"
|
|
" <div class=\"row\">\n"
|
|
" <input id=\"conceptName\" placeholder=\"concept name (e.g. hello)\" />\n"
|
|
" <button class=\"btn\" id=\"btnConceptCreate\" type=\"button\">Create</button>\n"
|
|
" <button class=\"btn\" id=\"btnLoad\" type=\"button\">Load</button>\n"
|
|
" <button class=\"btn\" id=\"btnSave\" type=\"button\">Save</button>\n"
|
|
" </div>\n"
|
|
" <div class=\"row\" style=\"margin-top:10px;\">\n"
|
|
" <select id=\"mode\">\n"
|
|
" <option value=\"text\">bytes: text (utf-8)</option>\n"
|
|
" <option value=\"base64\">bytes: base64</option>\n"
|
|
" <option value=\"hex\">bytes: hex</option>\n"
|
|
" <option value=\"pel_program\">PEL program: JSON</option>\n"
|
|
" </select>\n"
|
|
" <input id=\"typeTag\" placeholder=\"X-Amduat-Type-Tag (optional)\" />\n"
|
|
" <input id=\"latestRef\" placeholder=\"latest_ref\" readonly />\n"
|
|
" </div>\n"
|
|
" <textarea id=\"editor\" spellcheck=\"false\" placeholder=\"(bytes or PEL program authoring JSON)\"></textarea>\n"
|
|
" <div class=\"row\" style=\"margin-top:10px;\">\n"
|
|
" <button class=\"btn\" id=\"btnProgramTemplate\" type=\"button\">Insert identity program</button>\n"
|
|
" </div>\n"
|
|
" <div class=\"row\" style=\"margin-top:10px;\">\n"
|
|
" <input id=\"publishRef\" placeholder=\"publish existing ref\" />\n"
|
|
" <button class=\"btn\" id=\"btnPublishRef\" type=\"button\">Publish ref</button>\n"
|
|
" </div>\n"
|
|
" </div>\n"
|
|
"\n"
|
|
" <div class=\"stack span-5\">\n"
|
|
" <div class=\"card\" id=\"runner\">\n"
|
|
" <div class=\"muted\" style=\"margin-bottom:8px;\">Upload bytes (sets program_ref)</div>\n"
|
|
" <div class=\"row\">\n"
|
|
" <input id=\"uploadFile\" type=\"file\" />\n"
|
|
" <button class=\"btn\" id=\"btnUpload\" type=\"button\">Upload</button>\n"
|
|
" </div>\n"
|
|
" <hr style=\"border:none;border-top:1px solid rgba(255,255,255,.10);margin:14px 0;\" />\n"
|
|
" <div class=\"muted\" style=\"margin-bottom:8px;\">Run</div>\n"
|
|
" <input id=\"programRef\" placeholder=\"program_ref (hex ref or concept name)\" />\n"
|
|
" <div class=\"muted\" style=\"margin:10px 0 8px;\">input_refs (comma-separated hex refs or names)</div>\n"
|
|
" <input id=\"inputRefs\" placeholder=\"in0,in1,...\" />\n"
|
|
" <div class=\"muted\" style=\"margin:10px 0 8px;\">params_ref (optional)</div>\n"
|
|
" <input id=\"paramsRef\" placeholder=\"params\" />\n"
|
|
" <div class=\"muted\" style=\"margin:10px 0 8px;\">scheme_ref (optional, default dag)</div>\n"
|
|
" <input id=\"schemeRef\" placeholder=\"dag\" />\n"
|
|
" <div class=\"row\" style=\"margin-top:12px;\">\n"
|
|
" <button class=\"btn primary\" id=\"btnRun\" type=\"button\">Run</button>\n"
|
|
" <a class=\"muted\" href=\"/v1/contract\">/v1/contract</a>\n"
|
|
" <a class=\"muted\" href=\"/v1/meta\">/v1/meta</a>\n"
|
|
" </div>\n"
|
|
" <div class=\"muted\" style=\"margin:14px 0 8px;\">Response</div>\n"
|
|
" <pre id=\"out\"></pre>\n"
|
|
" </div>\n"
|
|
" <div class=\"card\" id=\"relations\">\n"
|
|
" <div class=\"muted\" style=\"margin-bottom:8px;\">Relations</div>\n"
|
|
" <div class=\"row\" style=\"margin-top:10px;\">\n"
|
|
" <button class=\"btn\" id=\"btnRelations\" type=\"button\">Refresh</button>\n"
|
|
" </div>\n"
|
|
" <pre id=\"relationsOut\"></pre>\n"
|
|
" </div>\n"
|
|
" </div>\n"
|
|
" </section>\n"
|
|
" <section id=\"about\" class=\"grid\" style=\"margin-top:16px;\">\n"
|
|
" <article class=\"card span-6\">\n"
|
|
" <h2>About</h2>\n"
|
|
" <p class=\"muted\">amduatd is a local-first mapping surface over a single ASL store root. This UI is a lightweight editor and runner for concepts and PEL programs.</p>\n"
|
|
" </article>\n"
|
|
" <article class=\"card span-6\">\n"
|
|
" <h2>Links</h2>\n"
|
|
" <p class=\"muted\"><a href=\"/v1/contract\">/v1/contract</a> • <a href=\"/v1/meta\">/v1/meta</a> • <a href=\"/v1/relations\">/v1/relations</a></p>\n"
|
|
" </article>\n"
|
|
" </section>\n"
|
|
" </main>\n"
|
|
" <footer>\n"
|
|
" <span>© 2025 Niklas Rydberg.</span>\n"
|
|
" <span><a href=\"#title\">Back to top</a></span>\n"
|
|
" </footer>\n"
|
|
" </div>\n"
|
|
"\n"
|
|
" <script>\n"
|
|
" const el = (id) => document.getElementById(id);\n"
|
|
" const out = (v) => { el('out').textContent = typeof v === 'string' ? v : JSON.stringify(v, null, 2); };\n"
|
|
" const td = new TextDecoder('utf-8');\n"
|
|
" const te = new TextEncoder();\n"
|
|
" const toHex = (u8) => Array.from(u8).map(b => b.toString(16).padStart(2,'0')).join('');\n"
|
|
" const fromHex = (s) => { const t=(s||'').trim(); if(t.length%2) throw new Error('hex length must be even'); const o=new Uint8Array(t.length/2); for(let i=0;i<o.length;i++){o[i]=parseInt(t.slice(i*2,i*2+2),16);} return o; };\n"
|
|
" const toB64 = (u8) => { let bin=''; for(let i=0;i<u8.length;i++) bin += String.fromCharCode(u8[i]); return btoa(bin); };\n"
|
|
" const fromB64 = (s) => { const bin=atob((s||'').trim()); const o=new Uint8Array(bin.length); for(let i=0;i<bin.length;i++) o[i]=bin.charCodeAt(i); return o; };\n"
|
|
"\n"
|
|
" async function ensureConcept(name){\n"
|
|
" const resp = await fetch('/v1/concepts',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({name})});\n"
|
|
" if(resp.status === 409) return; // already exists\n"
|
|
" if(!resp.ok) throw new Error(await resp.text());\n"
|
|
" }\n"
|
|
"\n"
|
|
" async function loadConcept(){\n"
|
|
" const name = el('conceptName').value.trim();\n"
|
|
" if(!name){ out('missing concept name'); return; }\n"
|
|
" const resp = await fetch(`/v1/concepts/${encodeURIComponent(name)}`);\n"
|
|
" const text = await resp.text();\n"
|
|
" if(!resp.ok){ out(text); return; }\n"
|
|
" const j = JSON.parse(text);\n"
|
|
" el('latestRef').value = j.latest_ref || '';\n"
|
|
" el('programRef').value = name;\n"
|
|
" if(!j.latest_ref){ el('editor').value=''; out(text); return; }\n"
|
|
" const mode = el('mode').value;\n"
|
|
" const infoResp = await fetch(`/v1/artifacts/${j.latest_ref}?format=info`);\n"
|
|
" if(infoResp.ok){ const info = JSON.parse(await infoResp.text()); el('typeTag').value = info.has_type_tag ? info.type_tag : ''; }\n"
|
|
" if(mode === 'pel_program'){\n"
|
|
" if(!el('editor').value.trim()){\n"
|
|
" el('editor').value = JSON.stringify({\n"
|
|
" nodes:[{id:1,op:{name:'pel.bytes.concat',version:1},inputs:[{external:{input_index:0}}],params_hex:''}],\n"
|
|
" roots:[{node_id:1,output_index:0}]\n"
|
|
" }, null, 2);\n"
|
|
" }\n"
|
|
" out(text);\n"
|
|
" return;\n"
|
|
" }\n"
|
|
" const aResp = await fetch(`/v1/artifacts/${j.latest_ref}`);\n"
|
|
" if(!aResp.ok){ out(await aResp.text()); return; }\n"
|
|
" const u8 = new Uint8Array(await aResp.arrayBuffer());\n"
|
|
" if(mode==='hex') el('editor').value = toHex(u8);\n"
|
|
" else if(mode==='base64') el('editor').value = toB64(u8);\n"
|
|
" else el('editor').value = td.decode(u8);\n"
|
|
" out(text);\n"
|
|
" }\n"
|
|
"\n"
|
|
" async function saveConcept(){\n"
|
|
" const name = el('conceptName').value.trim();\n"
|
|
" if(!name){ out('missing concept name'); return; }\n"
|
|
" await ensureConcept(name);\n"
|
|
" const mode = el('mode').value;\n"
|
|
" if(mode === 'pel_program'){\n"
|
|
" const body = JSON.parse(el('editor').value || '{}');\n"
|
|
" const mkResp = await fetch('/v1/pel/programs',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)});\n"
|
|
" const mkText = await mkResp.text();\n"
|
|
" if(!mkResp.ok){ out(mkText); return; }\n"
|
|
" const mk = JSON.parse(mkText);\n"
|
|
" const pref = mk.program_ref;\n"
|
|
" const pubResp = await fetch(`/v1/concepts/${encodeURIComponent(name)}/publish`,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({ref:pref})});\n"
|
|
" const pubText = await pubResp.text();\n"
|
|
" out(pubText);\n"
|
|
" if(pubResp.ok){ el('typeTag').value = '0x00000101'; await loadConcept(); }\n"
|
|
" return;\n"
|
|
" }\n"
|
|
" let u8;\n"
|
|
" if(mode==='hex') u8 = fromHex(el('editor').value);\n"
|
|
" else if(mode==='base64') u8 = fromB64(el('editor').value);\n"
|
|
" else u8 = te.encode(el('editor').value);\n"
|
|
" const headers = {'Content-Type':'application/octet-stream'};\n"
|
|
" const typeTag = el('typeTag').value.trim();\n"
|
|
" if(typeTag) headers['X-Amduat-Type-Tag'] = typeTag;\n"
|
|
" const putResp = await fetch('/v1/artifacts',{method:'POST',headers,body:u8});\n"
|
|
" const putText = await putResp.text();\n"
|
|
" if(!putResp.ok){ out(putText); return; }\n"
|
|
" const put = JSON.parse(putText);\n"
|
|
" const pubResp = await fetch(`/v1/concepts/${encodeURIComponent(name)}/publish`,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({ref:put.ref})});\n"
|
|
" const pubText = await pubResp.text();\n"
|
|
" out(pubText);\n"
|
|
" if(pubResp.ok) await loadConcept();\n"
|
|
" }\n"
|
|
"\n"
|
|
" el('btnConceptCreate').addEventListener('click', async () => { try{ await ensureConcept(el('conceptName').value.trim()); out('{\"ok\":true}\\n'); }catch(e){ out(String(e)); } });\n"
|
|
" el('btnLoad').addEventListener('click', () => loadConcept().catch(e => out(String(e))));\n"
|
|
" el('btnSave').addEventListener('click', () => saveConcept().catch(e => out(String(e))));\n"
|
|
" el('mode').addEventListener('change', () => {\n"
|
|
" if(el('mode').value === 'pel_program'){\n"
|
|
" el('typeTag').value = '0x00000101';\n"
|
|
" }\n"
|
|
" loadConcept().catch(() => {});\n"
|
|
" });\n"
|
|
"\n"
|
|
" el('btnProgramTemplate').addEventListener('click', () => {\n"
|
|
" el('mode').value = 'pel_program';\n"
|
|
" el('typeTag').value = '0x00000101';\n"
|
|
" el('editor').value = JSON.stringify({\n"
|
|
" nodes:[{id:1,op:{name:'pel.bytes.concat',version:1},inputs:[{external:{input_index:0}}],params_hex:''}],\n"
|
|
" roots:[{node_id:1,output_index:0}]\n"
|
|
" }, null, 2);\n"
|
|
" });\n"
|
|
"\n"
|
|
" el('btnPublishRef').addEventListener('click', async () => {\n"
|
|
" try{\n"
|
|
" const name = el('conceptName').value.trim();\n"
|
|
" const ref = el('publishRef').value.trim();\n"
|
|
" if(!name||!ref){ out('missing name/ref'); return; }\n"
|
|
" await ensureConcept(name);\n"
|
|
" const resp = await fetch(`/v1/concepts/${encodeURIComponent(name)}/publish`,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({ref})});\n"
|
|
" out(await resp.text());\n"
|
|
" }catch(e){ out(String(e)); }\n"
|
|
" });\n"
|
|
"\n"
|
|
" el('btnUpload').addEventListener('click', async () => {\n"
|
|
" try {\n"
|
|
" const file = el('uploadFile').files && el('uploadFile').files[0];\n"
|
|
" if (!file) { out('no file selected'); return; }\n"
|
|
" const resp = await fetch('/v1/artifacts', { method:'POST', headers:{'Content-Type':'application/octet-stream'}, body:file });\n"
|
|
" const text = await resp.text();\n"
|
|
" out(text);\n"
|
|
" if (resp.ok) { const j = JSON.parse(text); if (j && j.ref) el('programRef').value = j.ref; }\n"
|
|
" } catch (e) { out(String(e)); }\n"
|
|
" });\n"
|
|
"\n"
|
|
" el('btnRun').addEventListener('click', async () => {\n"
|
|
" try {\n"
|
|
" const program_ref = el('programRef').value.trim();\n"
|
|
" const input_refs = (el('inputRefs').value || '').split(',').map(s => s.trim()).filter(Boolean);\n"
|
|
" const params_ref = el('paramsRef').value.trim();\n"
|
|
" const scheme_ref = el('schemeRef').value.trim();\n"
|
|
" const body = { program_ref, input_refs };\n"
|
|
" if (params_ref) body.params_ref = params_ref;\n"
|
|
" if (scheme_ref) body.scheme_ref = scheme_ref;\n"
|
|
" const resp = await fetch('/v1/pel/run', { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(body) });\n"
|
|
" out(await resp.text());\n"
|
|
" } catch (e) { out(String(e)); }\n"
|
|
" });\n"
|
|
"\n"
|
|
" async function loadRelations(){\n"
|
|
" const resp = await fetch('/v1/relations');\n"
|
|
" const text = await resp.text();\n"
|
|
" el('relationsOut').textContent = text;\n"
|
|
" }\n"
|
|
" el('btnRelations').addEventListener('click', () => loadRelations().catch(e => out(String(e))));\n"
|
|
" loadRelations().catch(() => {});\n"
|
|
" </script>\n"
|
|
"</body>\n"
|
|
"</html>\n";
|
|
|
|
bool amduatd_ui_can_handle(const amduatd_http_req_t *req) {
|
|
char no_query[1024];
|
|
|
|
if (req == NULL) {
|
|
return false;
|
|
}
|
|
if (strcmp(req->method, "GET") != 0) {
|
|
return false;
|
|
}
|
|
amduatd_path_without_query(req->path, no_query, sizeof(no_query));
|
|
return strcmp(no_query, "/v1/ui") == 0;
|
|
}
|
|
|
|
bool amduatd_ui_handle(amduatd_ctx_t *ctx,
|
|
const amduatd_http_req_t *req,
|
|
amduatd_http_resp_t *resp) {
|
|
amduat_artifact_t artifact;
|
|
amduat_asl_store_error_t err;
|
|
|
|
if (ctx == NULL || req == NULL || resp == NULL) {
|
|
return false;
|
|
}
|
|
if (!amduatd_ui_can_handle(req)) {
|
|
return false;
|
|
}
|
|
|
|
if (ctx->store == NULL || ctx->ui_ref.hash_id == 0 ||
|
|
ctx->ui_ref.digest.data == NULL || ctx->ui_ref.digest.len == 0) {
|
|
resp->ok = amduatd_http_send_text(resp->fd,
|
|
500,
|
|
"Internal Server Error",
|
|
"ui not available\n",
|
|
false);
|
|
return true;
|
|
}
|
|
memset(&artifact, 0, sizeof(artifact));
|
|
err = amduat_asl_store_get(ctx->store, ctx->ui_ref, &artifact);
|
|
if (err == AMDUAT_ASL_STORE_ERR_NOT_FOUND) {
|
|
resp->ok = amduatd_http_send_text(resp->fd,
|
|
404,
|
|
"Not Found",
|
|
"not found\n",
|
|
false);
|
|
return true;
|
|
}
|
|
if (err != AMDUAT_ASL_STORE_OK) {
|
|
amduat_asl_artifact_free(&artifact);
|
|
resp->ok = amduatd_http_send_text(resp->fd,
|
|
500,
|
|
"Internal Server Error",
|
|
"store error\n",
|
|
false);
|
|
return true;
|
|
}
|
|
if (artifact.bytes.len != 0 && artifact.bytes.data == NULL) {
|
|
amduat_asl_artifact_free(&artifact);
|
|
resp->ok = amduatd_http_send_text(resp->fd,
|
|
500,
|
|
"Internal Server Error",
|
|
"store error\n",
|
|
false);
|
|
return true;
|
|
}
|
|
|
|
resp->ok = amduatd_http_send_status(resp->fd,
|
|
200,
|
|
"OK",
|
|
"text/html; charset=utf-8",
|
|
artifact.bytes.data,
|
|
artifact.bytes.len,
|
|
false);
|
|
amduat_asl_artifact_free(&artifact);
|
|
return true;
|
|
}
|
|
|
|
bool amduatd_seed_ui_html(amduat_asl_store_t *store,
|
|
const amduat_asl_store_fs_config_t *cfg,
|
|
amduat_reference_t *out_ref) {
|
|
amduat_artifact_t artifact;
|
|
amduat_asl_store_error_t err;
|
|
|
|
if (out_ref != NULL) {
|
|
memset(out_ref, 0, sizeof(*out_ref));
|
|
}
|
|
if (store == NULL || cfg == NULL || out_ref == NULL) {
|
|
return false;
|
|
}
|
|
|
|
artifact = amduat_artifact(amduat_octets(k_amduatd_ui_html,
|
|
strlen(k_amduatd_ui_html)));
|
|
(void)amduat_asl_ref_derive(artifact,
|
|
cfg->config.encoding_profile_id,
|
|
cfg->config.hash_id,
|
|
out_ref,
|
|
NULL);
|
|
err = amduat_asl_store_put(store, artifact, out_ref);
|
|
if (err != AMDUAT_ASL_STORE_OK) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|