Spaces:
Running
Running
v0.3: App Store tab (VDR packages, live review)
Browse files- ag.js +7 -2
- index.html +1 -0
- samples/weather-plus-tampered.vdr +0 -0
- samples/weather-plus.vdr +0 -0
- server.py +4 -4
- store.js +127 -0
ag.js
CHANGED
|
@@ -128,7 +128,11 @@ window.VK_AG = ({el, t, toast}) => ({
|
|
| 128 |
finally { lock(false); refresh(); } };
|
| 129 |
msg.addEventListener('keydown', e => { if (e.key === 'Enter' && !e.isComposing) send(); });
|
| 130 |
const btn = (k, f, cls) => el('button', {class:'btn ' + (cls || ''), onclick:f}, t(k));
|
| 131 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
el('div', {class:'agcol'},
|
| 133 |
el('div', {class:'row', style:'margin-bottom:6px;flex-wrap:wrap;gap:6px'}, el('b', {}, t('agChat')), mode, el('span', {class:'tag none', id:'agModel'}, 'Meta Muse Spark 1.3'), sel),
|
| 134 |
el('div', {class:'row', style:'flex-wrap:wrap;gap:6px;margin-bottom:6px'}, ['a_normal', 'b_injection', 'c_persuasion', 'd_kill', 'e_tamper'].map((s, i) => btn('ag' + 'ABCDE'[i], () => scen(s)))),
|
|
@@ -158,13 +162,14 @@ window.VK_AG = ({el, t, toast}) => ({
|
|
| 158 |
});
|
| 159 |
|
| 160 |
const css = document.createElement('style');
|
| 161 |
-
css.textContent = `.aggrid{display:grid;grid-template-columns:minmax(0,1.15fr) minmax(0,1fr);gap:12px;height:calc(100% -
|
| 162 |
.agcol .btn{white-space:nowrap;flex:0 0 auto}.agcol .row{flex-wrap:wrap}
|
| 163 |
.agcol{display:flex;flex-direction:column;min-width:0;min-height:0}
|
| 164 |
.agchat{flex:1;min-height:120px;overflow:auto;border:1px solid var(--border);border-radius:10px;padding:8px;background:var(--bg)}
|
| 165 |
.agstream{flex:1;min-height:120px;overflow:auto}.agaudit{max-height:150px;overflow:auto}
|
| 166 |
.agm{margin:5px 0;padding:7px 9px;border-radius:9px;white-space:pre-wrap;word-break:break-word;font-size:13px}
|
| 167 |
.agm.u{background:#EEF2FF}.agm.a{background:var(--card);border:1px solid var(--border)}.agm.s{color:var(--muted);font-size:12px;padding:2px 9px}
|
|
|
|
| 168 |
.agstats{padding:6px 8px;border:1px dashed var(--border);border-radius:9px;margin-bottom:6px}.agv{margin:5px 0;padding:8px 10px}.aginput{flex:1;min-width:0;height:34px;padding:0 10px;border:1px solid var(--border);border-radius:9px;background:var(--card)}
|
| 169 |
@media (max-width:700px){.aggrid{grid-template-columns:1fr;height:auto}}`;
|
| 170 |
document.head.append(css);
|
|
|
|
| 128 |
finally { lock(false); refresh(); } };
|
| 129 |
msg.addEventListener('keydown', e => { if (e.key === 'Enter' && !e.isComposing) send(); });
|
| 130 |
const btn = (k, f, cls) => el('button', {class:'btn ' + (cls || ''), onclick:f}, t(k));
|
| 131 |
+
const agentPane = el('div', {class:'agpane'}); const storePane = el('div', {class:'agpane', style:'display:none'});
|
| 132 |
+
const tabs = el('div', {class:'agtabs'}, el('button', {class:'agtab on', 'data-tab':'agent'}, t('agTabAgent')), el('button', {class:'agtab', 'data-tab':'store'}, t('agTabStore')));
|
| 133 |
+
tabs.onclick = e => { const k = e.target.dataset.tab; if (!k) return; tabs.querySelectorAll('.agtab').forEach(x => x.classList.toggle('on', x.dataset.tab === k)); agentPane.style.display = k === 'agent' ? '' : 'none'; storePane.style.display = k === 'store' ? '' : 'none'; if (k === 'store') window.VK_STORE(storePane, {el, t, toast, call:(p, bd) => call(p, bd, true), live:() => LIVE}); };
|
| 134 |
+
b.append(tabs, agentPane, storePane);
|
| 135 |
+
agentPane.append(el('div', {class:'aggrid'},
|
| 136 |
el('div', {class:'agcol'},
|
| 137 |
el('div', {class:'row', style:'margin-bottom:6px;flex-wrap:wrap;gap:6px'}, el('b', {}, t('agChat')), mode, el('span', {class:'tag none', id:'agModel'}, 'Meta Muse Spark 1.3'), sel),
|
| 138 |
el('div', {class:'row', style:'flex-wrap:wrap;gap:6px;margin-bottom:6px'}, ['a_normal', 'b_injection', 'c_persuasion', 'd_kill', 'e_tamper'].map((s, i) => btn('ag' + 'ABCDE'[i], () => scen(s)))),
|
|
|
|
| 162 |
});
|
| 163 |
|
| 164 |
const css = document.createElement('style');
|
| 165 |
+
css.textContent = `.aggrid{display:grid;grid-template-columns:minmax(0,1.15fr) minmax(0,1fr);gap:12px;height:calc(100% - 26px)}
|
| 166 |
.agcol .btn{white-space:nowrap;flex:0 0 auto}.agcol .row{flex-wrap:wrap}
|
| 167 |
.agcol{display:flex;flex-direction:column;min-width:0;min-height:0}
|
| 168 |
.agchat{flex:1;min-height:120px;overflow:auto;border:1px solid var(--border);border-radius:10px;padding:8px;background:var(--bg)}
|
| 169 |
.agstream{flex:1;min-height:120px;overflow:auto}.agaudit{max-height:150px;overflow:auto}
|
| 170 |
.agm{margin:5px 0;padding:7px 9px;border-radius:9px;white-space:pre-wrap;word-break:break-word;font-size:13px}
|
| 171 |
.agm.u{background:#EEF2FF}.agm.a{background:var(--card);border:1px solid var(--border)}.agm.s{color:var(--muted);font-size:12px;padding:2px 9px}
|
| 172 |
+
.agtabs{display:flex;gap:4px;margin:-4px 0 8px;border-bottom:1px solid var(--border)}.agtab{border:0;background:none;padding:6px 14px;font-weight:700;cursor:pointer;border-bottom:3px solid transparent;color:var(--muted)}.agtab.on{color:var(--indigo);border-bottom-color:var(--indigo)}.agpane{height:calc(100% - 40px)}
|
| 173 |
.agstats{padding:6px 8px;border:1px dashed var(--border);border-radius:9px;margin-bottom:6px}.agv{margin:5px 0;padding:8px 10px}.aginput{flex:1;min-width:0;height:34px;padding:0 10px;border:1px solid var(--border);border-radius:9px;background:var(--card)}
|
| 174 |
@media (max-width:700px){.aggrid{grid-template-columns:1fr;height:auto}}`;
|
| 175 |
document.head.append(css);
|
index.html
CHANGED
|
@@ -78,6 +78,7 @@
|
|
| 78 |
|
| 79 |
<script src="i18n.js"></script>
|
| 80 |
<script src="demo_data.js"></script>
|
|
|
|
| 81 |
<script src="ag.js"></script>
|
| 82 |
<script src="app.js"></script>
|
| 83 |
</body>
|
|
|
|
| 78 |
|
| 79 |
<script src="i18n.js"></script>
|
| 80 |
<script src="demo_data.js"></script>
|
| 81 |
+
<script src="store.js"></script>
|
| 82 |
<script src="ag.js"></script>
|
| 83 |
<script src="app.js"></script>
|
| 84 |
</body>
|
samples/weather-plus-tampered.vdr
ADDED
|
Binary file (1.65 kB). View file
|
|
|
samples/weather-plus.vdr
ADDED
|
Binary file (1.65 kB). View file
|
|
|
server.py
CHANGED
|
@@ -9,9 +9,9 @@ HF_AUTH = os.environ.get("BACKEND_HF_TOKEN", "")
|
|
| 9 |
BK_TOKEN = os.environ.get("BACKEND_TOKEN", "")
|
| 10 |
RATE = int(os.environ.get("RATE_PER_10MIN", "20"))
|
| 11 |
TIMEOUT = float(os.environ.get("BACKEND_TIMEOUT", "300"))
|
| 12 |
-
GET_OK = {"health", "status", "audit"}
|
| 13 |
-
POST_OK = {"chat", "scenario", "kill", "resume", "tamper", "reset"}
|
| 14 |
-
STATIC = {"index.html", "app.js", "ag.js", "i18n.js", "demo_data.js", "styles.css", "ag_replay.json"}
|
| 15 |
HITS = {}
|
| 16 |
|
| 17 |
|
|
@@ -75,7 +75,7 @@ class H(SimpleHTTPRequestHandler):
|
|
| 75 |
if limited(ip):
|
| 76 |
return self._json(429, {"error": "rate limit"})
|
| 77 |
n = int(self.headers.get("Content-Length", 0) or 0)
|
| 78 |
-
if n > 4096:
|
| 79 |
return self._json(413, {"error": "too large"})
|
| 80 |
try:
|
| 81 |
req = json.loads(self.rfile.read(n) or b"{}")
|
|
|
|
| 9 |
BK_TOKEN = os.environ.get("BACKEND_TOKEN", "")
|
| 10 |
RATE = int(os.environ.get("RATE_PER_10MIN", "20"))
|
| 11 |
TIMEOUT = float(os.environ.get("BACKEND_TIMEOUT", "300"))
|
| 12 |
+
GET_OK = {"health", "status", "audit", "store/catalog"}
|
| 13 |
+
POST_OK = {"chat", "scenario", "kill", "resume", "tamper", "reset", "store/submit"}
|
| 14 |
+
STATIC = {"index.html", "app.js", "ag.js", "store.js", "samples/weather-plus.vdr", "samples/weather-plus-tampered.vdr", "i18n.js", "demo_data.js", "styles.css", "ag_replay.json"}
|
| 15 |
HITS = {}
|
| 16 |
|
| 17 |
|
|
|
|
| 75 |
if limited(ip):
|
| 76 |
return self._json(429, {"error": "rate limit"})
|
| 77 |
n = int(self.headers.get("Content-Length", 0) or 0)
|
| 78 |
+
if n > (700000 if name == "store/submit" else 4096):
|
| 79 |
return self._json(413, {"error": "too large"})
|
| 80 |
try:
|
| 81 |
req = json.loads(self.rfile.read(n) or b"{}")
|
store.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* ViKernel App Store tab (inside Agent Guard): Developer Guide, Submit for Review (live checks), Catalog.
|
| 2 |
+
Packages use the VDR package format (.vdr). Built during the hackathon. */
|
| 3 |
+
(() => {
|
| 4 |
+
'use strict';
|
| 5 |
+
const S = {
|
| 6 |
+
en: {
|
| 7 |
+
agTabAgent: 'Agent', agTabStore: 'App Store',
|
| 8 |
+
stGuide: 'Developer Guide', stSubmit: 'Submit for Review', stCatalog: 'Catalog',
|
| 9 |
+
stIntro: 'Agent apps ship as VDR packages (.vdr). ViKernel reviews every package live and countersigns the ones it lists. Only countersigned packages install.',
|
| 10 |
+
stFormat: 'VDR package format v1', stFormatBody: 'A .vdr file is a zip with exactly: manifest.json, app/ (tool code or a workflow), icon.png, META-VK/HASHES (sha256 of every file), META-VK/SIGNATURE (publisher ed25519 over HASHES), META-VK/PUBLISHER (publisher id and public key). After review the store adds META-VK/STORE_SIG. Any extra, missing or changed file is rejected: one changed byte and the package does not install, the same rule as ViKernel boot integrity.',
|
| 11 |
+
stManifest: 'Manifest', stCli: 'Four commands (vdrpack.py, in our public repo)',
|
| 12 |
+
stEnforce: 'How ViKernel enforces it',
|
| 13 |
+
stE1: 'Undeclared actions are denied: a tool, domain or path that is not in the manifest never runs.',
|
| 14 |
+
stE2: 'The kill switch sits above every app. When the kernel halts, every app stops; only the operator key resumes.',
|
| 15 |
+
stE3: 'Every app action is decided by the kernel and sealed in the audit chain.',
|
| 16 |
+
stE4: 'Demo keys only. The store key never leaves the server.',
|
| 17 |
+
stPick: 'Upload a .vdr package', stSampleOk: 'Use sample: weather-plus.vdr', stSampleBad: 'Use sample with one byte changed',
|
| 18 |
+
stRunning: 'Reviewing live...', stPass: 'PASS: listed in the catalog', stRej: 'REJECTED', stDownload: 'Download the countersigned package',
|
| 19 |
+
stProbe: 'probe', stKernel: 'kernel', stVerdict: 'verdict',
|
| 20 |
+
stPaid: 'Paid listings coming soon.', stBadge: 'Verified by ViKernel', stFree: 'Free', stPerms: 'permissions', stRisk: 'risk',
|
| 21 |
+
stNeedLive: 'The App Store needs the live backend. It is starting or busy; try again in a minute.',
|
| 22 |
+
},
|
| 23 |
+
ko: {
|
| 24 |
+
agTabAgent: 'μμ΄μ νΈ', agTabStore: 'μ± μ€ν μ΄',
|
| 25 |
+
stGuide: 'κ°λ°μ μλ΄', stSubmit: 'μ¬μ¬ μ μΆ', stCatalog: 'μΉ΄νλ‘κ·Έ',
|
| 26 |
+
stIntro: 'μμ΄μ νΈ μ±μ VDR ν¨ν€μ§(.vdr)λ‘ λ°°ν¬ν©λλ€. ViKernel μ΄ λͺ¨λ ν¨ν€μ§λ₯Ό μ€μκ°μΌλ‘ μ¬μ¬νκ³ , λ±μ¬νλ ν¨ν€μ§μ 곡λ μλͺ
ν©λλ€. 곡λ μλͺ
λ ν¨ν€μ§λ§ μ€μΉλ©λλ€.',
|
| 27 |
+
stFormat: 'VDR ν¨ν€μ§ νμ v1', stFormatBody: '.vdr νμΌμ μ νν λ€μμ λ΄μ zip μ
λλ€: manifest.json, app/(λꡬ μ½λλ μν¬νλ‘), icon.png, META-VK/HASHES(λͺ¨λ νμΌμ sha256), META-VK/SIGNATURE(HASHES μ λν κ²μμ ed25519 μλͺ
), META-VK/PUBLISHER(κ²μμ id μ 곡κ°ν€). μ¬μ¬ λ€ μ€ν μ΄κ° META-VK/STORE_SIG λ₯Ό λν©λλ€. νμΌμ΄ λ μκ±°λ λΉ μ§κ±°λ λ°λλ©΄ κ±°λΆν©λλ€. 1λ°μ΄νΈλ§ λ°λμ΄λ μ€μΉλμ§ μμ΅λλ€. ViKernel λΆν
무결μ±κ³Ό κ°μ κ·μΉμ
λλ€.',
|
| 28 |
+
stManifest: 'λ§€λνμ€νΈ', stCli: 'λͺ
λ Ή 4κ° (κ³΅κ° μ μ₯μμ vdrpack.py)',
|
| 29 |
+
stEnforce: 'ViKernel μ΄ μ§ν€λ λ°©λ²',
|
| 30 |
+
stE1: 'μ μΈνμ§ μμ νλμ κ±°λΆν©λλ€. λ§€λνμ€νΈμ μλ λꡬ·λλ©μΈΒ·κ²½λ‘λ μ€νλμ§ μμ΅λλ€.',
|
| 31 |
+
stE2: 'μ μ§ μ€μμΉλ λͺ¨λ μ± μμ μμ΅λλ€. 컀λμ΄ μ μ§νλ©΄ λͺ¨λ μ±μ΄ λ©μΆκ³ , μ΄μμ μ΄μ λ‘λ§ μ¬κ°ν©λλ€.',
|
| 32 |
+
stE3: 'μ±μ λͺ¨λ νλμ 컀λμ΄ νμ νκ³ κ°μ¬ μ¬μ¬μ λ΄μΈν©λλ€.',
|
| 33 |
+
stE4: 'μμ°μ© ν€λ§ μλλ€. μ€ν μ΄ ν€λ μλ² λ°μΌλ‘ λκ°μ§ μμ΅λλ€.',
|
| 34 |
+
stPick: '.vdr ν¨ν€μ§ μ¬λ¦¬κΈ°', stSampleOk: '견본 μ¬μ©: weather-plus.vdr', stSampleBad: '1λ°μ΄νΈ λ°κΎΌ 견본 μ¬μ©',
|
| 35 |
+
stRunning: 'μ€μκ° μ¬μ¬ μ€...', stPass: 'ν΅κ³Ό: μΉ΄νλ‘κ·Έμ λ±μ¬λ¨', stRej: 'κ±°λΆ', stDownload: '곡λ μλͺ
λ ν¨ν€μ§ λ΄λ €λ°κΈ°',
|
| 36 |
+
stProbe: 'νμΉ¨', stKernel: '컀λ', stVerdict: 'νμ ',
|
| 37 |
+
stPaid: 'μ λ£ λ±μ¬λ κ³§ μ΄λ¦½λλ€.', stBadge: 'ViKernel κ²μ¦', stFree: '무λ£', stPerms: 'κΆν', stRisk: 'μνλ',
|
| 38 |
+
stNeedLive: 'μ± μ€ν μ΄λ μ€μκ° λ°±μλκ° νμν©λλ€. μμ μ€μ΄κ±°λ λ°μ©λλ€. 1λΆ λ€ λ€μ μλνμΈμ.',
|
| 39 |
+
},
|
| 40 |
+
};
|
| 41 |
+
for (const l of ['en', 'ko']) Object.assign(window.VK_I18N[l], S[l]);
|
| 42 |
+
|
| 43 |
+
const MANIFEST = `{
|
| 44 |
+
"name": "Weather Plus",
|
| 45 |
+
"version": "1.0.0",
|
| 46 |
+
"publisher": "acme-demo",
|
| 47 |
+
"description": "Weather with a short forecast note",
|
| 48 |
+
"permissions": {
|
| 49 |
+
"tools": ["get_weather", "search_web"],
|
| 50 |
+
"egress": [],
|
| 51 |
+
"paths": []
|
| 52 |
+
},
|
| 53 |
+
"rate_limits": {"per_minute": 30}
|
| 54 |
+
}`;
|
| 55 |
+
const CLI = `python vdrpack.py init weather-plus --name "Weather Plus" --publisher acme-demo
|
| 56 |
+
python vdrpack.py build weather-plus weather-plus.vdr
|
| 57 |
+
python vdrpack.py sign weather-plus.vdr weather-plus/publisher_demo.key acme-demo
|
| 58 |
+
python vdrpack.py verify weather-plus.vdr`;
|
| 59 |
+
|
| 60 |
+
window.VK_STORE = (pane, {el, t, toast, call, live}) => {
|
| 61 |
+
if (pane.dataset.built) { pane.querySelector('[data-sec=catalog]') && pane.__loadCat && pane.__loadCat(); return; }
|
| 62 |
+
pane.dataset.built = '1';
|
| 63 |
+
const sec = {};
|
| 64 |
+
const nav = el('div', {class:'row', style:'gap:6px;margin-bottom:8px;flex-wrap:wrap'});
|
| 65 |
+
const body = el('div', {class:'stbody'});
|
| 66 |
+
const show = k => { for (const [n, d] of Object.entries(sec)) d.style.display = n === k ? '' : 'none';
|
| 67 |
+
nav.querySelectorAll('button').forEach(x => x.classList.toggle('pri', x.dataset.k === k)); if (k === 'catalog') loadCat(); };
|
| 68 |
+
for (const [k, lab] of [['guide', 'stGuide'], ['submit', 'stSubmit'], ['catalog', 'stCatalog']]) {
|
| 69 |
+
nav.append(el('button', {class:'btn', 'data-k':k, onclick:() => show(k)}, t(lab)));
|
| 70 |
+
sec[k] = el('div', {'data-sec':k}); body.append(sec[k]);
|
| 71 |
+
}
|
| 72 |
+
pane.append(el('p', {class:'small muted', style:'margin:0 0 8px'}, t('stIntro')), nav, body);
|
| 73 |
+
|
| 74 |
+
sec.guide.append(
|
| 75 |
+
el('h4', {}, t('stFormat')), el('p', {class:'small', style:'line-height:1.6'}, t('stFormatBody')),
|
| 76 |
+
el('h4', {}, t('stManifest')), el('pre', {class:'stpre mono'}, MANIFEST),
|
| 77 |
+
el('h4', {}, t('stCli')), el('pre', {class:'stpre mono'}, CLI),
|
| 78 |
+
el('h4', {}, t('stEnforce')), el('ul', {class:'small', style:'line-height:1.7;margin:0;padding-left:18px'}, ['stE1', 'stE2', 'stE3', 'stE4'].map(k => el('li', {}, t(k)))));
|
| 79 |
+
|
| 80 |
+
const out = el('div', {class:'stout'});
|
| 81 |
+
const file = el('input', {type:'file', accept:'.vdr', style:'max-width:100%'});
|
| 82 |
+
const b64 = buf => { let s = ''; const u = new Uint8Array(buf); for (let i = 0; i < u.length; i += 0x8000) s += String.fromCharCode.apply(null, u.subarray(i, i + 0x8000)); return btoa(s); };
|
| 83 |
+
const submit = async (name, buf) => {
|
| 84 |
+
out.innerHTML = ''; out.append(el('div', {class:'small muted'}, name + ' Β· ' + buf.byteLength + ' bytes Β· ' + t('stRunning')));
|
| 85 |
+
let r;
|
| 86 |
+
try { r = await call('store/submit', {vdr_b64:b64(buf)}); } catch (e) { out.innerHTML = ''; out.append(el('div', {class:'card small'}, t('stNeedLive'))); return; }
|
| 87 |
+
out.innerHTML = '';
|
| 88 |
+
out.append(el('div', {class:'small muted', style:'margin-bottom:6px'}, name + ' Β· ' + buf.byteLength + ' bytes'));
|
| 89 |
+
for (const s of r.steps || []) {
|
| 90 |
+
const row = el('div', {class:'card stck' + (s.ok ? '' : ' blocked')}, el('span', {class:'tag ' + (s.ok ? 'allow' : 'deny')}, s.ok ? 'PASS' : 'FAIL'), el('b', {style:'margin:0 8px'}, s.check), el('span', {class:'small mono'}, s.detail));
|
| 91 |
+
if (s.probes) row.append(el('table', {class:'sttab small'}, el('tr', {}, el('th', {}, t('stProbe')), el('th', {}, 'tool'), el('th', {}, t('stKernel')), el('th', {}, t('stVerdict')), el('th', {}, 'audit')),
|
| 92 |
+
s.probes.map(p => el('tr', {}, el('td', {}, p.probe), el('td', {class:'mono'}, p.fn), el('td', {}, p.kernel), el('td', {}, el('span', {class:'tag ' + (p.verdict === 'ALLOW' ? 'allow' : 'deny')}, p.verdict)), el('td', {class:'mono'}, p.audit_seq != null ? '#' + p.audit_seq : '-')))));
|
| 93 |
+
out.append(row);
|
| 94 |
+
}
|
| 95 |
+
const pass = r.status === 'PASS';
|
| 96 |
+
out.append(el('div', {class:'stresult ' + (pass ? 'ok' : 'bad')}, pass ? t('stPass') : t('stRej') + ': ' + (r.reason || '')));
|
| 97 |
+
if (pass && r.package_b64) out.append(el('a', {href:'data:application/octet-stream;base64,' + r.package_b64, download:name.replace(/\.vdr$/, '') + '.signed.vdr', class:'small'}, t('stDownload')));
|
| 98 |
+
};
|
| 99 |
+
file.onchange = async () => { const f = file.files[0]; if (f) submit(f.name, await f.arrayBuffer()); };
|
| 100 |
+
const sample = async n => submit(n, await (await fetch('samples/' + n, {cache:'no-store'})).arrayBuffer());
|
| 101 |
+
sec.submit.append(el('div', {class:'row', style:'gap:8px;flex-wrap:wrap;margin-bottom:8px'}, el('label', {class:'small'}, t('stPick') + ' '), file,
|
| 102 |
+
el('button', {class:'btn', onclick:() => sample('weather-plus.vdr')}, t('stSampleOk')), el('button', {class:'btn', onclick:() => sample('weather-plus-tampered.vdr')}, t('stSampleBad'))), out);
|
| 103 |
+
|
| 104 |
+
const loadCat = pane.__loadCat = async () => {
|
| 105 |
+
sec.catalog.innerHTML = '';
|
| 106 |
+
let c;
|
| 107 |
+
try { c = await call('store/catalog'); } catch { sec.catalog.append(el('div', {class:'card small'}, t('stNeedLive'))); return; }
|
| 108 |
+
const grid = el('div', {class:'stgrid'});
|
| 109 |
+
for (const a of c.apps) grid.append(el('div', {class:'card'},
|
| 110 |
+
el('div', {class:'row', style:'justify-content:space-between;gap:6px'}, el('b', {}, a.name + ' ' + a.version), el('span', {class:'tag allow'}, t('stBadge'))),
|
| 111 |
+
el('div', {class:'small muted'}, a.publisher + ' Β· ' + t('stFree') + ' Β· ' + t('stRisk') + ' ' + a.risk),
|
| 112 |
+
el('div', {class:'small', style:'margin:4px 0'}, a.description),
|
| 113 |
+
el('div', {class:'small mono'}, t('stPerms') + ': ' + (a.permissions.tools || []).join(', ') + ((a.permissions.paths || []).length ? ' Β· paths ' + a.permissions.paths.join(', ') : '')),
|
| 114 |
+
el('div', {class:'small muted mono'}, 'STORE_SIG ' + a.store_sig + '...')));
|
| 115 |
+
sec.catalog.append(grid, el('p', {class:'small muted'}, t('stPaid') + ' Store public key: ' + String(c.store_public_key).slice(0, 16) + '...'));
|
| 116 |
+
};
|
| 117 |
+
show('guide');
|
| 118 |
+
};
|
| 119 |
+
|
| 120 |
+
const css = document.createElement('style');
|
| 121 |
+
css.textContent = `.stbody{overflow:auto;height:calc(100% - 70px)}.stbody h4{margin:10px 0 4px;font-size:13px}
|
| 122 |
+
.stpre{background:var(--bg);border:1px solid var(--border);border-radius:9px;padding:8px 10px;font-size:12px;white-space:pre-wrap;margin:0}
|
| 123 |
+
.stck{margin:5px 0;padding:7px 10px}.sttab{width:100%;border-collapse:collapse;margin-top:6px}.sttab td,.sttab th{border-top:1px solid var(--border);padding:3px 6px;text-align:left}
|
| 124 |
+
.stresult{margin:8px 0;padding:10px;border-radius:10px;font-weight:800}.stresult.ok{background:#ECFDF5;color:#065F46}.stresult.bad{background:#FFF1F2;color:#BE123C}
|
| 125 |
+
.stgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:8px}`;
|
| 126 |
+
document.head.append(css);
|
| 127 |
+
})();
|