fix: dashboard API paths for nginx/NPM proxy routing
Some checks failed
CI/CD Pipeline / Solidity Contracts (push) Failing after 1m18s
CI/CD Pipeline / Security Scanning (push) Successful in 2m45s
CI/CD Pipeline / Lint and Format (push) Failing after 47s
CI/CD Pipeline / Terraform Validation (push) Failing after 28s
CI/CD Pipeline / Kubernetes Validation (push) Has been cancelled
Validation / validate-genesis (push) Has started running
Validation / validate-terraform (push) Has been cancelled
Validation / validate-kubernetes (push) Has been cancelled
Validation / validate-smart-contracts (push) Has been cancelled
Validation / validate-security (push) Has been cancelled
Validation / validate-documentation (push) Has been cancelled

Use short /settlement and /exchange paths in production so nginx maps them
to backend /api/v1/* without double-prefix 404s. Align all nginx configs.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-29 00:53:46 -07:00
parent 543482eddc
commit a29b32f042
8 changed files with 62 additions and 55 deletions

View File

@@ -11,7 +11,7 @@ location /api/v1/ {
}
location /settlement/ {
proxy_pass http://omnl_settlement/;
proxy_pass http://omnl_settlement/api/v1/settlement/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
@@ -22,7 +22,7 @@ location /settlement/ {
}
location /exchange/ {
proxy_pass http://omnl_exchange/;
proxy_pass http://omnl_exchange/api/v1/exchange/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;

View File

@@ -40,14 +40,14 @@ http {
}
location /settlement/ {
proxy_pass http://omnl_lxc_settlement/;
proxy_pass http://omnl_lxc_settlement/api/v1/settlement/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_read_timeout 120s;
}
location /exchange/ {
proxy_pass http://omnl_lxc_exchange/;
proxy_pass http://omnl_lxc_exchange/api/v1/exchange/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_read_timeout 120s;

View File

@@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>OMNL Mission Control Chain 138</title>
<title>OMNL Mission Control - Chain 138</title>
<style>
html, body { margin: 0; min-height: 100%; background: #0f2847; color: #e8f4ff; }
#root { min-height: 100vh; }
@@ -33,7 +33,7 @@
<div id="root">
<div class="omnl-boot" role="status" aria-live="polite">
<div class="omnl-boot__title">OMNL Mission Control</div>
<div class="omnl-boot__hint">Loading dashboard</div>
<div class="omnl-boot__hint">Loading dashboard...</div>
</div>
</div>
<script type="module" src="/src/main.tsx"></script>

View File

@@ -22,6 +22,24 @@ export const SETTLEMENT_MIDDLEWARE_URL = prodOrLocal(
'/settlement',
'http://localhost:3011',
);
/** Production nginx maps /settlement/* → settlement-middleware /api/v1/settlement/* */
function proxiedServiceUrl(base: string, servicePrefix: string, path: string): string {
const root = base.replace(/\/$/, '');
const suffix = path.startsWith('/') ? path : `/${path}`;
if (root.startsWith('/')) {
return `${root}${suffix}`;
}
return `${root}${servicePrefix}${suffix}`;
}
export function settlementApi(path: string): string {
return proxiedServiceUrl(SETTLEMENT_MIDDLEWARE_URL, '/api/v1/settlement', path);
}
export function exchangeApi(path: string): string {
return proxiedServiceUrl(DBIS_EXCHANGE_URL, '/api/v1/exchange', path);
}
export const ENHANCED_SWAP_ROUTER_V2 =
import.meta.env.VITE_ENHANCED_SWAP_ROUTER_V2 ||
'0xa421706768aeb7fafa2d912c5e10824ef3437ad4';
@@ -66,7 +84,7 @@ export function formatAmountFromWei(wei: string, decimals: number): string {
}
export async function fetchExchangeTokens(): Promise<DexToken[]> {
try {
const res = await fetch(`${DBIS_EXCHANGE_URL.replace(/\/$/, '')}/api/v1/exchange/tokens`);
const res = await fetch(exchangeApi('/tokens'));
if (!res.ok) return DEFAULT_TOKENS;
const data = await res.json();
return Array.isArray(data.tokens) && data.tokens.length > 0 ? data.tokens : DEFAULT_TOKENS;

View File

@@ -1,4 +1,4 @@
import { SETTLEMENT_MIDDLEWARE_URL } from './dex';
import { settlementApi } from './dex';
export type OmnlChainSummary = {
chainId: number;
@@ -21,8 +21,7 @@ export type ChainRegistryResponse = {
export async function fetchOmnlChainRegistry(): Promise<ChainRegistryResponse | null> {
try {
const base = SETTLEMENT_MIDDLEWARE_URL.replace(/\/$/, '');
const res = await fetch(`${base}/api/v1/settlement/chains`);
const res = await fetch(settlementApi('/chains'));
if (!res.ok) return null;
return res.json();
} catch {

View File

@@ -1,5 +1,5 @@
import { useCallback, useEffect, useState } from 'react';
import { DBIS_EXCHANGE_URL, SETTLEMENT_MIDDLEWARE_URL } from '../../config/dex';
import { exchangeApi, settlementApi } from '../../config/dex';
export type MoneySupply = {
asOf?: string;
@@ -12,14 +12,14 @@ export type MoneySupply = {
M2?: { broadMoney?: string; gl2200?: string; metaFiatPending?: string };
};
async function loadMoneySupply(settlement: string): Promise<MoneySupply | null> {
const publicRes = await fetch(`${settlement}/api/v1/settlement/money-supply/public`);
async function loadMoneySupply(): Promise<MoneySupply | null> {
const publicRes = await fetch(settlementApi('/money-supply/public'));
if (publicRes.ok) return publicRes.json();
const apiKey = import.meta.env.VITE_OMNL_API_KEY;
if (!apiKey) return null;
const authRes = await fetch(`${settlement}/api/v1/settlement/money-supply`, {
const authRes = await fetch(settlementApi('/money-supply'), {
headers: { Authorization: `Bearer ${apiKey}` },
});
return authRes.ok ? authRes.json() : null;
@@ -34,19 +34,16 @@ export function useCentralBank() {
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const settlement = SETTLEMENT_MIDDLEWARE_URL.replace(/\/$/, '');
const exchange = DBIS_EXCHANGE_URL.replace(/\/$/, '');
const refresh = useCallback(async () => {
setLoading(true);
setError(null);
try {
const [h, o, m2, mon, ms] = await Promise.all([
fetch(`${settlement}/api/v1/settlement/health`).then((r) => r.json()),
fetch(`${settlement}/api/v1/settlement/office`).then((r) => r.json()),
fetch(`${settlement}/api/v1/settlement/tokens/m2`).then((r) => r.json()),
fetch(`${exchange}/api/v1/exchange/swap/monitor?limit=10`).then((r) => r.json()),
loadMoneySupply(settlement),
fetch(settlementApi('/health')).then((r) => r.json()),
fetch(settlementApi('/office')).then((r) => r.json()),
fetch(settlementApi('/tokens/m2')).then((r) => r.json()),
fetch(exchangeApi('/swap/monitor?limit=10')).then((r) => r.json()),
loadMoneySupply(),
]);
setHealth(h);
setOffice(o);
@@ -58,7 +55,7 @@ export function useCentralBank() {
} finally {
setLoading(false);
}
}, [settlement, exchange]);
}, []);
useEffect(() => {
refresh();

View File

@@ -1,5 +1,5 @@
import { useCallback, useEffect, useState } from 'react';
import { DBIS_EXCHANGE_URL, SETTLEMENT_MIDDLEWARE_URL, type DexToken } from '../../config/dex';
import { exchangeApi, settlementApi, type DexToken } from '../../config/dex';
import type { MoneySupply } from '../central-bank/useCentralBank';
export type M2Token = DexToken & {
@@ -17,19 +17,14 @@ export function useOffice24() {
const [moneySupply, setMoneySupply] = useState<MoneySupply | null>(null);
const [loading, setLoading] = useState(true);
const settlement = SETTLEMENT_MIDDLEWARE_URL.replace(/\/$/, '');
const exchange = DBIS_EXCHANGE_URL.replace(/\/$/, '');
const refresh = useCallback(async () => {
setLoading(true);
try {
const [o, t, h, ms] = await Promise.all([
fetch(`${settlement}/api/v1/settlement/office`).then((r) => r.json()),
fetch(`${settlement}/api/v1/settlement/tokens/m2`).then((r) => r.json()),
fetch(`${exchange}/api/v1/exchange/health`).then((r) => r.json()),
fetch(`${settlement}/api/v1/settlement/money-supply/public`).then((r) =>
r.ok ? r.json() : null,
),
fetch(settlementApi('/office')).then((r) => r.json()),
fetch(settlementApi('/tokens/m2')).then((r) => r.json()),
fetch(exchangeApi('/health')).then((r) => r.json()),
fetch(settlementApi('/money-supply/public')).then((r) => (r.ok ? r.json() : null)),
]);
setOffice(o);
setTokens(Array.isArray(t.tokens) ? t.tokens : []);
@@ -38,7 +33,7 @@ export function useOffice24() {
} finally {
setLoading(false);
}
}, [settlement, exchange]);
}, []);
useEffect(() => {
refresh();

View File

@@ -1,7 +1,7 @@
import { useCallback, useEffect, useState } from 'react';
import { useAccount, useSendTransaction } from 'wagmi';
import {
DBIS_EXCHANGE_URL,
exchangeApi,
DEFAULT_TOKENS,
fetchExchangeTokens,
parseAmountToWei,
@@ -37,8 +37,6 @@ export function useDbisSwap() {
const [loadAmount, setLoadAmount] = useState('');
const [lastSettlement, setLastSettlement] = useState<Record<string, unknown> | null>(null);
const baseUrl = DBIS_EXCHANGE_URL.replace(/\/$/, '');
useEffect(() => {
fetchExchangeTokens().then((list) => {
setTokens(list);
@@ -51,12 +49,12 @@ export function useDbisSwap() {
if (!address) return;
try {
const q = new URLSearchParams({ wallet: address });
const res = await fetch(`${baseUrl}/api/v1/exchange/ledgers?${q}`);
const res = await fetch(`${exchangeApi('/ledgers')}?${q}`);
if (res.ok) setLedger(await res.json());
} catch {
/* optional */
}
}, [address, baseUrl]);
}, [address]);
const fetchQuote = useCallback(async () => {
if (!amountIn) return;
@@ -69,7 +67,7 @@ export function useDbisSwap() {
tokenOut: tokenOut.address,
amountIn: wei,
});
const res = await fetch(`${baseUrl}/api/v1/exchange/quote?${q}`);
const res = await fetch(`${exchangeApi('/quote')}?${q}`);
if (!res.ok) throw new Error(await res.text());
const quote = await res.json();
setPlan({ amountOut: quote.amountOut ?? quote.expectedOutput });
@@ -78,7 +76,7 @@ export function useDbisSwap() {
} finally {
setLoading(false);
}
}, [amountIn, tokenIn, tokenOut, baseUrl]);
}, [amountIn, tokenIn, tokenOut]);
const fetchPlan = useCallback(async () => {
if (!amountIn || !address) return;
@@ -86,7 +84,7 @@ export function useDbisSwap() {
setError(null);
try {
const wei = parseAmountToWei(amountIn, tokenIn.decimals);
const res = await fetch(`${baseUrl}/api/v1/exchange/execute-plan`, {
const res = await fetch(exchangeApi('/execute-plan'), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
@@ -105,12 +103,12 @@ export function useDbisSwap() {
} finally {
setLoading(false);
}
}, [amountIn, tokenIn, tokenOut, address, slippageBps, baseUrl]);
}, [amountIn, tokenIn, tokenOut, address, slippageBps]);
const settleSwap = useCallback(
async (hash: string) => {
try {
const res = await fetch(`${baseUrl}/api/v1/exchange/swap/settle`, {
const res = await fetch(exchangeApi('/swap/settle'), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
@@ -127,7 +125,7 @@ export function useDbisSwap() {
/* optional M2 audit */
}
},
[baseUrl, amountIn, address, tokenIn, tokenOut],
[amountIn, address, tokenIn, tokenOut],
);
const executeSwap = useCallback(async () => {
@@ -143,7 +141,7 @@ export function useDbisSwap() {
data: plan.execution.encodedCalldata as `0x${string}`,
});
setTxHash(hash);
await fetch(`${baseUrl}/api/v1/exchange/swap/record`, {
await fetch(exchangeApi('/swap/record'), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
@@ -162,14 +160,14 @@ export function useDbisSwap() {
} finally {
setLoading(false);
}
}, [plan, address, sendTransactionAsync, fetchPlan, tokenIn, tokenOut, amountIn, baseUrl, settleSwap]);
}, [plan, address, sendTransactionAsync, fetchPlan, tokenIn, tokenOut, amountIn, settleSwap]);
const loadM2Token = useCallback(async () => {
if (!address || !loadAmount) return;
setLoading(true);
setError(null);
try {
const res = await fetch(`${baseUrl}/api/v1/exchange/token-load`, {
const res = await fetch(exchangeApi('/token-load'), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
@@ -191,14 +189,14 @@ export function useDbisSwap() {
} finally {
setLoading(false);
}
}, [address, loadAmount, tokenOut, baseUrl, loadLedgers]);
}, [address, loadAmount, tokenOut, loadLedgers]);
const internalTransfer = useCallback(async () => {
if (!address || !transferTo || !transferAmount) return;
setLoading(true);
setError(null);
try {
const res = await fetch(`${baseUrl}/api/v1/exchange/transfer/internal`, {
const res = await fetch(exchangeApi('/transfer/internal'), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
@@ -218,14 +216,14 @@ export function useDbisSwap() {
} finally {
setLoading(false);
}
}, [address, transferTo, transferAmount, tokenIn, baseUrl]);
}, [address, transferTo, transferAmount, tokenIn]);
const externalTransfer = useCallback(async () => {
if (!address || !transferTo || !transferAmount || !externalIban) return;
setLoading(true);
setError(null);
try {
const res = await fetch(`${baseUrl}/api/v1/exchange/transfer/external`, {
const res = await fetch(exchangeApi('/transfer/external'), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
@@ -246,16 +244,16 @@ export function useDbisSwap() {
} finally {
setLoading(false);
}
}, [address, transferTo, transferAmount, externalIban, tokenIn, baseUrl]);
}, [address, transferTo, transferAmount, externalIban, tokenIn]);
const loadMonitor = useCallback(async () => {
try {
const res = await fetch(`${baseUrl}/api/v1/exchange/swap/monitor?limit=20`);
const res = await fetch(`${exchangeApi('/swap/monitor')}?limit=20`);
if (res.ok) setMonitor(await res.json());
} catch {
/* optional */
}
}, [baseUrl]);
}, []);
return {
address,