docs(portal): internal same-origin path map on /docs and hub links
Some checks failed
CI/CD Pipeline / Solidity Contracts (push) Failing after 1m23s
CI/CD Pipeline / Security Scanning (push) Successful in 2m41s
CI/CD Pipeline / Lint and Format (push) Failing after 51s
CI/CD Pipeline / Terraform Validation (push) Failing after 26s
CI/CD Pipeline / Kubernetes Validation (push) Successful in 32s
Validation / validate-genesis (push) Successful in 31s
Validation / validate-terraform (push) Failing after 32s
Validation / validate-kubernetes (push) Failing after 13s
Validation / validate-smart-contracts (push) Failing after 11s
Validation / validate-security (push) Failing after 1m24s
Validation / validate-documentation (push) Failing after 22s
Verify Deployment / Verify Deployment (push) Failing after 1m9s

Add omnlInternalPaths config, expand DocsPage with path-only API tables,
and switch QuickPortalLinks to relative routes instead of hardcoded FQDNs.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
defiQUG
2026-07-02 15:09:36 -07:00
parent 2b72242eb6
commit b5d4f268bf
3 changed files with 287 additions and 37 deletions

View File

@@ -0,0 +1,109 @@
/**
* Same-origin internal path map for hub /docs and in-portal navigation.
* SSOT: proxmox repo config/omnl-internal-paths.v1.json — keep in sync on edits.
*/
export type InternalPathEntry = {
label: string;
path: string;
method?: string;
auth?: string;
notes?: string;
};
export type InternalPathGroup = {
id: string;
label: string;
entries: InternalPathEntry[];
};
export const INTERNAL_PATH_POLICY = {
internalLinks:
'Same-origin paths only — no FQDNs, localhost, or 127.0.0.1 in portal navigation or /docs links.',
nginxRequired:
'API prefixes /settlement/, /exchange/, /api/v1/, /omnl/ require nginx in front of the SPA (production LXC or dev deploy-omnl-domains.sh).',
externalShare:
'Institutional share URLs (https://… FQDNs) are documented in operator manifests — not used as internal links.',
} as const;
export const INTERNAL_PATH_PREFIXES = [
{ label: 'Settlement', prefix: '/settlement', upstream: '/api/v1/settlement', port: 3011 },
{ label: 'Exchange', prefix: '/exchange', upstream: '/api/v1/exchange', port: 3012 },
{ label: 'OMNL API', prefix: '/api/v1', upstream: '/api/v1', port: 3000 },
{ label: 'OMNL HTML', prefix: '/omnl', upstream: '/omnl', port: 3000 },
] as const;
export const INTERNAL_PATH_FORBIDDEN = [
'/settlement/api/v1/settlement/',
'/exchange/api/v1/exchange/',
'z-bank/settle-m0-m4',
] as const;
export const INTERNAL_PATH_GROUPS: InternalPathGroup[] = [
{
id: 'spa',
label: 'React SPA',
entries: [
{ label: 'DBIS Hub', path: '/hub' },
{ label: 'Central Bank', path: '/central-bank' },
{ label: 'M0→M4 panel', path: '/central-bank#z-full-settlement' },
{ label: 'Settlement protocols', path: '/central-bank#z-settlement-protocols' },
{ label: 'Office 24', path: '/office-24' },
{ label: 'DBIS Trade', path: '/trade' },
{ label: 'Digital Swap', path: '/swap' },
{ label: 'Bridge', path: '/bridge' },
{ label: 'Reserve', path: '/reserve' },
{ label: 'History', path: '/history' },
{ label: 'Wallets', path: '/wallets' },
{ label: 'Admin', path: '/admin' },
{ label: 'API docs', path: '/docs' },
],
},
{
id: 'settlement',
label: 'Settlement API',
entries: [
{ method: 'GET', path: '/settlement/health', auth: 'none' },
{ method: 'GET', path: '/settlement/public/money-supply', auth: 'none' },
{ method: 'GET', path: '/settlement/chains', auth: 'none' },
{ method: 'POST', path: '/settlement/external-transfer', auth: 'super-admin', notes: '→ Fineract journals' },
{ method: 'POST', path: '/settlement/token-load', auth: 'super-admin' },
{ method: 'POST', path: '/settlement/transfer/external', auth: 'super-admin' },
],
},
{
id: 'exchange',
label: 'Exchange API',
entries: [
{ method: 'GET', path: '/exchange/health', auth: 'none' },
{ method: 'GET', path: '/exchange/tokens', auth: 'read' },
{ method: 'GET', path: '/exchange/quote', auth: 'read' },
{ method: 'POST', path: '/exchange/plan', auth: 'trade' },
{ method: 'POST', path: '/exchange/swap/settle', auth: 'admin' },
],
},
{
id: 'omnl-api',
label: 'OMNL API',
entries: [
{ method: 'GET', path: '/api/v1/omnl/catalog', auth: 'none' },
{ method: 'GET', path: '/api/v1/omnl/openapi.json', auth: 'none' },
{ method: 'GET', path: '/api/v1/omnl/health', auth: 'omnl-key-in-production' },
{ method: 'GET', path: '/api/v1/omnl/ipsas/fineract-health', auth: 'omnl-key-in-production' },
],
},
{
id: 'omnl-html',
label: 'OMNL HTML consoles',
entries: [
{ path: '/omnl/compliance', label: 'Compliance' },
{ path: '/omnl/dashboard', label: 'Dashboard' },
{ path: '/omnl/terminal', label: 'Terminal' },
],
},
];
export const INTERNAL_STACK_FLOW = [
'POST /settlement/external-transfer → settlement-middleware → Fineract POST /journalentries',
'GET /settlement/public/money-supply ← Fineract GL (fineractConnected / fineractLive)',
'GET /api/v1/omnl/ipsas/fineract-health → probes Fineract /glaccounts?limit=1',
] as const;

View File

@@ -1,28 +1,30 @@
import NasaIcon from '../../components/icons/NasaIcon';
const LINKS = [
{ label: 'Central Bank', href: 'https://secure.omdnl.org/central-bank', icon: 'central-bank' as const },
{ label: 'Online banking', href: 'https://online.omdnl.org/central-bank', icon: 'globe' as const },
{ label: 'Office 24', href: 'https://office24.omdnl.org/office-24', icon: 'office' as const },
{ label: 'DBIS Trade', href: 'https://exchange.omdnl.org/trade', icon: 'trade' as const },
{ label: 'Digital swap', href: 'https://digital.omdnl.org/swap', icon: 'swap' as const },
] as const;
export default function QuickPortalLinks() {
return (
<div className="dash-quick-links mb-8">
<p className="dash-quick-links__label flex items-center gap-1.5">
import { Link } from 'react-router-dom';
import NasaIcon from '../../components/icons/NasaIcon';
/** Same-origin portal surfaces — no FQDNs (see /docs). */
const LINKS = [
{ label: 'Central Bank', to: '/central-bank', icon: 'central-bank' as const },
{ label: 'Office 24', to: '/office-24', icon: 'office' as const },
{ label: 'DBIS Trade', to: '/trade', icon: 'trade' as const },
{ label: 'Digital swap', to: '/swap', icon: 'swap' as const },
{ label: 'API paths', to: '/docs', icon: 'link' as const },
] as const;
export default function QuickPortalLinks() {
return (
<div className="dash-quick-links mb-8">
<p className="dash-quick-links__label flex items-center gap-1.5">
<NasaIcon name="link" size={12} className="nasa-icon--telemetry" />
Portal surfaces
</p>
<div className="flex flex-wrap gap-2">
{LINKS.map(({ label, to, icon }) => (
<Link key={to} to={to} className="dash-quick-links__chip">
<NasaIcon name={icon} size={12} />
{label}
</Link>
))}
</div>
</div>
);
}

View File

@@ -1,18 +1,157 @@
/**
* Developer / API documentation. Not shown in main UI.
* Developer / API documentation — same-origin internal path map.
*/
import { Link } from 'react-router-dom';
import {
INTERNAL_PATH_FORBIDDEN,
INTERNAL_PATH_GROUPS,
INTERNAL_PATH_POLICY,
INTERNAL_PATH_PREFIXES,
INTERNAL_STACK_FLOW,
} from '../config/omnlInternalPaths';
function PathCode({ path }: { path: string }) {
const isSpa = INTERNAL_PATH_GROUPS.find((g) => g.id === 'spa')?.entries.some((e) => e.path === path);
if (isSpa) {
return (
<Link to={path} className="text-teal-400 hover:underline font-mono text-sm">
{path}
</Link>
);
}
return <code className="bg-[#252830] px-1.5 py-0.5 rounded text-teal-400 text-sm">{path}</code>;
}
export default function DocsPage() {
return (
<div className="container mx-auto px-4 py-12 max-w-3xl">
<div className="container mx-auto px-4 py-12 max-w-4xl">
<h1 className="text-2xl font-bold text-white mb-2">Developer reference</h1>
<p className="text-[#A0A0A0] text-sm mb-8">
For swap+bridge+swap (Dodoex PMM): use <code className="bg-[#252830] px-1.5 py-0.5 rounded text-teal-400">POST /api/bridge/quote</code> with{' '}
<code className="bg-[#252830] px-1.5 py-0.5 rounded text-teal-400">sourceToken</code>, <code className="bg-[#252830] px-1.5 py-0.5 rounded text-teal-400">destinationToken</code>,{' '}
<code className="bg-[#252830] px-1.5 py-0.5 rounded text-teal-400">sourceChainId</code>, <code className="bg-[#252830] px-1.5 py-0.5 rounded text-teal-400">destinationChainId</code>,{' '}
<code className="bg-[#252830] px-1.5 py-0.5 rounded text-teal-400">amount</code>. Response includes{' '}
<code className="bg-[#252830] px-1.5 py-0.5 rounded text-teal-400">sourceSwapQuote</code>,{' '}
<code className="bg-[#252830] px-1.5 py-0.5 rounded text-teal-400">destinationSwapQuote</code>,{' '}
<code className="bg-[#252830] px-1.5 py-0.5 rounded text-teal-400">minReceived</code>.
Internal links use <strong className="text-white font-medium">same-origin paths only</strong> no FQDNs,
localhost, or 127.0.0.1. Institutional share URLs live in operator manifests.
</p>
<section className="mb-10 rounded-lg border border-[#2a2f3a] bg-[#1a1d24] p-5">
<h2 className="text-lg font-semibold text-white mb-3">Policy</h2>
<ul className="text-sm text-[#A0A0A0] space-y-2 list-disc pl-5">
<li>{INTERNAL_PATH_POLICY.internalLinks}</li>
<li>{INTERNAL_PATH_POLICY.nginxRequired}</li>
<li>{INTERNAL_PATH_POLICY.externalShare}</li>
</ul>
</section>
<section className="mb-10">
<h2 className="text-lg font-semibold text-white mb-3">Nginx prefixes</h2>
<div className="overflow-x-auto">
<table className="w-full text-sm text-left border-collapse">
<thead>
<tr className="text-[#888] border-b border-[#2a2f3a]">
<th className="py-2 pr-4">Layer</th>
<th className="py-2 pr-4">Portal prefix</th>
<th className="py-2 pr-4">Service path</th>
<th className="py-2">Port</th>
</tr>
</thead>
<tbody className="text-[#ccc]">
{INTERNAL_PATH_PREFIXES.map((row) => (
<tr key={row.prefix} className="border-b border-[#2a2f3a]/60">
<td className="py-2 pr-4">{row.label}</td>
<td className="py-2 pr-4">
<code className="text-teal-400">{row.prefix}/</code>
</td>
<td className="py-2 pr-4">
<code className="text-teal-400">{row.upstream}</code>
</td>
<td className="py-2">{row.port}</td>
</tr>
))}
</tbody>
</table>
</div>
</section>
{INTERNAL_PATH_GROUPS.map((group) => (
<section key={group.id} className="mb-10">
<h2 className="text-lg font-semibold text-white mb-3">{group.label}</h2>
<div className="overflow-x-auto">
<table className="w-full text-sm text-left border-collapse">
<thead>
<tr className="text-[#888] border-b border-[#2a2f3a]">
{(group.id === 'spa' || group.id === 'omnl-html') && (
<th className="py-2 pr-4">Label</th>
)}
{group.id !== 'spa' && group.id !== 'omnl-html' && (
<th className="py-2 pr-4 w-16">Method</th>
)}
<th className="py-2 pr-4">Path</th>
{group.id !== 'spa' && group.id !== 'omnl-html' && (
<th className="py-2 pr-4 w-28">Auth</th>
)}
{group.id !== 'spa' && group.id !== 'omnl-html' && (
<th className="py-2">Notes</th>
)}
</tr>
</thead>
<tbody className="text-[#ccc]">
{group.entries.map((entry) => (
<tr key={entry.path} className="border-b border-[#2a2f3a]/60">
{(group.id === 'spa' || group.id === 'omnl-html') && (
<td className="py-2 pr-4">{entry.label}</td>
)}
{group.id !== 'spa' && group.id !== 'omnl-html' && (
<td className="py-2 pr-4 font-mono text-xs">{entry.method ?? '—'}</td>
)}
<td className="py-2 pr-4">
<PathCode path={entry.path} />
</td>
{group.id !== 'spa' && group.id !== 'omnl-html' && (
<td className="py-2 pr-4 text-xs">{entry.auth ?? '—'}</td>
)}
{group.id !== 'spa' && group.id !== 'omnl-html' && (
<td className="py-2 text-xs text-[#888]">{entry.notes ?? ''}</td>
)}
</tr>
))}
</tbody>
</table>
</div>
</section>
))}
<section className="mb-10">
<h2 className="text-lg font-semibold text-white mb-3">Stack flow (paths only)</h2>
<ul className="text-sm text-[#A0A0A0] space-y-2 list-disc pl-5 font-mono">
{INTERNAL_STACK_FLOW.map((line) => (
<li key={line}>{line}</li>
))}
</ul>
</section>
<section className="mb-10">
<h2 className="text-lg font-semibold text-white mb-3">Forbidden patterns</h2>
<ul className="text-sm space-y-1">
{INTERNAL_PATH_FORBIDDEN.map((p) => (
<li key={p}>
<code className="bg-[#252830] px-1.5 py-0.5 rounded text-red-400">{p}</code>
</li>
))}
</ul>
</section>
<section className="mb-10 rounded-lg border border-[#2a2f3a] bg-[#1a1d24] p-5">
<h2 className="text-lg font-semibold text-white mb-3">Bridge quote API</h2>
<p className="text-[#A0A0A0] text-sm">
For swap+bridge+swap (DODO PMM):{' '}
<code className="bg-[#252830] px-1.5 py-0.5 rounded text-teal-400">POST /api/v1/bridge/quote</code> with{' '}
<code className="bg-[#252830] px-1.5 py-0.5 rounded text-teal-400">sourceToken</code>,{' '}
<code className="bg-[#252830] px-1.5 py-0.5 rounded text-teal-400">destinationToken</code>, chain IDs, and{' '}
<code className="bg-[#252830] px-1.5 py-0.5 rounded text-teal-400">amount</code>.
</p>
</section>
<p className="text-xs text-[#666]">
SSOT: <code>config/omnl-internal-paths.v1.json</code> · Operator map:{' '}
<code>docs/04-configuration/ZARDASHT_ENDPOINT_MAP.md</code>
</p>
</div>
);