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
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:
109
frontend-dapp/src/config/omnlInternalPaths.ts
Normal file
109
frontend-dapp/src/config/omnlInternalPaths.ts
Normal 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;
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user