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

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-03 02:14:10 -07:00
parent 3611257ddb
commit 32a33c1282
8 changed files with 924 additions and 0 deletions

View 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 &amp; 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 &amp; 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>

View 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>

View 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';
};
})();

View 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; }
}

View 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);
}

View 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');
})();

View 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>

View 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/');