354 lines
22 KiB
HTML
354 lines
22 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>AERE Verification Console</title>
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500;600&display=swap">
|
|
<style>
|
|
:root{
|
|
color-scheme: dark;
|
|
--bg:#0a0e13; --panel:#111721; --panel2:#0e141d; --line:#1e2732; --line2:#2a3644;
|
|
--ink:#e7edf4; --mut:#8a99ab; --dim:#5f6e80;
|
|
--accent:#39d3c8; --accent-dim:#1b6d69;
|
|
--ok:#4bcf8e; --warn:#e3ab4d; --crit:#f2685f;
|
|
--shadow:0 1px 0 rgba(255,255,255,.02), 0 12px 30px -18px rgba(0,0,0,.8);
|
|
--mono:"IBM Plex Mono",ui-monospace,SFMono-Regular,Menlo,monospace;
|
|
--sans:"IBM Plex Sans",system-ui,-apple-system,Segoe UI,Roboto,sans-serif;
|
|
}
|
|
:root[data-theme="light"]{
|
|
color-scheme: light;
|
|
--bg:#f3f5f8; --panel:#ffffff; --panel2:#f7f9fb; --line:#e3e8ef; --line2:#d3dbe5;
|
|
--ink:#16202c; --mut:#5a6b7d; --dim:#8a99ab;
|
|
--accent:#0e928a; --accent-dim:#a7e6e1;
|
|
--ok:#12915c; --warn:#a9720a; --crit:#c93a30;
|
|
--shadow:0 1px 2px rgba(16,32,48,.06), 0 12px 28px -20px rgba(16,32,48,.35);
|
|
}
|
|
*{box-sizing:border-box}
|
|
body{margin:0;background:var(--bg);color:var(--ink);font-family:var(--sans);font-size:14px;line-height:1.5;-webkit-font-smoothing:antialiased}
|
|
.wrap{max-width:1000px;margin:0 auto;padding-inline:16px;padding-block:0 56px}
|
|
a{color:var(--accent)}
|
|
.mono{font-family:var(--mono);font-variant-ligatures:none}
|
|
.tnum{font-variant-numeric:tabular-nums}
|
|
|
|
/* header */
|
|
header{position:sticky;top:env(safe-area-inset-top,0px);z-index:5;background:color-mix(in srgb,var(--bg) 88%,transparent);backdrop-filter:blur(10px);border-bottom:1px solid var(--line)}
|
|
.hbar{max-width:1000px;margin:0 auto;padding:14px 16px;display:flex;align-items:center;gap:14px;flex-wrap:wrap}
|
|
.mark{display:flex;align-items:center;gap:10px;font-weight:700;letter-spacing:.02em}
|
|
.glyph{width:26px;height:26px;flex:none}
|
|
.mark .sub{font-weight:500;color:var(--mut);font-size:12px;letter-spacing:.06em;text-transform:uppercase}
|
|
.host{margin-left:auto;display:flex;align-items:center;gap:8px;color:var(--mut);font-size:12.5px}
|
|
.host .mono{color:var(--ink)}
|
|
.verdict{display:inline-flex;align-items:center;gap:8px;padding:7px 14px;border-radius:999px;font-weight:700;font-size:13px;letter-spacing:.03em;border:1px solid}
|
|
.verdict.ok{color:var(--ok);border-color:color-mix(in srgb,var(--ok) 45%,transparent);background:color-mix(in srgb,var(--ok) 12%,transparent)}
|
|
.verdict.bad{color:var(--crit);border-color:color-mix(in srgb,var(--crit) 45%,transparent);background:color-mix(in srgb,var(--crit) 12%,transparent)}
|
|
.dot{width:8px;height:8px;border-radius:50%;background:currentColor;box-shadow:0 0 0 4px color-mix(in srgb,currentColor 22%,transparent)}
|
|
|
|
/* intro line */
|
|
.lead{color:var(--mut);font-size:13px;margin:18px 0 4px}
|
|
.lead b{color:var(--ink);font-weight:600}
|
|
|
|
/* tiles */
|
|
.tiles{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin:16px 0 8px}
|
|
@media(max-width:760px){.tiles{grid-template-columns:repeat(2,1fr)}}
|
|
@media(max-width:420px){.tiles{grid-template-columns:1fr}}
|
|
.tile{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:14px 15px;box-shadow:var(--shadow);display:flex;flex-direction:column;gap:6px;min-height:98px}
|
|
.tile .k{font-size:11px;text-transform:uppercase;letter-spacing:.09em;color:var(--dim);display:flex;align-items:center;gap:7px}
|
|
.tile .v{font-size:24px;font-weight:700;font-family:var(--mono);line-height:1.1}
|
|
.tile .n{font-size:12px;color:var(--mut)}
|
|
.stripe{height:3px;border-radius:2px;margin-top:auto}
|
|
.s-ok{background:var(--ok)} .s-warn{background:var(--warn)} .s-crit{background:var(--crit)} .s-acc{background:var(--accent)}
|
|
.pin{width:7px;height:7px;border-radius:50%} .pin.ok{background:var(--ok)} .pin.warn{background:var(--warn)} .pin.crit{background:var(--crit)} .pin.acc{background:var(--accent)}
|
|
|
|
/* section */
|
|
section{margin-top:28px}
|
|
h2{font-size:13px;text-transform:uppercase;letter-spacing:.1em;color:var(--mut);font-weight:600;margin:0 0 12px;display:flex;align-items:center;gap:9px}
|
|
h2 .rule{height:1px;background:var(--line);flex:1}
|
|
|
|
/* chain */
|
|
.chain{display:flex;flex-direction:column;gap:0}
|
|
.node{position:relative;background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:13px 15px;box-shadow:var(--shadow)}
|
|
.node + .link{height:26px;margin-left:26px;border-left:2px dashed var(--line2);position:relative}
|
|
.node + .link::after{content:"prev";position:absolute;left:12px;top:50%;transform:translateY(-50%);font-family:var(--mono);font-size:10px;color:var(--dim);letter-spacing:.05em;background:var(--bg);padding:0 6px}
|
|
.node.head{border-color:color-mix(in srgb,var(--accent) 55%,var(--line));box-shadow:var(--shadow),0 0 0 1px color-mix(in srgb,var(--accent) 30%,transparent)}
|
|
.nrow{display:flex;align-items:center;gap:12px;flex-wrap:wrap}
|
|
.seq{font-family:var(--mono);font-weight:600;color:var(--accent);font-size:13px;min-width:30px}
|
|
.kind{display:inline-flex;align-items:center;gap:7px;font-weight:600;font-size:13.5px}
|
|
.kind .tag{font-family:var(--mono);font-size:10.5px;text-transform:uppercase;letter-spacing:.06em;color:var(--mut);border:1px solid var(--line2);border-radius:5px;padding:1px 6px}
|
|
.nname{color:var(--mut);font-size:12.5px}
|
|
.nat{margin-left:auto;color:var(--dim);font-size:11.5px;font-family:var(--mono)}
|
|
.headflag{font-size:10.5px;color:var(--accent);border:1px solid color-mix(in srgb,var(--accent) 45%,transparent);border-radius:999px;padding:1px 8px;letter-spacing:.05em;text-transform:uppercase}
|
|
.hashline{margin-top:9px;display:grid;grid-template-columns:auto 1fr auto;gap:8px 10px;align-items:center}
|
|
.hlab{font-family:var(--mono);font-size:10.5px;color:var(--dim);text-transform:uppercase;letter-spacing:.05em}
|
|
.hval{font-family:var(--mono);font-size:12px;color:var(--ink);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
.hval.dim{color:var(--mut)}
|
|
.copy{border:1px solid var(--line2);background:transparent;color:var(--mut);border-radius:6px;font-family:var(--mono);font-size:10.5px;padding:2px 8px;cursor:pointer;transition:.15s}
|
|
.copy:hover{color:var(--ink);border-color:var(--accent)}
|
|
.copy.done{color:var(--ok);border-color:var(--ok)}
|
|
|
|
/* ladder */
|
|
.ladder{display:grid;grid-template-columns:repeat(4,1fr);gap:10px}
|
|
@media(max-width:640px){.ladder{grid-template-columns:1fr 1fr}}
|
|
.step{border:1px solid var(--line);background:var(--panel2);border-radius:10px;padding:12px;display:flex;flex-direction:column;gap:6px;position:relative;opacity:.62}
|
|
.step.cur{opacity:1;background:var(--panel);border-color:color-mix(in srgb,var(--accent) 50%,var(--line));box-shadow:var(--shadow)}
|
|
.step .lv{font-family:var(--mono);font-size:11px;color:var(--dim)}
|
|
.step .nm{font-weight:600;font-size:13px}
|
|
.step .ds{font-size:11.5px;color:var(--mut)}
|
|
.step.cur .nm{color:var(--accent)}
|
|
|
|
/* migration bars */
|
|
.mig{display:grid;grid-template-columns:1.3fr 1fr;gap:14px}
|
|
@media(max-width:640px){.mig{grid-template-columns:1fr}}
|
|
.card{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:15px;box-shadow:var(--shadow)}
|
|
.bar{height:10px;border-radius:6px;background:var(--panel2);overflow:hidden;display:flex;margin:10px 0}
|
|
.bar i{display:block;height:100%}
|
|
.legend{display:flex;flex-wrap:wrap;gap:12px 16px;font-size:12px;color:var(--mut)}
|
|
.legend span{display:inline-flex;align-items:center;gap:7px}
|
|
.mrow{display:flex;justify-content:space-between;align-items:baseline;padding:7px 0;border-top:1px solid var(--line)}
|
|
.mrow:first-of-type{border-top:0}
|
|
.mrow .b{font-weight:600;color:var(--ink);font-family:var(--mono)}
|
|
|
|
/* paste */
|
|
.paste{margin-top:14px}
|
|
textarea{width:100%;min-height:120px;background:var(--panel2);color:var(--ink);border:1px solid var(--line);border-radius:10px;padding:12px;font-family:var(--mono);font-size:12px;resize:vertical}
|
|
textarea:focus{outline:2px solid color-mix(in srgb,var(--accent) 50%,transparent);outline-offset:1px;border-color:var(--accent)}
|
|
.row{display:flex;gap:10px;align-items:center;margin-top:10px;flex-wrap:wrap}
|
|
button.act{border:1px solid var(--accent-dim);background:color-mix(in srgb,var(--accent) 16%,transparent);color:var(--accent);font-family:var(--sans);font-weight:600;font-size:13px;padding:8px 16px;border-radius:8px;cursor:pointer}
|
|
button.act:hover{background:color-mix(in srgb,var(--accent) 26%,transparent)}
|
|
button.ghost{border:1px solid var(--line2);background:transparent;color:var(--mut)}
|
|
.err{color:var(--crit);font-size:12.5px;font-family:var(--mono)}
|
|
.foot{margin-top:34px;padding-top:16px;border-top:1px solid var(--line);color:var(--dim);font-size:11.5px;line-height:1.7}
|
|
.foot code{font-family:var(--mono);color:var(--mut)}
|
|
.warnbox{margin-top:12px;border:1px solid color-mix(in srgb,var(--crit) 45%,transparent);background:color-mix(in srgb,var(--crit) 10%,transparent);color:var(--crit);border-radius:10px;padding:11px 13px;font-size:12.5px}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<header>
|
|
<div class="hbar">
|
|
<div class="mark">
|
|
<svg class="glyph" viewBox="0 0 32 32" fill="none" aria-hidden="true">
|
|
<path d="M16 2 3 8v8c0 8 5.4 12.6 13 14 7.6-1.4 13-6 13-14V8L16 2Z" fill="none" stroke="var(--accent)" stroke-width="1.6"/>
|
|
<path d="M10.5 16.2 14.4 20l7.3-8.2" stroke="var(--accent)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
</svg>
|
|
<div>AERE Verification Console<div class="sub">quantum security control plane</div></div>
|
|
</div>
|
|
<div class="host">
|
|
<span>host</span><span class="mono" id="host">—</span>
|
|
<span id="verdict" class="verdict ok"><span class="dot"></span><span id="vtext">OK</span></span>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<div class="wrap">
|
|
<p class="lead">The state of a deployment, <b>checked again independently</b> by the control plane: the audit chain is recomputed from its entries, and the integrity of every AIP-23 envelope is recomputed. The console does not trust what the sidecar says about itself.</p>
|
|
|
|
<div class="tiles" id="tiles"></div>
|
|
|
|
<div id="alert"></div>
|
|
|
|
<section>
|
|
<h2>Audit chain <span class="rule"></span> <span id="chainmeta" class="mono" style="font-size:11px;color:var(--dim);text-transform:none;letter-spacing:0"></span></h2>
|
|
<div class="chain" id="chain"></div>
|
|
</section>
|
|
|
|
<section>
|
|
<h2>Post-quantum finality <span class="rule"></span></h2>
|
|
<div class="ladder" id="ladder"></div>
|
|
</section>
|
|
|
|
<section>
|
|
<h2>Post-quantum migration <span class="rule"></span></h2>
|
|
<div class="mig">
|
|
<div class="card">
|
|
<div style="font-weight:600;margin-bottom:2px">Migration plan</div>
|
|
<div class="n" style="color:var(--mut);font-size:12px">actions derived from the cryptographic inventory of the deployment</div>
|
|
<div class="bar" id="migbar"></div>
|
|
<div class="legend">
|
|
<span><i class="pin crit"></i> critical</span>
|
|
<span><i class="pin acc"></i> automatic</span>
|
|
<span><i class="pin warn"></i> manual</span>
|
|
</div>
|
|
</div>
|
|
<div class="card" id="migrows"></div>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="paste">
|
|
<h2>Render your own output <span class="rule"></span></h2>
|
|
<p class="lead" style="margin-top:0">Paste the output of <code class="mono" style="color:var(--ink)">node consola.mjs --bundle b.json --json</code> or a bundle from <code class="mono" style="color:var(--ink)">node sidecar.mjs bundle</code>.</p>
|
|
<textarea id="in" spellcheck="false" placeholder='{ "kind":"aere-control-plane-console", ... }'></textarea>
|
|
<div class="row">
|
|
<button class="act" id="render">Render</button>
|
|
<button class="act ghost" id="reset">Example</button>
|
|
<span class="err" id="perr" hidden></span>
|
|
</div>
|
|
</section>
|
|
|
|
<div class="foot">
|
|
A rendering of the output of the AUTHORITATIVE tools (<code>consola.mjs</code> and the sidecar). The verdict and the integrity of the chain and of the envelopes are computed by the tools, not in the browser: this page shows them as they are. The audit chain is an append-only hash chain where every entry covers <code>sha256(seq | prev | envelope)</code>; its head can be given post-quantum finality by notarizing it on AereNotary (Aere Network). The envelopes follow the AERE Proof Protocol (AIP-23). The data shown on opening is an example deployment generated by the tools; the finality step marked is the one of that example (a head that can be notarized and is not yet). For output you paste, the console does not check finality, so no step is marked.
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
const GEN = "0x"+"00".repeat(32);
|
|
const short = h => !h ? "—" : (h.length>22 ? h.slice(0,12)+"…"+h.slice(-8) : h);
|
|
const KINDNAME = {
|
|
"aere-proof-of-runtime":["Runtime","the running binary is the attested one"],
|
|
"aere-proof-of-data":["Deployment","the deployed artifact, as a digest"],
|
|
"aere-audit-head":["Log head","can be notarized"],
|
|
};
|
|
|
|
// an example generated by the verification layer and the control plane (console output format 2)
|
|
const SAMPLE = {
|
|
v:2, kind:"aere-control-plane-console", host:"aws-eu-prod-1",
|
|
auditChain:{ok:true,count:3,head:"0x5f63faec605908668fee3703303b006e2ebe66b3318b9fc8d1f01e86edfb41ee",brokenAtSeq:null,reason:null},
|
|
envelopes:{total:3,intact:3,bad:[]},
|
|
selfReportSuspect:null,
|
|
migration:{total:3,critical:2,auto:2,manual:1,blocked:0},
|
|
execution:null,
|
|
verdict:"OK",
|
|
entries:[
|
|
{seq:0,kind:"aere-proof-of-runtime",at:"2026-09-26T08:00:00Z",name:"aws-eu-prod-1",statementHash:"0x1c0183e10520f70884dea2947c763413f8514606026e9fe294bc4cfdb380be67",hash:"0x6168463c93c47acbd8b40f02ed5089e87a77c07a0a2e8a431026bd7f765f8969",prev:GEN},
|
|
{seq:1,kind:"aere-proof-of-data",at:"2026-09-26T08:05:00Z",name:"checkout-api@4.2.1",statementHash:"0x5fa286c04da5fe37f4a9c9963999b4162926d81064b95af28a91a665f86b2c68",hash:"0xf2ff1835e76511a902ec02f8fa523a455d114743e1e916abe11cfc8b822579e8",prev:"0x6168463c93c47acbd8b40f02ed5089e87a77c07a0a2e8a431026bd7f765f8969"},
|
|
{seq:2,kind:"aere-proof-of-runtime",at:"2026-09-26T08:10:00Z",name:"aws-eu-prod-1",statementHash:"0xa9358d663d8ec8b3a59c7db6a2eab2ed842d2adac61f6d84d43657a7b831c4d5",hash:"0x5f63faec605908668fee3703303b006e2ebe66b3318b9fc8d1f01e86edfb41ee",prev:"0xf2ff1835e76511a902ec02f8fa523a455d114743e1e916abe11cfc8b822579e8"}
|
|
]
|
|
};
|
|
|
|
function normalize(o){
|
|
// accepts a console state OR a sidecar bundle (it has entries + chainOk); a bundle is shown as the bundle declares it, since the
|
|
// browser does not recompute anything: run consola.mjs on it for a checked verdict
|
|
if(o.kind==="aere-verify-layer-bundle" || (o.entries && !o.auditChain)){
|
|
return {
|
|
kind:"aere-control-plane-console", host:o.host||"?",
|
|
auditChain:{ok:!!o.chainOk,count:o.count??(o.entries||[]).length,head:o.head||null,brokenAtSeq:null,reason:null},
|
|
envelopes:{total:(o.entries||[]).length,intact:(o.entries||[]).length,bad:[]},
|
|
selfReportSuspect:null, migration:null, execution:null, verdict:"UNCHECKED",
|
|
note:"A bundle pasted here is shown as it declares itself (chainOk="+(!!o.chainOk)+"): the browser checks nothing. Run node consola.mjs --bundle on it for a checked verdict.",
|
|
entries:(o.entries||[]).map(e=>({seq:e.seq,kind:e.proof?.statement?.kind||"?",at:e.proof?.statement?.createdAt||"",name:e.proof?.statement?.name||e.proof?.statement?.host||"",statementHash:e.proof?.statementHash||"",hash:e.hash,prev:e.prev}))
|
|
};
|
|
}
|
|
return o;
|
|
}
|
|
|
|
function el(t,c,x){const n=document.createElement(t);if(c)n.className=c;if(x!=null)n.textContent=x;return n;}
|
|
|
|
function copyBtn(text){
|
|
const b=el("button","copy","copy");
|
|
b.addEventListener("click",async()=>{
|
|
try{await navigator.clipboard.writeText(text);}catch(e){/* the clipboard is not available here */}
|
|
b.textContent="copied";b.classList.add("done");setTimeout(()=>{b.textContent="copy";b.classList.remove("done");},1200);
|
|
});
|
|
return b;
|
|
}
|
|
|
|
// `exemplu`: numai exemplul are o treapta de finalitate cunoscuta (capul lui nu e notarizat); pentru o iesire lipita consola nu masoara
|
|
// finalitatea, deci nu se marcheaza nicio treapta si caseta spune ca nu e verificata aici (2026-09-30, B-24)
|
|
function render(d, exemplu=false){
|
|
d=normalize(d);
|
|
const ok = d.verdict==="OK";
|
|
document.getElementById("host").textContent=d.host||"?";
|
|
const v=document.getElementById("verdict"); v.className="verdict "+(ok?"ok":"bad");
|
|
document.getElementById("vtext").textContent=d.verdict||"?";
|
|
|
|
// tiles
|
|
const ac=d.auditChain||{}, pl=d.envelopes||{}, mg=d.migration, ex=d.execution;
|
|
const bad=(pl.bad&&pl.bad.length);
|
|
const tiles=[
|
|
{k:"Audit chain",pin:ac.ok?"ok":"crit",v:ac.ok?"INTACT":"BROKEN",n:(ac.count??0)+" entries"+(ac.ok?"":" · broken at seq "+ac.brokenAtSeq),s:ac.ok?"s-ok":"s-crit"},
|
|
{k:"AIP-23 envelopes",pin:bad?"crit":"ok",v:(pl.intact??0)+"/"+(pl.total??0),n:bad?"intact · bad: "+pl.bad.map(x=>x.seq).join(","):"intact",s:bad?"s-crit":"s-ok"},
|
|
{k:"PQ migration",pin:mg?(mg.critical?"crit":"acc"):"acc",v:mg&&mg.total!=null?String(mg.total):"—",n:mg?(mg.total==null?(mg.error||"plan not readable"):(mg.critical+" critical · "+mg.auto+" automatic")):"no plan attached",s:mg&&mg.critical?"s-crit":"s-acc"},
|
|
ex?{k:"Execution",pin:ex.chainOk?(ex.failed?"crit":"ok"):"crit",v:ex.chainOk?"INTACT":"BROKEN",n:ex.actions+" actions · "+ex.ok+" OK · "+ex.failed+" failed",s:ex.chainOk&&!ex.failed?"s-ok":"s-crit"}
|
|
:(exemplu?{k:"Finality",pin:"warn",v:"ready",n:"example: the head can be notarized (not notarized)",s:"s-warn"}
|
|
:{k:"Finality",pin:"warn",v:"not checked",n:"the console does not check finality: notarize the head, then check it with the AIP-23 verifier",s:"s-warn"}),
|
|
];
|
|
const T=document.getElementById("tiles");T.innerHTML="";
|
|
for(const t of tiles){
|
|
const c=el("div","tile");
|
|
const k=el("div","k");k.append(el("i","pin "+t.pin),document.createTextNode(t.k));
|
|
c.append(k,el("div","v",t.v),el("div","n",t.n),el("div","stripe "+t.s));
|
|
T.append(c);
|
|
}
|
|
|
|
// alert
|
|
const A=document.getElementById("alert");A.innerHTML="";
|
|
if(d.selfReportSuspect){const w=el("div","warnbox");w.textContent="Suspect self-report: "+d.selfReportSuspect;A.append(w);}
|
|
if(d.note){const w=el("div","warnbox");w.textContent=d.note;A.append(w);}
|
|
|
|
// chain
|
|
const C=document.getElementById("chain");C.innerHTML="";
|
|
const cm=document.getElementById("chainmeta");
|
|
cm.textContent = ac.head ? ("head "+short(ac.head)) : "";
|
|
const entries=d.entries||[];
|
|
if(!entries.length){
|
|
C.append(el("div","card","No per-entry detail (only the console summary was pasted). Paste a bundle from the sidecar for the whole chain."));
|
|
}
|
|
entries.forEach((e,i)=>{
|
|
if(i>0){C.append(el("div","link"));}
|
|
const isHead = e.hash===ac.head;
|
|
const n=el("div","node"+(isHead?" head":""));
|
|
const row=el("div","nrow");
|
|
row.append(el("span","seq","#"+e.seq));
|
|
const kn=KINDNAME[e.kind]||[e.kind,""];
|
|
const kind=el("span","kind");kind.append(document.createTextNode(kn[0]));const tag=el("span","tag",String(e.kind).replace("aere-proof-of-","").replace("aere-",""));kind.append(tag);
|
|
row.append(kind);
|
|
if(e.name)row.append(el("span","nname",e.name));
|
|
if(isHead)row.append(el("span","headflag","head"));
|
|
row.append(el("span","nat",(e.at||"").replace("T"," ").replace("Z","")));
|
|
n.append(row);
|
|
const hl=el("div","hashline");
|
|
hl.append(el("span","hlab","hash"));const hv=el("span","hval",e.hash||"—");hl.append(hv,copyBtn(e.hash||""));
|
|
hl.append(el("span","hlab","prev"));const pv=el("span","hval dim",e.prev===GEN?"genesis 0x00…00":short(e.prev));hl.append(pv,el("span"));
|
|
n.append(hl);
|
|
C.append(n);
|
|
});
|
|
|
|
// ladder: the finality steps of the AIP-23 verifier; the step marked is the one of the example (a head not yet notarized)
|
|
const steps=[
|
|
["1","not notarized","the statementHash of the head is not on AereNotary yet"],
|
|
["2","pending","a notarization transaction was seen, not yet under an anchor"],
|
|
["3","anchor without certificate","in a block, under an anchor without a complete post-quantum certificate"],
|
|
["4","post-quantum","under an anchor with a Falcon / SLH-DSA certificate"],
|
|
];
|
|
const cur = exemplu ? 1 : 0;
|
|
const L=document.getElementById("ladder");L.innerHTML="";
|
|
steps.forEach((s,i)=>{
|
|
const st=el("div","step"+(i+1===cur?" cur":""));
|
|
st.append(el("div","lv","step "+s[0]),el("div","nm",s[1]),el("div","ds",s[2]));
|
|
L.append(st);
|
|
});
|
|
|
|
// migration
|
|
const mb=document.getElementById("migbar");mb.innerHTML="";mb.style.display="";
|
|
const mr=document.getElementById("migrows");mr.innerHTML="";
|
|
if(mg && mg.total){
|
|
// the bar: critical / automatic / manual scaled to their sum (a visual approximation over categories of urgency and method)
|
|
const parts=[["var(--crit)",mg.critical,"critical"],["var(--accent)",mg.auto,"automatic"],["var(--warn)",mg.manual,"manual"]];
|
|
const sum=parts.reduce((a,p)=>a+p[1],0)||1;
|
|
for(const p of parts){const i=el("i");i.style.background=p[0];i.style.width=(p[1]/sum*100)+"%";mb.append(i);}
|
|
for(const [lab,val] of [["Total actions",mg.total],["Critical",mg.critical],["Automatic",mg.auto],["Manual",mg.manual],["Blocked",mg.blocked??0]]){
|
|
const r=el("div","mrow");r.append(el("span",null,lab),el("span","b tnum",String(val)));mr.append(r);
|
|
}
|
|
} else {
|
|
mb.style.display="none";
|
|
mr.append(el("div","n","No migration plan attached. Run node consola.mjs --bundle b.json --plan plan.json."));
|
|
}
|
|
}
|
|
|
|
document.getElementById("render").addEventListener("click",()=>{
|
|
const e=document.getElementById("perr");e.hidden=true;
|
|
try{ const o=JSON.parse(document.getElementById("in").value.trim()); render(o); }
|
|
catch(err){ e.hidden=false; e.textContent="invalid JSON: "+err.message; }
|
|
});
|
|
document.getElementById("reset").addEventListener("click",()=>{
|
|
document.getElementById("in").value=JSON.stringify(SAMPLE,null,1);
|
|
document.getElementById("perr").hidden=true; render(SAMPLE, true);
|
|
});
|
|
|
|
render(SAMPLE, true);
|
|
</script>
|
|
</body>
|
|
</html>
|