1083 lines
35 KiB
HTML
1083 lines
35 KiB
HTML
<!doctype html>
|
|
<html lang="pl">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>mevnode interactive topology</title>
|
|
<style>
|
|
:root {
|
|
--bg: #f2ede5;
|
|
--paper: rgba(255, 251, 244, 0.92);
|
|
--panel: #fffdf8;
|
|
--ink: #1e1a16;
|
|
--muted: #665f55;
|
|
--line: #d8c9b3;
|
|
--read: #0d6d73;
|
|
--write: #b7412f;
|
|
--data: #495565;
|
|
--ops: #8a6a12;
|
|
--focus: #171411;
|
|
--shadow: 0 20px 50px rgba(30, 26, 22, 0.10);
|
|
--radius: 18px;
|
|
}
|
|
|
|
* { box-sizing: border-box; }
|
|
|
|
body {
|
|
margin: 0;
|
|
font-family: "IBM Plex Sans", "Segoe UI", sans-serif;
|
|
color: var(--ink);
|
|
background:
|
|
radial-gradient(circle at top left, rgba(13, 109, 115, 0.12), transparent 28%),
|
|
radial-gradient(circle at top right, rgba(183, 65, 47, 0.12), transparent 24%),
|
|
linear-gradient(180deg, #faf6ef 0%, var(--bg) 100%);
|
|
}
|
|
|
|
.page {
|
|
max-width: 1600px;
|
|
margin: 0 auto;
|
|
padding: 28px 18px 40px;
|
|
}
|
|
|
|
.topbar {
|
|
display: grid;
|
|
grid-template-columns: 1.2fr 0.8fr;
|
|
gap: 20px;
|
|
margin-bottom: 18px;
|
|
}
|
|
|
|
.hero,
|
|
.panel,
|
|
.card,
|
|
.inspector,
|
|
.legend {
|
|
background: var(--paper);
|
|
border: 1px solid rgba(216, 201, 179, 0.9);
|
|
border-radius: var(--radius);
|
|
box-shadow: var(--shadow);
|
|
backdrop-filter: blur(12px);
|
|
}
|
|
|
|
.hero {
|
|
padding: 24px;
|
|
}
|
|
|
|
.eyebrow {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
padding: 6px 10px;
|
|
border-radius: 999px;
|
|
border: 1px solid rgba(30, 26, 22, 0.10);
|
|
font-size: 12px;
|
|
font-weight: 700;
|
|
letter-spacing: 0.08em;
|
|
text-transform: uppercase;
|
|
color: var(--muted);
|
|
}
|
|
|
|
h1 {
|
|
margin: 14px 0 10px;
|
|
font-size: clamp(2rem, 4vw, 3.7rem);
|
|
line-height: 0.98;
|
|
font-family: "IBM Plex Serif", Georgia, serif;
|
|
}
|
|
|
|
p {
|
|
margin: 0;
|
|
color: var(--muted);
|
|
line-height: 1.55;
|
|
}
|
|
|
|
.legend {
|
|
padding: 18px 20px;
|
|
display: grid;
|
|
gap: 10px;
|
|
align-content: start;
|
|
}
|
|
|
|
.legend-row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.dot {
|
|
width: 12px;
|
|
height: 12px;
|
|
border-radius: 999px;
|
|
flex: 0 0 12px;
|
|
}
|
|
|
|
.dot.read { background: var(--read); }
|
|
.dot.write { background: var(--write); }
|
|
.dot.data { background: var(--data); }
|
|
.dot.ops { background: var(--ops); }
|
|
|
|
.toolbar {
|
|
display: flex;
|
|
gap: 10px;
|
|
flex-wrap: wrap;
|
|
margin: 18px 0 8px;
|
|
}
|
|
|
|
.filter {
|
|
border: 1px solid var(--line);
|
|
background: #fffaf4;
|
|
color: var(--ink);
|
|
border-radius: 999px;
|
|
padding: 10px 14px;
|
|
font: inherit;
|
|
font-weight: 700;
|
|
cursor: pointer;
|
|
transition: 140ms ease;
|
|
}
|
|
|
|
.filter:hover,
|
|
.filter.active {
|
|
transform: translateY(-1px);
|
|
border-color: rgba(30, 26, 22, 0.18);
|
|
box-shadow: 0 10px 22px rgba(30, 26, 22, 0.08);
|
|
}
|
|
|
|
.filter[data-filter="read"].active { color: var(--read); }
|
|
.filter[data-filter="write"].active { color: var(--write); }
|
|
.filter[data-filter="data"].active { color: var(--data); }
|
|
.filter[data-filter="ops"].active { color: var(--ops); }
|
|
|
|
.layout {
|
|
display: grid;
|
|
grid-template-columns: minmax(900px, 1.35fr) minmax(300px, 0.65fr);
|
|
gap: 18px;
|
|
align-items: start;
|
|
}
|
|
|
|
.panel {
|
|
padding: 16px;
|
|
overflow: auto;
|
|
}
|
|
|
|
.canvas {
|
|
position: relative;
|
|
min-height: 920px;
|
|
border-radius: 16px;
|
|
background:
|
|
linear-gradient(rgba(216, 201, 179, 0.18) 1px, transparent 1px),
|
|
linear-gradient(90deg, rgba(216, 201, 179, 0.18) 1px, transparent 1px),
|
|
linear-gradient(180deg, #fffdf9, #fff9f1);
|
|
background-size: 28px 28px, 28px 28px, 100% 100%;
|
|
padding: 14px;
|
|
}
|
|
|
|
.network {
|
|
display: grid;
|
|
grid-template-columns: 320px 120px 1fr;
|
|
gap: 18px;
|
|
align-items: stretch;
|
|
min-height: 100%;
|
|
}
|
|
|
|
.col {
|
|
display: grid;
|
|
gap: 14px;
|
|
align-content: start;
|
|
}
|
|
|
|
.node-title {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: baseline;
|
|
gap: 10px;
|
|
margin-bottom: 4px;
|
|
padding: 4px 2px 8px;
|
|
}
|
|
|
|
.node-title h2 {
|
|
margin: 0;
|
|
font-size: 1.35rem;
|
|
font-family: "IBM Plex Serif", Georgia, serif;
|
|
}
|
|
|
|
.node-title span {
|
|
color: var(--muted);
|
|
font-size: 13px;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.card {
|
|
padding: 15px;
|
|
background: var(--panel);
|
|
border-left: 6px solid var(--line);
|
|
cursor: pointer;
|
|
transition: 160ms ease;
|
|
}
|
|
|
|
.card:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 16px 30px rgba(30, 26, 22, 0.10);
|
|
}
|
|
|
|
.card.active {
|
|
border-color: var(--focus);
|
|
box-shadow: 0 18px 35px rgba(30, 26, 22, 0.15);
|
|
transform: translateY(-2px) scale(1.01);
|
|
}
|
|
|
|
.card.dimmed,
|
|
.link.dimmed,
|
|
.lane-label.dimmed {
|
|
opacity: 0.22;
|
|
}
|
|
|
|
.card.read { border-left-color: var(--read); }
|
|
.card.write { border-left-color: var(--write); }
|
|
.card.data { border-left-color: var(--data); }
|
|
.card.ops { border-left-color: var(--ops); }
|
|
|
|
.card h3 {
|
|
margin: 0 0 6px;
|
|
font-size: 1rem;
|
|
}
|
|
|
|
.meta {
|
|
font-size: 12px;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.08em;
|
|
color: var(--muted);
|
|
font-weight: 700;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.chips {
|
|
display: flex;
|
|
gap: 8px;
|
|
flex-wrap: wrap;
|
|
margin-top: 10px;
|
|
}
|
|
|
|
.chip {
|
|
display: inline-block;
|
|
padding: 4px 8px;
|
|
border-radius: 999px;
|
|
font-size: 12px;
|
|
font-weight: 700;
|
|
background: rgba(30, 26, 22, 0.06);
|
|
color: var(--ink);
|
|
}
|
|
|
|
.middle {
|
|
position: relative;
|
|
display: grid;
|
|
grid-template-rows: repeat(5, 1fr);
|
|
align-items: center;
|
|
justify-items: center;
|
|
gap: 14px;
|
|
padding: 42px 0;
|
|
}
|
|
|
|
.link {
|
|
width: 6px;
|
|
height: 100%;
|
|
border-radius: 999px;
|
|
position: relative;
|
|
min-height: 90px;
|
|
transition: opacity 140ms ease, transform 140ms ease;
|
|
}
|
|
|
|
.link::after {
|
|
content: "";
|
|
position: absolute;
|
|
bottom: -4px;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
border-left: 7px solid transparent;
|
|
border-right: 7px solid transparent;
|
|
border-top: 10px solid currentColor;
|
|
color: inherit;
|
|
}
|
|
|
|
.link.read { background: var(--read); color: var(--read); }
|
|
.link.write { background: var(--write); color: var(--write); }
|
|
.link.data { background: var(--data); color: var(--data); }
|
|
.link.ops { background: var(--ops); color: var(--ops); }
|
|
|
|
.lane-label {
|
|
font-size: 12px;
|
|
font-weight: 800;
|
|
letter-spacing: 0.08em;
|
|
text-transform: uppercase;
|
|
color: var(--muted);
|
|
text-align: center;
|
|
max-width: 100px;
|
|
}
|
|
|
|
.inspector {
|
|
padding: 18px;
|
|
position: sticky;
|
|
top: 18px;
|
|
}
|
|
|
|
.inspector h2 {
|
|
margin: 0 0 8px;
|
|
font-size: 1.25rem;
|
|
font-family: "IBM Plex Serif", Georgia, serif;
|
|
}
|
|
|
|
.inspector .kind {
|
|
font-size: 12px;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.08em;
|
|
color: var(--muted);
|
|
font-weight: 800;
|
|
margin-bottom: 14px;
|
|
}
|
|
|
|
.inspector dl {
|
|
margin: 18px 0 0;
|
|
display: grid;
|
|
gap: 12px;
|
|
}
|
|
|
|
.inspector dt {
|
|
font-size: 12px;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.08em;
|
|
color: var(--muted);
|
|
font-weight: 800;
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.inspector dd {
|
|
margin: 0;
|
|
line-height: 1.5;
|
|
color: var(--ink);
|
|
}
|
|
|
|
.inspector ul {
|
|
margin: 6px 0 0;
|
|
padding-left: 18px;
|
|
}
|
|
|
|
.inspector li { margin: 6px 0; }
|
|
|
|
.hint {
|
|
margin-top: 16px;
|
|
padding-top: 14px;
|
|
border-top: 1px solid var(--line);
|
|
font-size: 14px;
|
|
color: var(--muted);
|
|
}
|
|
|
|
.service-map {
|
|
margin-top: 18px;
|
|
display: grid;
|
|
gap: 18px;
|
|
}
|
|
|
|
.map-title {
|
|
padding: 4px 2px;
|
|
}
|
|
|
|
.map-title h2 {
|
|
margin: 0 0 6px;
|
|
font-size: 1.5rem;
|
|
font-family: "IBM Plex Serif", Georgia, serif;
|
|
}
|
|
|
|
.namespaces {
|
|
display: grid;
|
|
grid-template-columns: repeat(3, 1fr);
|
|
gap: 16px;
|
|
}
|
|
|
|
.namespace {
|
|
background: var(--panel);
|
|
border: 1px solid var(--line);
|
|
border-radius: 16px;
|
|
padding: 16px;
|
|
}
|
|
|
|
.namespace h3 {
|
|
margin: 0 0 4px;
|
|
font-size: 1.02rem;
|
|
}
|
|
|
|
.namespace small {
|
|
display: block;
|
|
color: var(--muted);
|
|
margin-bottom: 12px;
|
|
line-height: 1.45;
|
|
}
|
|
|
|
.k8s-item {
|
|
border: 1px solid rgba(216, 201, 179, 0.9);
|
|
border-radius: 14px;
|
|
padding: 12px;
|
|
background: #fffdf8;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.k8s-item:last-child { margin-bottom: 0; }
|
|
|
|
.k8s-head {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
gap: 10px;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.k8s-head strong {
|
|
display: block;
|
|
font-size: 0.98rem;
|
|
}
|
|
|
|
.type {
|
|
font-size: 11px;
|
|
font-weight: 800;
|
|
letter-spacing: 0.08em;
|
|
text-transform: uppercase;
|
|
color: var(--muted);
|
|
}
|
|
|
|
.status {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
font-size: 12px;
|
|
font-weight: 800;
|
|
padding: 5px 8px;
|
|
border-radius: 999px;
|
|
background: rgba(30, 26, 22, 0.06);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.status.ok {
|
|
color: #17603b;
|
|
background: rgba(23, 96, 59, 0.10);
|
|
}
|
|
|
|
.status.warn {
|
|
color: #8d3a14;
|
|
background: rgba(183, 65, 47, 0.10);
|
|
}
|
|
|
|
.k8s-item p {
|
|
font-size: 14px;
|
|
}
|
|
|
|
code {
|
|
font-family: "IBM Plex Mono", monospace;
|
|
background: rgba(30, 26, 22, 0.06);
|
|
padding: 2px 6px;
|
|
border-radius: 6px;
|
|
}
|
|
|
|
@media (max-width: 1240px) {
|
|
.topbar,
|
|
.layout {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.network {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.middle {
|
|
grid-template-columns: repeat(5, 1fr);
|
|
grid-template-rows: none;
|
|
padding: 0;
|
|
}
|
|
|
|
.link {
|
|
width: 100%;
|
|
height: 6px;
|
|
min-height: 6px;
|
|
}
|
|
|
|
.link::after {
|
|
right: -4px;
|
|
left: auto;
|
|
bottom: 50%;
|
|
transform: translateY(50%) rotate(-90deg);
|
|
}
|
|
|
|
.lane-label {
|
|
max-width: none;
|
|
}
|
|
|
|
.inspector {
|
|
position: static;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="page">
|
|
<section class="topbar">
|
|
<div class="hero">
|
|
<div class="eyebrow">Interactive topology / click to inspect</div>
|
|
<h1>Interaktywny diagram uslug na <code>mevnode_bot</code> i zaleznosci od <code>mevnode_sol</code></h1>
|
|
<p>
|
|
Klikaj komponenty albo filtruj sciezki. Diagram rozdziela read plane,
|
|
write plane, data plane i ops plane. Celem jest utrzymanie twardego podzialu:
|
|
<strong>validator zostaje na sol</strong>, a <strong>produkt, ingest i execution</strong>
|
|
dzialaja na bot w <code>k3s</code>.
|
|
</p>
|
|
<div class="toolbar" id="filters">
|
|
<button class="filter active" data-filter="all">All</button>
|
|
<button class="filter" data-filter="read">Read path</button>
|
|
<button class="filter" data-filter="write">Write path</button>
|
|
<button class="filter" data-filter="data">Data plane</button>
|
|
<button class="filter" data-filter="ops">Ops plane</button>
|
|
</div>
|
|
</div>
|
|
|
|
<aside class="legend">
|
|
<div class="legend-row"><span class="dot read"></span> Read: Yellowstone, ingest, DLOB read side</div>
|
|
<div class="legend-row"><span class="dot write"></span> Write: tx-router, RPC, TPU, Jito</div>
|
|
<div class="legend-row"><span class="dot data"></span> Data: Postgres, Redis, Hasura</div>
|
|
<div class="legend-row"><span class="dot ops"></span> Ops: frontend, API, Grafana, Portainer, GitOps</div>
|
|
<p>
|
|
Klikniecie komponentu otwiera po prawej opis roli, wejsc, wyjsc i punktow ryzyka.
|
|
</p>
|
|
</aside>
|
|
</section>
|
|
|
|
<section class="layout">
|
|
<div class="panel">
|
|
<div class="canvas">
|
|
<div class="network">
|
|
<div class="col" id="left-col">
|
|
<div class="node-title">
|
|
<h2>mevnode_sol</h2>
|
|
<span>bare metal</span>
|
|
</div>
|
|
|
|
<article class="card read active" data-id="agave" data-kind="read ops">
|
|
<div class="meta">validator / rpc</div>
|
|
<h3>Agave validator</h3>
|
|
<p>Source of truth dla chain state, snapshotow i lokalnego RPC.</p>
|
|
<div class="chips">
|
|
<span class="chip">127.0.0.1:8899</span>
|
|
<span class="chip">getAccountInfo</span>
|
|
<span class="chip">getMultipleAccounts</span>
|
|
</div>
|
|
</article>
|
|
|
|
<article class="card read" data-id="yellowstone" data-kind="read">
|
|
<div class="meta">geyser / live stream</div>
|
|
<h3>Yellowstone gRPC</h3>
|
|
<p>Pushuje account updates, sloty, tx i block meta po <code>wg0</code>.</p>
|
|
<div class="chips">
|
|
<span class="chip">10.91.0.1:10000</span>
|
|
<span class="chip">x-token</span>
|
|
</div>
|
|
</article>
|
|
|
|
<article class="card ops" data-id="sol-metrics" data-kind="ops">
|
|
<div class="meta">monitoring</div>
|
|
<h3>Plugin metrics</h3>
|
|
<p>Metryki pluginu i gRPC do Prometheusa i diagnostyki.</p>
|
|
<div class="chips">
|
|
<span class="chip">10.91.0.1:8999</span>
|
|
</div>
|
|
</article>
|
|
</div>
|
|
|
|
<div class="middle">
|
|
<div class="link read" data-kind="read"></div>
|
|
<div class="lane-label" data-kind="read">yellowstone / live feed</div>
|
|
<div class="link write" data-kind="write"></div>
|
|
<div class="lane-label" data-kind="write">rpc / tpu / jito</div>
|
|
<div class="link ops" data-kind="ops"></div>
|
|
</div>
|
|
|
|
<div class="col" id="right-col">
|
|
<div class="node-title">
|
|
<h2>mevnode_bot</h2>
|
|
<span>single-node k3s</span>
|
|
</div>
|
|
|
|
<article class="card read" data-id="yellowstone-consumer" data-kind="read">
|
|
<div class="meta">trade namespace</div>
|
|
<h3>yellowstone-consumer</h3>
|
|
<p>Bierze live feed z Yellowstone i zamienia go na dane wewnetrzne.</p>
|
|
<div class="chips">
|
|
<span class="chip">accounts</span>
|
|
<span class="chip">slots</span>
|
|
<span class="chip">transactions</span>
|
|
</div>
|
|
</article>
|
|
|
|
<article class="card read" data-id="trade-ingestor" data-kind="read data">
|
|
<div class="meta">trade namespace</div>
|
|
<h3>trade-ingestor</h3>
|
|
<p>Transformuje dane i zapisuje je do DB lub feedow uslugowych.</p>
|
|
</article>
|
|
|
|
<article class="card read data" data-id="dlob-publisher" data-kind="read data">
|
|
<div class="meta">trade namespace</div>
|
|
<h3>dlob-publisher</h3>
|
|
<p>Czyta RPC Agavy i buduje stan DLOB, ktory trafia do Redis.</p>
|
|
<div class="chips">
|
|
<span class="chip">getMultipleAccounts</span>
|
|
<span class="chip">Redis</span>
|
|
</div>
|
|
</article>
|
|
|
|
<article class="card read data" data-id="dlob-server" data-kind="read data ops">
|
|
<div class="meta">trade namespace</div>
|
|
<h3>dlob-server</h3>
|
|
<p>Serwuje gotowy widok DLOB dla API i wizualizacji.</p>
|
|
</article>
|
|
|
|
<article class="card write" data-id="strategy-engine" data-kind="write">
|
|
<div class="meta">execution</div>
|
|
<h3>strategy-engine</h3>
|
|
<p>Konsumuje feed i decyduje o akcjach tradingowych.</p>
|
|
</article>
|
|
|
|
<article class="card write" data-id="tx-router" data-kind="write">
|
|
<div class="meta">execution</div>
|
|
<h3>tx-router</h3>
|
|
<p>Wybiera write path: <code>RPC</code>, <code>TPU</code> albo <code>Jito</code>.</p>
|
|
</article>
|
|
|
|
<article class="card data" data-id="postgres" data-kind="data">
|
|
<div class="meta">data namespace</div>
|
|
<h3>Postgres + Hasura</h3>
|
|
<p>Trwaly storage, stan aplikacji, historia, pozycje i GraphQL.</p>
|
|
</article>
|
|
|
|
<article class="card data" data-id="redis" data-kind="data">
|
|
<div class="meta">data namespace</div>
|
|
<h3>Redis</h3>
|
|
<p>Cache, pub/sub i szybki stan DLOB dla serwisow read-side.</p>
|
|
</article>
|
|
|
|
<article class="card ops" data-id="frontend-api" data-kind="ops">
|
|
<div class="meta">web namespace</div>
|
|
<h3>Frontend + API + Ingress</h3>
|
|
<p>Warstwa produktu. UI pokazuje dane, API agreguje odpowiedzi dla klienta.</p>
|
|
</article>
|
|
|
|
<article class="card ops" data-id="ops-stack" data-kind="ops">
|
|
<div class="meta">ops namespace</div>
|
|
<h3>Prometheus / Grafana / Portainer / GitOps</h3>
|
|
<p>Monitoring, obsluga klastra, deployment z Gitea i obserwowalnosc.</p>
|
|
</article>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<aside class="inspector" id="inspector">
|
|
<div class="kind" id="inspector-kind">Selected component</div>
|
|
<h2 id="inspector-title">Agave validator</h2>
|
|
<p id="inspector-text">
|
|
Bazowy komponent po stronie <code>mevnode_sol</code>. Utrzymuje ledger, snapshoty,
|
|
lokalne RPC i stan kont. Bez zdrowego RPC nie przejdzie bootstrap DLOB publishera.
|
|
</p>
|
|
<dl>
|
|
<div>
|
|
<dt>Inputs</dt>
|
|
<dd id="inspector-inputs">entrypoints, snapshots, accounts state, network gossip</dd>
|
|
</div>
|
|
<div>
|
|
<dt>Outputs</dt>
|
|
<dd id="inspector-outputs">RPC responses, local chain state, base data for Yellowstone</dd>
|
|
</div>
|
|
<div>
|
|
<dt>Why it exists</dt>
|
|
<dd id="inspector-why">To jest zrodlo prawdy dla chain access i jedyne miejsce, gdzie stoi validator.</dd>
|
|
</div>
|
|
<div>
|
|
<dt>Risk / note</dt>
|
|
<dd id="inspector-risk">Nie przenosimy tego na bot. Read plane i execution plane maja pozostac oddzielone.</dd>
|
|
</div>
|
|
</dl>
|
|
<div class="hint">
|
|
Kliknij kolejny komponent albo wybierz filtr u gory, zeby przygaszac pozostale warstwy.
|
|
</div>
|
|
</aside>
|
|
</section>
|
|
|
|
<section class="service-map">
|
|
<div class="map-title">
|
|
<h2>k3s object map</h2>
|
|
<p>
|
|
To jest aktualny rozklad obiektow w klastrze <code>mevnode_bot</code>:
|
|
workloady, uslugi i typy ekspozycji. Tu widac juz konkretne
|
|
<code>Deployment</code>, <code>StatefulSet</code>, <code>ClusterIP</code>,
|
|
<code>NodePort</code> i stan gotowosci.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="namespaces">
|
|
<section class="namespace">
|
|
<h3>kube-system</h3>
|
|
<small>Podstawowe komponenty klastra potrzebne, zeby single-node k3s dzialal stabilnie.</small>
|
|
|
|
<article class="k8s-item">
|
|
<div class="k8s-head">
|
|
<div>
|
|
<strong>metrics-server</strong>
|
|
<div class="type">Deployment + ClusterIP</div>
|
|
</div>
|
|
<span class="status ok">1/1 ready</span>
|
|
</div>
|
|
<p>Service <code>metrics-server</code> na porcie <code>443</code>.</p>
|
|
</article>
|
|
|
|
<article class="k8s-item">
|
|
<div class="k8s-head">
|
|
<div>
|
|
<strong>coredns</strong>
|
|
<div class="type">Deployment + ClusterIP</div>
|
|
</div>
|
|
<span class="status ok">1/1 ready</span>
|
|
</div>
|
|
<p>Service <code>kube-dns</code> z DNS wewnatrz klastra.</p>
|
|
</article>
|
|
|
|
<article class="k8s-item">
|
|
<div class="k8s-head">
|
|
<div>
|
|
<strong>local-path-provisioner</strong>
|
|
<div class="type">Deployment</div>
|
|
</div>
|
|
<span class="status ok">1/1 ready</span>
|
|
</div>
|
|
<p>Provisioning lokalnego storage dla PVC na pojedynczym nodzie.</p>
|
|
</article>
|
|
</section>
|
|
|
|
<section class="namespace">
|
|
<h3>portainer</h3>
|
|
<small>Most pomiedzy Dockerowym Portainerem na hoscie a lokalnym klastrem k3s.</small>
|
|
|
|
<article class="k8s-item">
|
|
<div class="k8s-head">
|
|
<div>
|
|
<strong>portainer-agent</strong>
|
|
<div class="type">Deployment + NodePort</div>
|
|
</div>
|
|
<span class="status ok">1/1 ready</span>
|
|
</div>
|
|
<p>Service <code>portainer-agent</code> wystawiony jako <code>NodePort 30778 -> 9001</code>.</p>
|
|
</article>
|
|
|
|
<article class="k8s-item">
|
|
<div class="k8s-head">
|
|
<div>
|
|
<strong>portainer-agent-headless</strong>
|
|
<div class="type">Headless Service</div>
|
|
</div>
|
|
<span class="status ok">cluster wiring</span>
|
|
</div>
|
|
<p>Uzywany przez agenta do wewnetrznego cluster discovery.</p>
|
|
</article>
|
|
</section>
|
|
|
|
<section class="namespace">
|
|
<h3>trade-staging</h3>
|
|
<small>Glowny namespace produktu: web, ingest, DLOB i bazy danych.</small>
|
|
|
|
<article class="k8s-item">
|
|
<div class="k8s-head">
|
|
<div>
|
|
<strong>trade-frontend</strong>
|
|
<div class="type">Deployment + NodePort</div>
|
|
</div>
|
|
<span class="status ok">1/1 ready</span>
|
|
</div>
|
|
<p>Service <code>trade-frontend</code>: <code>NodePort 30081 -> 8081</code>.</p>
|
|
</article>
|
|
|
|
<article class="k8s-item">
|
|
<div class="k8s-head">
|
|
<div>
|
|
<strong>trade-api</strong>
|
|
<div class="type">Deployment + ClusterIP</div>
|
|
</div>
|
|
<span class="status ok">1/1 ready</span>
|
|
</div>
|
|
<p>Service <code>trade-api</code> na porcie <code>8787</code>.</p>
|
|
</article>
|
|
|
|
<article class="k8s-item">
|
|
<div class="k8s-head">
|
|
<div>
|
|
<strong>trade-ingestor</strong>
|
|
<div class="type">Deployment</div>
|
|
</div>
|
|
<span class="status ok">1/1 ready</span>
|
|
</div>
|
|
<p>Ingest pipeline bez osobnego Service. Przetwarza dane w tle.</p>
|
|
</article>
|
|
|
|
<article class="k8s-item">
|
|
<div class="k8s-head">
|
|
<div>
|
|
<strong>dlob-redis</strong>
|
|
<div class="type">Deployment + ClusterIP</div>
|
|
</div>
|
|
<span class="status ok">1/1 ready</span>
|
|
</div>
|
|
<p>Service <code>dlob-redis</code> na porcie <code>6379</code>.</p>
|
|
</article>
|
|
|
|
<article class="k8s-item">
|
|
<div class="k8s-head">
|
|
<div>
|
|
<strong>dlob-server</strong>
|
|
<div class="type">Deployment + ClusterIP</div>
|
|
</div>
|
|
<span class="status warn">0/1 not ready</span>
|
|
</div>
|
|
<p>Service <code>dlob-server</code> na porcie <code>6969</code>. Zalezy od poprawnie napelnionego stanu DLOB.</p>
|
|
</article>
|
|
|
|
<article class="k8s-item">
|
|
<div class="k8s-head">
|
|
<div>
|
|
<strong>dlob-publisher</strong>
|
|
<div class="type">Deployment</div>
|
|
</div>
|
|
<span class="status warn">0/1 not ready</span>
|
|
</div>
|
|
<p>Nie ma Service. To komponent zalezn y od zdrowego Agave RPC i bootstrapu Drift account reads.</p>
|
|
</article>
|
|
|
|
<article class="k8s-item">
|
|
<div class="k8s-head">
|
|
<div>
|
|
<strong>dlob-worker / depth / slippage</strong>
|
|
<div class="type">Deployments</div>
|
|
</div>
|
|
<span class="status ok">1/1 ready</span>
|
|
</div>
|
|
<p>Workery pochodnych widokow orderbooka, bez ekspozycji zewnetrznej.</p>
|
|
</article>
|
|
|
|
<article class="k8s-item">
|
|
<div class="k8s-head">
|
|
<div>
|
|
<strong>postgres</strong>
|
|
<div class="type">StatefulSet + Headless Service</div>
|
|
</div>
|
|
<span class="status ok">1/1 ready</span>
|
|
</div>
|
|
<p>Service <code>postgres</code> na <code>5432</code>, headless dla stabilnej tozsamosci StatefulSet.</p>
|
|
</article>
|
|
|
|
<article class="k8s-item">
|
|
<div class="k8s-head">
|
|
<div>
|
|
<strong>hasura</strong>
|
|
<div class="type">Deployment + ClusterIP</div>
|
|
</div>
|
|
<span class="status ok">1/1 ready</span>
|
|
</div>
|
|
<p>Service <code>hasura</code> na porcie <code>8080</code>.</p>
|
|
</article>
|
|
</section>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
|
|
<script>
|
|
const model = {
|
|
agave: {
|
|
title: 'Agave validator',
|
|
kind: 'read / ops / source of truth',
|
|
text: 'Bazowy komponent po stronie mevnode_sol. Utrzymuje ledger, snapshoty, lokalne RPC i stan kont. Bez zdrowego RPC nie przejdzie bootstrap DLOB publishera.',
|
|
inputs: 'entrypoints, snapshots, accounts state, network gossip',
|
|
outputs: 'RPC responses, local chain state, feed source for Yellowstone plugin',
|
|
why: 'Validator i chain access zostaja tylko na sol. Bot nie ma nigdy Agave ani ledgera.',
|
|
risk: 'Jesli read RPC dusi sie na getAccountInfo/getMultipleAccounts, read side na bot traci bootstrap.'
|
|
},
|
|
yellowstone: {
|
|
title: 'Yellowstone gRPC',
|
|
kind: 'read / live stream',
|
|
text: 'Plugin Geyser wystawia pushowy stream account updates, slotow, transakcji i block meta. To jest glowny low-latency feed dla bot.',
|
|
inputs: 'Agave runtime, Geyser plugin callbacks, x-token auth',
|
|
outputs: 'gRPC subscriptions na 10.91.0.1:10000',
|
|
why: 'Live stream jest lepszy do ingestu niz polling RPC dla wszystkiego.',
|
|
risk: 'To nie zastapi wszystkich request/response z RPC. Biblioteki dalej moga potrzebowac punktowych odczytow kont.'
|
|
},
|
|
'sol-metrics': {
|
|
title: 'Plugin metrics',
|
|
kind: 'ops / observability',
|
|
text: 'Metryki gRPC i pluginu Geyser wystawione po wg0 do diagnostyki.',
|
|
inputs: 'Agave plugin internals',
|
|
outputs: 'Prometheus metrics na 10.91.0.1:8999',
|
|
why: 'Bez metryk ciezko diagnozowac lagi, reconnecty i zachowanie snapshot/replay.',
|
|
risk: 'To jest tylko read-only ops path, nie sluzy do write path.'
|
|
},
|
|
'yellowstone-consumer': {
|
|
title: 'yellowstone-consumer',
|
|
kind: 'read / k3s workload',
|
|
text: 'Pierwszy komponent po stronie bot, ktory konsumuje live stream i mapuje go na wewnetrzne modele.',
|
|
inputs: 'Yellowstone gRPC subscriptions: accounts, slots, transactions',
|
|
outputs: 'events dla strategy-engine, ingestora i dalszych workerow',
|
|
why: 'Oddziela surowy stream chaina od logiki biznesowej.',
|
|
risk: 'Musi byc lekki i odporny na reconnect. Nie powinien wykonywac ciezkich transformacji w goracej sciezce.'
|
|
},
|
|
'trade-ingestor': {
|
|
title: 'trade-ingestor',
|
|
kind: 'read / data',
|
|
text: 'Warstwa transformacji i zapisu. Odbiera dane z consumerow i utrzymuje je w ksztalcie uzytecznym dla reszty stacku.',
|
|
inputs: 'parsed chain events, market data, application transforms',
|
|
outputs: 'Postgres, Redis, internal service feeds',
|
|
why: 'Normalizuje dane przed API i frontendem.',
|
|
risk: 'Jesli zrobi sie za ciezki, zacznie opozniac caly read path.'
|
|
},
|
|
'dlob-publisher': {
|
|
title: 'dlob-publisher',
|
|
kind: 'read / data / drift-specific',
|
|
text: 'Buduje stan DLOB. Nadal potrzebuje zdrowego Agave RPC do bootstrapu i odczytow kont typu getMultipleAccounts.',
|
|
inputs: 'Agave RPC, opcjonalnie Yellowstone stream, Drift account reads',
|
|
outputs: 'Redis state dla dlob-server i workerow',
|
|
why: 'To jest most miedzy chain state i orderbookiem widocznym dla produktu.',
|
|
risk: 'To byl glowny punkt blokady, kiedy Agave read RPC nie odpowiadalo na ciezsze account reads.'
|
|
},
|
|
'dlob-server': {
|
|
title: 'dlob-server',
|
|
kind: 'read / data / ops-facing',
|
|
text: 'Wystawia gotowy stan DLOB dla API, frontendu i ewentualnych klientow wewnetrznych.',
|
|
inputs: 'Redis, workers, precomputed depth/slippage state',
|
|
outputs: 'read endpoints dla produktu',
|
|
why: 'UI i API nie powinny bezposrednio dotykac surowego stanu z publishera.',
|
|
risk: 'Bez poprawnie napelnionego Redis serwuje pusty albo niepelny widok.'
|
|
},
|
|
'strategy-engine': {
|
|
title: 'strategy-engine',
|
|
kind: 'write / execution',
|
|
text: 'Tu zapada decyzja tradingowa. Konsumuje live feed i stan rynku, ale nie powinien robic wszystkiego sam.',
|
|
inputs: 'yellowstone-consumer, DLOB state, risk rules',
|
|
outputs: 'intencje zlecen przekazywane do order-manager / tx-router',
|
|
why: 'Rozdziela decyzje tradingowe od transportu transakcji.',
|
|
risk: 'Jesli tu wlozysz za duzo efektow ubocznych, trudno bedzie kontrolowac opoznienia i awarie.'
|
|
},
|
|
'tx-router': {
|
|
title: 'tx-router',
|
|
kind: 'write / transport',
|
|
text: 'Wybiera sciezke wysylki transakcji: RPC, TPU albo Jito. To osobna warstwa od strategii.',
|
|
inputs: 'signed transactions lub order intents',
|
|
outputs: 'RPC sendTransaction, TPU submit, Jito submit',
|
|
why: 'Read path i write path musza byc osobnymi decyzjami operacyjnymi.',
|
|
risk: 'Nie nalezy mieszac wyboru write path z warstwa UI ani z parserami read-side.'
|
|
},
|
|
postgres: {
|
|
title: 'Postgres + Hasura',
|
|
kind: 'data / durable storage',
|
|
text: 'Trwaly storage dla stanu aplikacji, historii, pozycji i konfiguracji. Hasura robi warstwe GraphQL nad tym modelem.',
|
|
inputs: 'trade-ingestor, API writes, migrations, bootstrap jobs',
|
|
outputs: 'query layer dla API i frontendu',
|
|
why: 'Jedno glowne zrodlo stanu aplikacji po stronie bot.',
|
|
risk: 'Nie warto mieszac tu feedu tick-by-tick, jesli pozniej pojawi sie potrzeba ciezszej analityki; wtedy lepszy jest ClickHouse.'
|
|
},
|
|
redis: {
|
|
title: 'Redis',
|
|
kind: 'data / fast state',
|
|
text: 'Szybki cache i stan posredni dla DLOB, pub/sub i workerow o niskiej latencji.',
|
|
inputs: 'dlob-publisher, workers, internal events',
|
|
outputs: 'dlob-server, API caches, ephemeral coordination',
|
|
why: 'Odciaza Postgresa z goracej sciezki read-side.',
|
|
risk: 'Nie moze byc jedynym trwalym zrodlem prawdy.'
|
|
},
|
|
'frontend-api': {
|
|
title: 'Frontend + API + Ingress',
|
|
kind: 'ops / product surface',
|
|
text: 'Warstwa produktu. Frontend pokazuje dane, a API agreguje odpowiedzi z DB i serwisow read-side.',
|
|
inputs: 'Postgres, Hasura, dlob-server, internal APIs',
|
|
outputs: 'HTTP/HTTPS dla uzytkownika i operatora',
|
|
why: 'Produkt ma zyc na bot, nie na hoście validatora.',
|
|
risk: 'Nie powinien miec bezposrednich zaleznosci do ledgera ani do samego procesu validatora.'
|
|
},
|
|
'ops-stack': {
|
|
title: 'Prometheus / Grafana / Portainer / GitOps',
|
|
kind: 'ops / platform',
|
|
text: 'Warstwa operacyjna klastra: monitoring, widocznosc workloadow w Portainerze i deployment z Gitea.',
|
|
inputs: 'k3s metrics, app metrics, manifests from Gitea',
|
|
outputs: 'dashboards, alerts, cluster visibility, controlled rollouts',
|
|
why: 'Przestajesz zarzadzac luznymi kontenerami i trzymasz runtime w jednym modelu.',
|
|
risk: 'To nie powinno siedziec na sol. Validator host ma byc maksymalnie prosty.'
|
|
}
|
|
};
|
|
|
|
const cards = [...document.querySelectorAll('.card')];
|
|
const filterButtons = [...document.querySelectorAll('.filter')];
|
|
const links = [...document.querySelectorAll('.link')];
|
|
const laneLabels = [...document.querySelectorAll('.lane-label')];
|
|
const inspector = {
|
|
kind: document.getElementById('inspector-kind'),
|
|
title: document.getElementById('inspector-title'),
|
|
text: document.getElementById('inspector-text'),
|
|
inputs: document.getElementById('inspector-inputs'),
|
|
outputs: document.getElementById('inspector-outputs'),
|
|
why: document.getElementById('inspector-why'),
|
|
risk: document.getElementById('inspector-risk')
|
|
};
|
|
|
|
let activeFilter = 'all';
|
|
let activeCard = 'agave';
|
|
|
|
function renderInspector(id) {
|
|
const item = model[id];
|
|
if (!item) return;
|
|
inspector.kind.textContent = item.kind;
|
|
inspector.title.textContent = item.title;
|
|
inspector.text.innerHTML = item.text;
|
|
inspector.inputs.textContent = item.inputs;
|
|
inspector.outputs.textContent = item.outputs;
|
|
inspector.why.textContent = item.why;
|
|
inspector.risk.textContent = item.risk;
|
|
}
|
|
|
|
function applyFilter() {
|
|
cards.forEach((card) => {
|
|
const kinds = card.dataset.kind.split(' ');
|
|
const match = activeFilter === 'all' || kinds.includes(activeFilter);
|
|
card.classList.toggle('dimmed', !match);
|
|
});
|
|
|
|
links.forEach((link) => {
|
|
const match = activeFilter === 'all' || link.dataset.kind === activeFilter;
|
|
link.classList.toggle('dimmed', !match);
|
|
});
|
|
|
|
laneLabels.forEach((label) => {
|
|
const match = activeFilter === 'all' || label.dataset.kind === activeFilter;
|
|
label.classList.toggle('dimmed', !match);
|
|
});
|
|
|
|
filterButtons.forEach((button) => {
|
|
button.classList.toggle('active', button.dataset.filter === activeFilter);
|
|
});
|
|
}
|
|
|
|
cards.forEach((card) => {
|
|
card.addEventListener('click', () => {
|
|
activeCard = card.dataset.id;
|
|
cards.forEach((x) => x.classList.toggle('active', x.dataset.id === activeCard));
|
|
renderInspector(activeCard);
|
|
});
|
|
});
|
|
|
|
filterButtons.forEach((button) => {
|
|
button.addEventListener('click', () => {
|
|
activeFilter = button.dataset.filter;
|
|
applyFilter();
|
|
});
|
|
});
|
|
|
|
renderInspector(activeCard);
|
|
applyFilter();
|
|
</script>
|
|
</body>
|
|
</html>
|