chore(frontend): add sync-omnl-consoles script and operator console static assets
Some checks failed
CI/CD Pipeline / Solidity Contracts (push) Has been cancelled
CI/CD Pipeline / Security Scanning (push) Has been cancelled
CI/CD Pipeline / Lint and Format (push) Has been cancelled
CI/CD Pipeline / Terraform Validation (push) Has been cancelled
CI/CD Pipeline / Kubernetes Validation (push) Has been cancelled
Validation / validate-genesis (push) Successful in 32s
Validation / validate-terraform (push) Failing after 31s
Validation / validate-kubernetes (push) Failing after 13s
Validation / validate-smart-contracts (push) Failing after 14s
Validation / validate-security (push) Has started running
Validation / validate-documentation (push) Has been cancelled
Some checks failed
CI/CD Pipeline / Solidity Contracts (push) Has been cancelled
CI/CD Pipeline / Security Scanning (push) Has been cancelled
CI/CD Pipeline / Lint and Format (push) Has been cancelled
CI/CD Pipeline / Terraform Validation (push) Has been cancelled
CI/CD Pipeline / Kubernetes Validation (push) Has been cancelled
Validation / validate-genesis (push) Successful in 32s
Validation / validate-terraform (push) Failing after 31s
Validation / validate-kubernetes (push) Failing after 13s
Validation / validate-smart-contracts (push) Failing after 14s
Validation / validate-security (push) Has started running
Validation / validate-documentation (push) Has been cancelled
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
84
frontend-dapp/public/omnl/compliance/index.html
Normal file
84
frontend-dapp/public/omnl/compliance/index.html
Normal file
@@ -0,0 +1,84 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="omnl-api-base" content="auto" />
|
||||
<title>OMNL Compliance Console</title>
|
||||
<link rel="stylesheet" href="/omnl/static/omnl-compliance-console.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<header>
|
||||
<div>
|
||||
<h1>OMNL Compliance Console</h1>
|
||||
<p class="sub">HYBX · IPSAS / IFRS / US GAAP · Web3 notary & reserve attestation · Chain 138</p>
|
||||
</div>
|
||||
<div class="toolbar">
|
||||
<button type="button" id="btn-refresh">Refresh</button>
|
||||
<button type="button" class="primary" id="btn-download-safe">Download Safe tx</button>
|
||||
<button type="button" id="btn-toggle-raw">Raw JSON</button>
|
||||
<a class="btn" href="/omnl/dashboard">Legacy dashboard</a>
|
||||
<a class="btn" href="/api/v1/omnl/openapi.json">OpenAPI</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div id="load-status" class="status-bar loading">Initializing…</div>
|
||||
<p id="refreshed-at" class="sub" style="margin-top:-0.5rem"></p>
|
||||
|
||||
<section class="section">
|
||||
<h2>Posture</h2>
|
||||
<div id="posture-grid" class="grid"></div>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<h2>Pending actions</h2>
|
||||
<div id="pending-actions"></div>
|
||||
</section>
|
||||
|
||||
<div class="grid" style="grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));">
|
||||
<section class="section card">
|
||||
<h2>On-chain evidence</h2>
|
||||
<div id="evidence-kv"></div>
|
||||
</section>
|
||||
<section class="section card">
|
||||
<h2>Gnosis Safe</h2>
|
||||
<div id="safe-kv"></div>
|
||||
</section>
|
||||
<section class="section card">
|
||||
<h2>External visibility</h2>
|
||||
<div id="external-kv"></div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<section class="section card">
|
||||
<h2>HO nostro & reserve coverage</h2>
|
||||
<div id="ho-liquidity-kv"></div>
|
||||
</section>
|
||||
|
||||
<section class="section card">
|
||||
<h2>Triple-state reconcile</h2>
|
||||
<div id="triple-summary"></div>
|
||||
</section>
|
||||
|
||||
<section class="section card">
|
||||
<h2>Sign-offs</h2>
|
||||
<div id="signoffs-summary"></div>
|
||||
</section>
|
||||
|
||||
<section id="raw-section" class="section hidden">
|
||||
<h2>Full snapshot</h2>
|
||||
<pre id="raw-json" class="raw">{}</pre>
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
<p>
|
||||
Auth: pass <code>?access_token=</code> when <code>OMNL_API_KEY</code> or <code>OMNL_DASHBOARD_TOKEN</code> is set.
|
||||
API: <a href="/api/v1/omnl/compliance/console">/api/v1/omnl/compliance/console</a>
|
||||
</p>
|
||||
</footer>
|
||||
</div>
|
||||
<script src="/omnl/static/omnl-api-base.js"></script>
|
||||
<script src="/omnl/static/omnl-compliance-console.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
55
frontend-dapp/public/omnl/dashboard/index.html
Normal file
55
frontend-dapp/public/omnl/dashboard/index.html
Normal file
@@ -0,0 +1,55 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
||||
<title>HYBX OMNL — status</title>
|
||||
<style>
|
||||
body { font-family: system-ui, sans-serif; margin: 1.5rem; max-width: 56rem; overflow-x: hidden; }
|
||||
h1 { font-size: 1.25rem; }
|
||||
pre { background: #111; color: #eee; padding: 1rem; overflow: auto; border-radius: 6px; font-size: 12px; -webkit-overflow-scrolling: touch; }
|
||||
.err { color: #f66; }
|
||||
a { color: #6cf; word-break: break-word; }
|
||||
@media (max-width: 640px) {
|
||||
body { margin: 1rem 0.75rem; }
|
||||
h1 { font-size: 1.1rem; }
|
||||
p { font-size: 0.9rem; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>OMNL API snapshot</h1>
|
||||
<p><a href="/omnl/compliance"><strong>→ OMNL Compliance Console</strong></a> (posture, pending actions, Safe tx download)</p>
|
||||
<p>Reads <code>/api/v1/omnl/ipsas/registry</code> and <code>/api/v1/omnl/ipsas/fineract-compare</code> (503 if Fineract env missing; 401 if <code>OMNL_API_KEY</code> is set and no <code>access_token</code> in this page URL).</p>
|
||||
<p><a href="/api/v1/omnl/openapi.json">OpenAPI 3 JSON</a> · <a href="/api/v1/omnl/catalog">OMNL API catalog</a> · <a href="/api/v1/omnl/integration-status">integration status</a> · <a href="/api/v1/omnl/ipsas/registry">registry JSON</a> · <a href="/api/v1/omnl/ipsas/matrix">matrix JSON</a> · <a href="/api/v1/omnl/ipsas/fineract-health">Fineract health</a></p>
|
||||
<h2>IPSAS registry</h2>
|
||||
<pre id="reg">Loading…</pre>
|
||||
<h2>Fineract compare</h2>
|
||||
<pre id="fin">Loading…</pre>
|
||||
<script src="/omnl/static/omnl-api-base.js"></script>
|
||||
<script>
|
||||
(function () {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const accessToken = params.get('access_token') || '';
|
||||
const apiBase = window.resolveOmnlApiBase ? window.resolveOmnlApiBase() : '/api/v1';
|
||||
const apiHeaders = accessToken ? { Authorization: 'Bearer ' + accessToken } : {};
|
||||
async function load() {
|
||||
try {
|
||||
const r = await fetch(apiBase + '/omnl/ipsas/registry');
|
||||
document.getElementById('reg').textContent = JSON.stringify(await r.json(), null, 2);
|
||||
} catch (e) {
|
||||
document.getElementById('reg').innerHTML = '<span class="err">' + e + '</span>';
|
||||
}
|
||||
try {
|
||||
const f = await fetch(apiBase + '/omnl/ipsas/fineract-compare', { headers: apiHeaders });
|
||||
const t = await f.text();
|
||||
document.getElementById('fin').textContent = t;
|
||||
} catch (e) {
|
||||
document.getElementById('fin').innerHTML = '<span class="err">' + e + '</span>';
|
||||
}
|
||||
}
|
||||
load();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
8
frontend-dapp/public/omnl/static/omnl-api-base.js
Normal file
8
frontend-dapp/public/omnl/static/omnl-api-base.js
Normal file
@@ -0,0 +1,8 @@
|
||||
(function () {
|
||||
/** auto → same-origin /api/v1 (vite proxy, portal-serve, or token-aggregation). */
|
||||
window.resolveOmnlApiBase = function () {
|
||||
const meta = document.querySelector('meta[name="omnl-api-base"]')?.getAttribute('content')?.trim();
|
||||
if (meta && meta !== 'auto') return meta.replace(/\/$/, '');
|
||||
return '/api/v1';
|
||||
};
|
||||
})();
|
||||
219
frontend-dapp/public/omnl/static/omnl-compliance-console.css
Normal file
219
frontend-dapp/public/omnl/static/omnl-compliance-console.css
Normal file
@@ -0,0 +1,219 @@
|
||||
:root {
|
||||
--bg: #0c0f14;
|
||||
--panel: #141a22;
|
||||
--border: #243044;
|
||||
--text: #e8edf4;
|
||||
--muted: #8b9cb3;
|
||||
--ok: #3dd68c;
|
||||
--warn: #f5c542;
|
||||
--bad: #f07178;
|
||||
--accent: #4ade80;
|
||||
--mono: ui-monospace, 'Cascadia Code', 'SF Mono', monospace;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.wrap {
|
||||
max-width: 72rem;
|
||||
margin: 0 auto;
|
||||
padding: 1.25rem 1rem 3rem;
|
||||
}
|
||||
|
||||
header {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.75rem 1.5rem;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.35rem;
|
||||
font-weight: 650;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.sub {
|
||||
color: var(--muted);
|
||||
font-size: 0.9rem;
|
||||
margin: 0.25rem 0 0;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
button, .btn {
|
||||
appearance: none;
|
||||
border: 1px solid var(--border);
|
||||
background: var(--panel);
|
||||
color: var(--text);
|
||||
border-radius: 8px;
|
||||
padding: 0.45rem 0.75rem;
|
||||
font-size: 0.85rem;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
|
||||
button:hover, .btn:hover { border-color: var(--accent); color: var(--accent); }
|
||||
button.primary { background: #14532d; border-color: #22c55e; }
|
||||
button.primary:hover { background: #166534; }
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.card h2 {
|
||||
font-size: 0.78rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
color: var(--muted);
|
||||
margin: 0 0 0.75rem;
|
||||
}
|
||||
|
||||
.section { margin-bottom: 1.25rem; }
|
||||
|
||||
.section > h2 {
|
||||
font-size: 1rem;
|
||||
margin: 0 0 0.65rem;
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: inline-block;
|
||||
padding: 0.2rem 0.55rem;
|
||||
border-radius: 999px;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 600;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.badge.ok { background: #143528; color: var(--ok); border-color: #2a6b4a; }
|
||||
.badge.warn { background: #3a3014; color: var(--warn); border-color: #6b5620; }
|
||||
.badge.bad { background: #3a1c20; color: var(--bad); border-color: #6b3038; }
|
||||
.badge.neutral { background: #1c2430; color: var(--muted); border-color: var(--border); }
|
||||
|
||||
.metric {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 650;
|
||||
}
|
||||
|
||||
.metric small {
|
||||
display: block;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 400;
|
||||
color: var(--muted);
|
||||
margin-top: 0.15rem;
|
||||
}
|
||||
|
||||
.actions-list { list-style: none; padding: 0; margin: 0; }
|
||||
|
||||
.actions-list li {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
padding: 0.85rem 1rem;
|
||||
margin-bottom: 0.65rem;
|
||||
background: #10151c;
|
||||
}
|
||||
|
||||
.actions-list h3 {
|
||||
margin: 0 0 0.35rem;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.actions-list p {
|
||||
margin: 0 0 0.5rem;
|
||||
color: var(--muted);
|
||||
font-size: 0.88rem;
|
||||
}
|
||||
|
||||
.link-row { display: flex; flex-wrap: wrap; gap: 0.5rem; }
|
||||
|
||||
.link-row a {
|
||||
color: var(--accent);
|
||||
font-size: 0.82rem;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.link-row a:hover { text-decoration: underline; }
|
||||
|
||||
.kv {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(8rem, 11rem) 1fr;
|
||||
gap: 0.35rem 0.75rem;
|
||||
font-size: 0.86rem;
|
||||
}
|
||||
|
||||
.kv dt { color: var(--muted); margin: 0; }
|
||||
.kv dd { margin: 0; font-family: var(--mono); font-size: 0.8rem; word-break: break-all; }
|
||||
|
||||
pre.raw {
|
||||
background: #090c10;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
padding: 0.85rem;
|
||||
overflow: auto;
|
||||
font-size: 0.72rem;
|
||||
line-height: 1.35;
|
||||
max-height: 24rem;
|
||||
}
|
||||
|
||||
.status-bar {
|
||||
padding: 0.65rem 0.85rem;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 0.88rem;
|
||||
}
|
||||
|
||||
.status-bar.loading { background: #1a2430; color: var(--muted); }
|
||||
.status-bar.error { background: #3a1c20; color: var(--bad); }
|
||||
.status-bar.ok { background: #143528; color: var(--ok); }
|
||||
|
||||
.hidden { display: none !important; }
|
||||
|
||||
footer {
|
||||
margin-top: 2rem;
|
||||
color: var(--muted);
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
footer a { color: var(--accent); }
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.kv { grid-template-columns: 1fr; }
|
||||
.wrap { padding-left: 0.75rem; padding-right: 0.75rem; }
|
||||
header { flex-direction: column; align-items: stretch; }
|
||||
.toolbar { width: 100%; }
|
||||
.toolbar button,
|
||||
.toolbar .btn {
|
||||
min-height: 44px;
|
||||
flex: 1 1 calc(50% - 0.25rem);
|
||||
justify-content: center;
|
||||
}
|
||||
h1 { font-size: 1.15rem; }
|
||||
pre, table { font-size: 12px; }
|
||||
}
|
||||
190
frontend-dapp/public/omnl/static/omnl-settlement-terminal.css
Normal file
190
frontend-dapp/public/omnl/static/omnl-settlement-terminal.css
Normal file
@@ -0,0 +1,190 @@
|
||||
:root {
|
||||
--font-mono: "Consolas", "Lucida Console", "Courier New", monospace;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
font-family: system-ui, sans-serif;
|
||||
}
|
||||
|
||||
body.theme-black {
|
||||
background: #0a0a0a;
|
||||
color: #33ff66;
|
||||
}
|
||||
|
||||
body.theme-color {
|
||||
background: #d8d8d8;
|
||||
color: #111;
|
||||
}
|
||||
|
||||
.layout {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 1rem 1.25rem 2rem;
|
||||
}
|
||||
|
||||
.topbar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.15rem;
|
||||
margin: 0 0 0.25rem;
|
||||
}
|
||||
|
||||
.sub {
|
||||
margin: 0;
|
||||
opacity: 0.85;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
button,
|
||||
.btn {
|
||||
font: inherit;
|
||||
cursor: pointer;
|
||||
border: 1px solid currentColor;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
padding: 0.35rem 0.65rem;
|
||||
border-radius: 4px;
|
||||
text-decoration: none;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
button.active {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.card {
|
||||
border: 1px solid currentColor;
|
||||
border-radius: 6px;
|
||||
padding: 0.75rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.params {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.params label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
font-size: 0.75rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
.params input {
|
||||
font: inherit;
|
||||
padding: 0.35rem 0.5rem;
|
||||
border: 1px solid currentColor;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.main {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr min(22rem, 100%);
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.main {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.terminal {
|
||||
margin: 0;
|
||||
min-height: 28rem;
|
||||
max-height: 70vh;
|
||||
overflow: auto;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.78rem;
|
||||
line-height: 1.35;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
body.theme-black .terminal {
|
||||
background: #000;
|
||||
color: #33ff66;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
body.theme-color .terminal {
|
||||
background: #ececec;
|
||||
color: #111;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.side h2 {
|
||||
font-size: 0.85rem;
|
||||
margin: 0 0 0.5rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.cmd-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 0.35rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.alchemy-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
font-size: 0.75rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.alchemy-row input {
|
||||
font: inherit;
|
||||
padding: 0.35rem;
|
||||
border: 1px solid currentColor;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.json-out {
|
||||
margin: 0;
|
||||
max-height: 16rem;
|
||||
overflow: auto;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.7rem;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
footer {
|
||||
font-size: 0.75rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
footer code {
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
219
frontend-dapp/public/omnl/static/omnl-settlement-terminal.js
Normal file
219
frontend-dapp/public/omnl/static/omnl-settlement-terminal.js
Normal file
@@ -0,0 +1,219 @@
|
||||
(function () {
|
||||
const API_BASE = window.resolveOmnlApiBase
|
||||
? window.resolveOmnlApiBase()
|
||||
: document.querySelector('meta[name="omnl-api-base"]')?.getAttribute('content') || '/api/v1';
|
||||
const screenEl = document.getElementById('terminal-screen');
|
||||
const jsonEl = document.getElementById('json-out');
|
||||
const inpRef = document.getElementById('inp-settlement-ref');
|
||||
const inpOffice = document.getElementById('inp-office-id');
|
||||
const inpDate = document.getElementById('inp-value-date');
|
||||
const inpAmount = document.getElementById('inp-amount');
|
||||
const inpToken = document.getElementById('inp-token');
|
||||
const inpAlchemyAddr = document.getElementById('inp-alchemy-addr');
|
||||
|
||||
let screenMode = 'black';
|
||||
|
||||
const inpConnection = document.getElementById('inp-connection');
|
||||
const DEFAULT_CONNECTION = 'mk-albert-gate-limited';
|
||||
const DEFAULT_WALLET = '0xb3B416BdE671c256aAbFB56358baD91D46BB9c39';
|
||||
|
||||
(function initFromQuery() {
|
||||
const p = new URLSearchParams(window.location.search);
|
||||
const conn = p.get('connectionId') || DEFAULT_CONNECTION;
|
||||
if (inpConnection) inpConnection.value = conn;
|
||||
const ref = p.get('settlementRef');
|
||||
if (ref) inpRef.value = ref;
|
||||
})();
|
||||
|
||||
function qs() {
|
||||
const p = new URLSearchParams(window.location.search);
|
||||
const token = inpToken.value.trim() || p.get('access_token') || '';
|
||||
const q = new URLSearchParams({
|
||||
settlementRef: inpRef.value.trim(),
|
||||
officeId: String(inpOffice.value || '1'),
|
||||
valueDate: inpDate.value,
|
||||
amountUsd: inpAmount.value.trim(),
|
||||
});
|
||||
const conn = inpConnection && inpConnection.value ? inpConnection.value.trim() : '';
|
||||
if (conn) q.set('connectionId', conn);
|
||||
if (token) q.set('access_token', token);
|
||||
return q;
|
||||
}
|
||||
|
||||
function authHeaders() {
|
||||
const token = inpToken.value.trim() || new URLSearchParams(window.location.search).get('access_token') || '';
|
||||
return token ? { Authorization: 'Bearer ' + token } : {};
|
||||
}
|
||||
|
||||
async function apiGet(path, extra) {
|
||||
const q = qs();
|
||||
if (extra) {
|
||||
Object.entries(extra).forEach(function (kv) {
|
||||
q.set(kv[0], kv[1]);
|
||||
});
|
||||
}
|
||||
const res = await fetch(API_BASE + path + '?' + q.toString(), { headers: authHeaders() });
|
||||
const text = await res.text();
|
||||
let data;
|
||||
try {
|
||||
data = JSON.parse(text);
|
||||
} catch {
|
||||
data = { raw: text, status: res.status };
|
||||
}
|
||||
if (!res.ok) throw new Error(data.error || data.raw || res.statusText);
|
||||
return data;
|
||||
}
|
||||
|
||||
async function apiPost(path, body) {
|
||||
const q = qs();
|
||||
const res = await fetch(API_BASE + path + '?' + q.toString(), {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', ...authHeaders() },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
const data = await res.json().catch(function () {
|
||||
return {};
|
||||
});
|
||||
if (!res.ok) throw new Error(data.error || res.statusText);
|
||||
return data;
|
||||
}
|
||||
|
||||
function showJson(data) {
|
||||
jsonEl.textContent = JSON.stringify(data, null, 2);
|
||||
}
|
||||
|
||||
function showScreen(text) {
|
||||
screenEl.textContent = text;
|
||||
}
|
||||
|
||||
function setTheme(mode) {
|
||||
screenMode = mode === 'color' ? 'color' : 'black';
|
||||
document.body.classList.toggle('theme-black', screenMode === 'black');
|
||||
document.body.classList.toggle('theme-color', screenMode === 'color');
|
||||
document.getElementById('btn-mode-black').classList.toggle('active', screenMode === 'black');
|
||||
document.getElementById('btn-mode-color').classList.toggle('active', screenMode === 'color');
|
||||
}
|
||||
|
||||
async function loadScreen(artifact) {
|
||||
const data = await apiGet('/omnl/terminal/screen', {
|
||||
mode: screenMode,
|
||||
artifact: artifact || 'package',
|
||||
format: 'json',
|
||||
});
|
||||
showScreen(data.text || '');
|
||||
showJson(data);
|
||||
}
|
||||
|
||||
async function runCmd(cmd) {
|
||||
try {
|
||||
if (cmd === 'help') {
|
||||
showScreen(
|
||||
[
|
||||
'OMNL / HYBX SETTLEMENT TERMINAL — COMMANDS',
|
||||
'',
|
||||
' status — integration status',
|
||||
' package — full JSON package',
|
||||
' pof — proof of funds',
|
||||
' debit-note — debit note',
|
||||
' remittance — remittance advice',
|
||||
' token-san — tokenization sanitized copy',
|
||||
' swift-copy — conversion SWIFT copy',
|
||||
' screen — terminal screen (current theme)',
|
||||
' alchemy balance — eth_getBalance via Alchemy',
|
||||
'',
|
||||
'Toggle Black/Green vs Gray/Black with toolbar buttons.',
|
||||
].join('\n')
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (cmd === 'status') {
|
||||
const q = qs();
|
||||
const token = q.get('access_token');
|
||||
const url = API_BASE + '/omnl/terminal/status' + (token ? '?access_token=' + encodeURIComponent(token) : '');
|
||||
const data = await fetch(url, { headers: authHeaders() }).then(function (r) {
|
||||
return r.json();
|
||||
});
|
||||
showJson(data);
|
||||
showScreen(JSON.stringify(data, null, 2));
|
||||
return;
|
||||
}
|
||||
if (cmd === 'package') {
|
||||
const data = await apiGet('/omnl/terminal/package');
|
||||
showJson(data);
|
||||
if (data.artifacts && data.artifacts.screens) {
|
||||
showScreen(data.artifacts.screens[screenMode] || '');
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (cmd === 'pof') {
|
||||
const data = await apiGet('/omnl/terminal/proof-of-funds');
|
||||
showJson(data);
|
||||
await loadScreen('proof-of-funds');
|
||||
return;
|
||||
}
|
||||
if (cmd === 'debit') {
|
||||
const data = await apiGet('/omnl/terminal/debit-note');
|
||||
showJson(data);
|
||||
await loadScreen('debit-note');
|
||||
return;
|
||||
}
|
||||
if (cmd === 'remittance') {
|
||||
const data = await apiGet('/omnl/terminal/remittance-advice');
|
||||
showJson(data);
|
||||
await loadScreen('remittance-advice');
|
||||
return;
|
||||
}
|
||||
if (cmd === 'token') {
|
||||
const data = await apiGet('/omnl/terminal/tokenization-sanitized');
|
||||
showJson(data);
|
||||
await loadScreen('tokenization-sanitized');
|
||||
return;
|
||||
}
|
||||
if (cmd === 'swift') {
|
||||
const data = await apiGet('/omnl/terminal/conversion-swift-copy');
|
||||
showJson(data);
|
||||
await loadScreen('conversion-swift-copy');
|
||||
return;
|
||||
}
|
||||
if (cmd === 'screen') {
|
||||
await loadScreen('package');
|
||||
return;
|
||||
}
|
||||
if (cmd === 'alchemy-balance') {
|
||||
const addr = (inpAlchemyAddr.value || '').trim();
|
||||
if (!addr) throw new Error('Set Alchemy address first');
|
||||
const data = await apiPost('/omnl/terminal/alchemy/rpc', {
|
||||
network: 'eth-mainnet',
|
||||
method: 'eth_getBalance',
|
||||
params: [addr, 'latest'],
|
||||
connectionId: (inpConnection && inpConnection.value) || DEFAULT_CONNECTION,
|
||||
});
|
||||
showJson(data);
|
||||
showScreen('ALCHEMY eth_getBalance\nADDRESS: ' + addr + '\nRESULT: ' + data.result);
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
showScreen('ERROR: ' + (e && e.message ? e.message : String(e)));
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('btn-mode-black').addEventListener('click', function () {
|
||||
setTheme('black');
|
||||
runCmd('screen');
|
||||
});
|
||||
document.getElementById('btn-mode-color').addEventListener('click', function () {
|
||||
setTheme('color');
|
||||
runCmd('screen');
|
||||
});
|
||||
document.getElementById('btn-refresh').addEventListener('click', function () {
|
||||
runCmd('screen');
|
||||
});
|
||||
document.querySelectorAll('[data-cmd]').forEach(function (btn) {
|
||||
btn.addEventListener('click', function () {
|
||||
runCmd(btn.getAttribute('data-cmd'));
|
||||
});
|
||||
});
|
||||
|
||||
setTheme('black');
|
||||
runCmd('screen');
|
||||
})();
|
||||
76
frontend-dapp/public/omnl/terminal/index.html
Normal file
76
frontend-dapp/public/omnl/terminal/index.html
Normal file
@@ -0,0 +1,76 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="omnl-api-base" content="auto" />
|
||||
<title>OMNL / HYBX Settlement Terminal</title>
|
||||
<link rel="stylesheet" href="/omnl/static/omnl-settlement-terminal.css" />
|
||||
</head>
|
||||
<body class="theme-black">
|
||||
<div class="layout">
|
||||
<header class="topbar">
|
||||
<div>
|
||||
<h1>OMNL / HYBX Settlement Terminal</h1>
|
||||
<p class="sub">Fineract SoR · Alchemy on-chain · institutional document API</p>
|
||||
</div>
|
||||
<div class="toolbar">
|
||||
<button type="button" id="btn-mode-black" class="active">Black / Green</button>
|
||||
<button type="button" id="btn-mode-color">Gray / Black</button>
|
||||
<button type="button" id="btn-refresh">Refresh</button>
|
||||
<a class="btn" href="/omnl/compliance">Compliance</a>
|
||||
<a class="btn" href="/api/v1/omnl/terminal/status">Status JSON</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="params card">
|
||||
<label>Connection
|
||||
<select id="inp-connection">
|
||||
<option value="mk-albert-gate-limited" selected>MK Albert Gate Limited</option>
|
||||
<option value="">(none — legacy defaults)</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>Settlement ref <input id="inp-settlement-ref" type="text" value="MK-ALBERT-GATE-20260529" /></label>
|
||||
<label>Office ID <input id="inp-office-id" type="number" value="1" min="1" /></label>
|
||||
<label>Value date <input id="inp-value-date" type="date" value="2026-03-17" /></label>
|
||||
<label>Amount USD <input id="inp-amount" type="text" value="1000000000.00" /></label>
|
||||
<label>API token <input id="inp-token" type="password" placeholder="OMNL_API_KEY (optional)" autocomplete="off" /></label>
|
||||
</section>
|
||||
|
||||
<div class="main">
|
||||
<section class="screen-wrap card">
|
||||
<pre id="terminal-screen" class="terminal" aria-live="polite">Loading…</pre>
|
||||
</section>
|
||||
<aside class="side card">
|
||||
<h2>Commands</h2>
|
||||
<div class="cmd-grid">
|
||||
<button type="button" data-cmd="help">help</button>
|
||||
<button type="button" data-cmd="status">status</button>
|
||||
<button type="button" data-cmd="package">package</button>
|
||||
<button type="button" data-cmd="pof">pof</button>
|
||||
<button type="button" data-cmd="debit">debit-note</button>
|
||||
<button type="button" data-cmd="remittance">remittance</button>
|
||||
<button type="button" data-cmd="token">token-san</button>
|
||||
<button type="button" data-cmd="swift">swift-copy</button>
|
||||
<button type="button" data-cmd="screen">screen</button>
|
||||
<button type="button" data-cmd="alchemy-balance">alchemy balance</button>
|
||||
</div>
|
||||
<label>Alchemy address
|
||||
<input id="inp-alchemy-addr" type="text" value="0xb3B416BdE671c256aAbFB56358baD91D46BB9c39" />
|
||||
</label>
|
||||
<h2>Output</h2>
|
||||
<pre id="json-out" class="json-out">{}</pre>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<p>
|
||||
API: <code>/api/v1/omnl/terminal/*</code> · Documents bound to Fineract — not SWIFT NET transmission.
|
||||
Pass <code>?access_token=</code> or Bearer when <code>OMNL_API_KEY</code> is set.
|
||||
</p>
|
||||
</footer>
|
||||
</div>
|
||||
<script src="/omnl/static/omnl-api-base.js"></script>
|
||||
<script src="/omnl/static/omnl-settlement-terminal.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
73
frontend-dapp/scripts/sync-omnl-consoles.mjs
Normal file
73
frontend-dapp/scripts/sync-omnl-consoles.mjs
Normal file
@@ -0,0 +1,73 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Copy OMNL operator console static assets into frontend-dapp/public for Vite + portal-serve.
|
||||
*/
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const frontendRoot = path.resolve(__dirname, '..');
|
||||
const srcPublic = path.resolve(frontendRoot, '../services/token-aggregation/public');
|
||||
const destRoot = path.join(frontendRoot, 'public', 'omnl');
|
||||
|
||||
const staticFiles = [
|
||||
'omnl-api-base.js',
|
||||
'omnl-compliance-console.css',
|
||||
'omnl-compliance-console.js',
|
||||
'omnl-settlement-terminal.css',
|
||||
'omnl-settlement-terminal.js',
|
||||
];
|
||||
|
||||
const pageMap = [
|
||||
{ src: 'omnl-compliance-console.html', dest: 'compliance/index.html' },
|
||||
{ src: 'omnl-dashboard.html', dest: 'dashboard/index.html' },
|
||||
{ src: 'omnl-settlement-terminal.html', dest: 'terminal/index.html' },
|
||||
];
|
||||
|
||||
function rmrf(dir) {
|
||||
if (!fs.existsSync(dir)) return;
|
||||
for (const ent of fs.readdirSync(dir, { withFileTypes: true })) {
|
||||
const p = path.join(dir, ent.name);
|
||||
if (ent.isDirectory()) rmrf(p);
|
||||
else fs.unlinkSync(p);
|
||||
}
|
||||
fs.rmdirSync(dir);
|
||||
}
|
||||
|
||||
function ensureDir(dir) {
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
|
||||
function copyFile(src, dest) {
|
||||
ensureDir(path.dirname(dest));
|
||||
fs.copyFileSync(src, dest);
|
||||
}
|
||||
|
||||
if (!fs.existsSync(srcPublic)) {
|
||||
console.warn('[sync-omnl-consoles] token-aggregation public missing — skip');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (fs.existsSync(destRoot)) rmrf(destRoot);
|
||||
ensureDir(path.join(destRoot, 'static'));
|
||||
|
||||
for (const name of staticFiles) {
|
||||
const src = path.join(srcPublic, name);
|
||||
if (!fs.existsSync(src)) {
|
||||
console.warn(`[sync-omnl-consoles] missing ${name}`);
|
||||
continue;
|
||||
}
|
||||
copyFile(src, path.join(destRoot, 'static', name));
|
||||
}
|
||||
|
||||
for (const { src, dest } of pageMap) {
|
||||
const from = path.join(srcPublic, src);
|
||||
if (!fs.existsSync(from)) {
|
||||
console.warn(`[sync-omnl-consoles] missing ${src}`);
|
||||
continue;
|
||||
}
|
||||
copyFile(from, path.join(destRoot, dest));
|
||||
}
|
||||
|
||||
console.log('[sync-omnl-consoles] synced operator consoles → public/omnl/');
|
||||
Reference in New Issue
Block a user